1 year ago

#222653

test-img

Var Delean

Android SeekBar: Move by thumb only

I know this question was asked and somewhat answered before, but none of the solutions I found so far really worked out for me: I have a custom seekBar with a large square block for the thumb, which I draw on the fly, and I want the seekBar progress to change only when the thumb is being dragged. Large thumb seekBar

I looked around and tried out all of the proposed solutions I could find, and some worked better then others, but they all have a big issue (for me at least): if I detect the boundaries of the thumb in order to start dragging the thumb, the onTouch ACTION_DOWN event makes the thumb jump to whatever position the pointer is on the screen, within the thumb's boundaries, of course.

If the thumb size is small, this is almost a non issue, but on a large thumb, this behavior is really annoying.

To make matters worse, if I declare a thumb offset of 0 to keep the thumb inside the seekBar, the jumping behavior changes with respect to where on the seekbar the thumb is: indetermined jump direction right in the middle of the seekbar, or it jumps to the right if the thumb is on the left half of the seekbar, or to the left if the thumb is on the right half of the seekbar.

Here's my starting code, I removed all of my failed attempts to make the seekBar's thumb move only when a drag starts:

activity_main.xml

   <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <TextView
        android:id="@+id/progressText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/mySeekbar"
        app:layout_constraintVertical_bias="0.154" />

    <com.example.thumbonlyseekbar.MySeekBar
        android:id="@+id/mySeekbar"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="150dp"
        android:layout_marginEnd="20dp"
        android:indeterminate="false"
        android:max="99"
        android:progress="0"
        android:progressDrawable="@drawable/my_seekbar"
        android:thumb="@drawable/thumb"
        android:thumbOffset="0dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
  
</androidx.constraintlayout.widget.ConstraintLayout>

MySeekbar.java

package com.example.thumbonlyseekbar;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import androidx.annotation.NonNull;


public class MySeekBar extends androidx.appcompat.widget.AppCompatSeekBar {

    Drawable mThumb;

    public MySeekBar(@NonNull Context context) {
        super(context);
    }

    public MySeekBar(@NonNull Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MySeekBar(@NonNull Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }


    @Override
    public void setThumb(Drawable thumb) {
        super.setThumb(thumb);
        mThumb = thumb;
    }
    public Drawable getSeekBarThumb() {
        return mThumb;
    }

}

MainActivity.java

package com.example.thumbonlyseekbar;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    MySeekBar customSeekBar;
    TextView progressValueText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        customSeekBar = findViewById(R.id.mySeekbar);

        progressValueText = findViewById(R.id.progressText);
        progressValueText.setText(String.valueOf(customSeekBar.getProgress()));


        customSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                progressValueText.setText(String.valueOf(progress));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

    }
}

thumb.xml

package com.example.thumbonlyseekbar;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    MySeekBar customSeekBar;
    TextView progressValueText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        customSeekBar = findViewById(R.id.mySeekbar);

        progressValueText = findViewById(R.id.progressText);
        progressValueText.setText(String.valueOf(customSeekBar.getProgress()));


        customSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                progressValueText.setText(String.valueOf(progress));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

    }
}

my_seekbar.xml

package com.example.thumbonlyseekbar;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    MySeekBar customSeekBar;
    TextView progressValueText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        customSeekBar = findViewById(R.id.mySeekbar);

        progressValueText = findViewById(R.id.progressText);
        progressValueText.setText(String.valueOf(customSeekBar.getProgress()));


        customSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                progressValueText.setText(String.valueOf(progress));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

    }
}

If you would like to see an example of what I mean by a no-thumb-jump seekBar, please take a look at the AMPLIFi Remote app in Google Play Store (it may only work on older devices, it works on my OREO 8.1, you may need to try it in an emulator that allows downloads from Google Play if you don't have an older device). The thumb's progress is unbelievably smooth, and there's absolutely no jumping around. Whoever wrote that app did an awesome job!

Many, many thanks, any suggestion is much appreciated!

java

android

seekbar

0 Answers

Your Answer

Accepted video resources