Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android custom SeekBar - progress is not following the thumb

I want to create a custom SeekBar, but I get this problem where the progress is not following the thumb of the SeekBar at certain places (around progress value from 0-10 and from 90-100). I believe it happens because I set the thumbOffset to 0, in order for the thumb to stay inside the progress bar. I want to make an iPhone style SeekBar.

SeekBar at progress 0 (is OK):

enter image description here

SeekBar at progress 5 (is NOT OK):

enter image description here

SeekBar at progress 50 (is OK):

enter image description here

SeekBar at progress 90 (is NOT OK):

enter image description here

SeekBar at progress 100 (is OK):

enter image description here

It looks like the thumbOffset is not being considered when drawing the progress based on the thumb position. I tried changing the thumbOffset to half of the width of the thumb and did all kinds of combinations with different paddings, but nothing solved the problem. I also tried all of the other solutions I found on SO.

Here's the XML I'm using:

SeekBar definition:

<SeekBar
    android:id="@+id/scan_seekbar"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_weight="0.5"
    android:max="100"
    android:progress="5"
    android:progressDrawable="@drawable/bg_seekbar"
    android:splitTrack="false"
    android:thumbOffset="0dp"
    android:thumb="@drawable/seekbar_thumb" />

Drawable bg_seekbar.xml:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape android:shape="rectangle">
            <size android:height="48dp"/>
            <corners android:radius="25dp"/>
            <stroke android:color="@color/semiLightBlue" android:width="1dp"/>
        </shape>
    </item>

    <item android:id="@android:id/progress">
        <clip>
            <shape android:shape="rectangle">
                <corners android:radius="25dp"/>
                <size android:height="48dp"/>
                <stroke android:color="@color/colorAccent" android:width="1dp"/>
                <solid android:color="@color/colorAccent"/>
            </shape>
        </clip>
    </item>
</layer-list>

SeekBar thumb seekbar_thumb.xml:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/colorAccent"/>
            <corners android:radius="25dp"/>

            <padding android:bottom="8dp" android:top="8dp" android:left="16dp" android:right="16dp" />
        </shape>
    </item>


    <item android:drawable="@drawable/ic_swipe_white" android:gravity="center" android:height="32dp" android:width="44dp"/>
</layer-list>

Did anyone else face this problem?

like image 965
AndrejH Avatar asked May 19 '20 11:05

AndrejH


2 Answers

Your implementation relies on a ClipDrawable to draw the progress bar. The progress drawable spans the entire length of the progress bar but is shortened, or clipped, by the ClipDrawable. This leaves the rounded corners on the left of the progress bar but squares off the right side. You are seeing the squared-off side as the progress bar grows, catches up to and overtakes the thumb.

There are probably several ways to solve this, but let's go for something that is XML-based. Let's replace the ClipDrawable with a ScaleDrawable as follows:

bg_seekbar.xml

<layer-list>
    <item android:id="@android:id/background">
        <shape android:shape="rectangle">
            <size android:height="48dp" />
            <corners android:radius="25dp" />
            <stroke
                android:width="1dp"
                android:color="@color/semiLightBlue" />
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <scale
            android:scaleWidth="100%"
            android:useIntrinsicSizeAsMinimum="true">
            <shape android:shape="rectangle">
                <corners android:radius="25dp" />
                <size
                    android:width="50dp"
                    android:height="48dp" />
                <stroke
                    android:width="1dp"
                    android:color="@color/colorAccent" />
                <solid android:color="@color/colorAccent" />
            </shape>
        </scale>
    </item>
</layer-list>

The scaleWidth attribute tells us that the drawable will be scaled to 100% when the level is at the maximum. The useIntrinsicSizeAsMinimum attribute which, by the way, does not seem to be documented, specifies that the minimum size of the drawable will be its intrinsic width which is 50dp x 48dp. This is not exactly true since the drawable will have zero width and height when the level is zero but will pop to the intrinsic size when the level is 1 or more.

Here is what happens with the progress bar after this change is made. I have gone with a default thumb to make it clear what is happening:

enter image description here

As you can see, once the thumb starts to move the progress bar leaps to its minimum size and grows from there.

So, let's reintroduce the original thumb. This thumb will overlay the progress bar at the start and hide the jump to the minimum size. We are looking just to cover that area so the white doesn't show through.

enter image description here

like image 158
Cheticamp Avatar answered Nov 16 '22 20:11

Cheticamp


I solved the problem this way:

First, the thumb never disappears. It appears when the progress is 0 or 100. When we use offset, thumb pops outside.

bg_seekbar_first.xml

We will use this when progress < max / 2

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background"
        android:gravity="center_vertical|fill_horizontal">
        <shape android:shape="rectangle">
            <corners android:radius="24dp"/>
            <size android:height="48dp" />
            <stroke android:width="1dp" android:color="@color/colorAccent"/>
        </shape>
    </item>
    <item android:id="@android:id/progress"
        android:gravity="center_vertical|fill_horizontal">
        <scale android:scaleWidth="100%">
            <shape android:shape="rectangle"
                android:tint="@color/colorAccent">
                <corners android:radius="0dp"/>
                <size android:height="48dp" />
                <solid android:color="@color/colorAccent" />
            </shape>
        </scale>
    </item>
</layer-list>

bg_seekbar_second.xml

We will use this when progress > max / 2

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background"
        android:gravity="center_vertical|fill_horizontal">
        <shape android:shape="rectangle">
            <corners android:radius="24dp"/>
            <size android:height="48dp" />
            <stroke android:width="1dp" android:color="@color/colorAccent"/>
        </shape>
    </item>
    <item android:id="@android:id/progress"
        android:gravity="center_vertical|fill_horizontal">
        <scale android:scaleWidth="100%">
            <shape android:shape="rectangle"
                android:tint="@color/colorAccent">
                <corners android:radius="24dp"/>
                <size android:height="48dp" />
                <solid android:color="@color/colorAccent" />
            </shape>
        </scale>
    </item>
</layer-list>

seekbar_thumb.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/colorAccent"/>
            <corners android:topRightRadius="24dp"
                 android:bottomRightRadius="24dp"/>

            <padding android:bottom="8dp" android:top="8dp" android:left="16dp" android:right="16dp" />
        </shape>
    </item>


    <item android:drawable="@android:drawable/ic_menu_close_clear_cancel" android:gravity="center" android:height="32dp" android:width="44dp"/>
</layer-list>

I changed radius of thumb

enter image description here

<com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="48dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        app:cardCornerRadius="24dp"
        android:layout_marginTop="64dp"
        >
        <SeekBar
            android:id="@+id/scan_seekbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            android:max="100"
            android:paddingStart="0dp"
            android:paddingEnd="0dp"
            android:progress="1"
            android:progressDrawable="@drawable/bg_seekbar_first"
            android:splitTrack="false"
            android:thumb="@drawable/seekbar_thumb"
            android:thumbOffset="0dp"
            />
    </com.google.android.material.card.MaterialCardView>

I added a cardView to solve this problem:

enter image description here

scan_seekbar.setOnSeekBarChangeListener(object  : SeekBar.OnSeekBarChangeListener{
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                if(progress < seekBar?.max!! / 2){
                    seekBar.progressDrawable = ContextCompat.getDrawable(context!!,R.drawable.bg_seekbar_first)
                }else{
                    seekBar.progressDrawable = ContextCompat.getDrawable(context!!,R.drawable.bg_seekbar_second)
                }
            }

            override fun onStartTrackingTouch(seekBar: SeekBar?) {
            }

            override fun onStopTrackingTouch(seekBar: SeekBar?) {
            }

        })

Then I used this code(Kotlin) onProgressChanged to solve this problem :

enter image description here

And result:

enter image description here

I hope it works for you.

like image 42
Kasım Özdemir Avatar answered Nov 16 '22 21:11

Kasım Özdemir