Prevent single tap from changing SeekBar progress
Asked Answered
T

13

12

I am using a SeekBar in my Android app. When a user single taps anywhere on the SeekBar, its progress value is changed. I only want the progress value to change when the user slides the SeekBar thumb (just like a UISlider in iOS).

I have tried setting the clickable property of the SeekBar to false but that didn't work. How can I achieve the desired behavior?

Tot answered 26/2, 2011 at 9:24 Comment(0)
S
10

I faced the same issue this week and I resolved it using a custom SeekBar: following my code:

public class Slider extends SeekBar {

private Drawable mThumb;

public Slider(Context context) {
    super(context);

}

public Slider(Context context, AttributeSet attrs) {
    super(context, attrs);

}

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

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {

        if (event.getX() >= mThumb.getBounds().left
                && event.getX() <= mThumb.getBounds().right
                && event.getY() <= mThumb.getBounds().bottom
                && event.getY() >= mThumb.getBounds().top) {

            super.onTouchEvent(event);
        } else {
            return false;
        }
    } else if (event.getAction() == MotionEvent.ACTION_UP) {
        return false;
    } else {
        super.onTouchEvent(event);
    }

    return true;
}}

Hope this helps

Shrewd answered 7/6, 2013 at 21:7 Comment(2)
FTW: thumb.getBounds().contains((int)event.getX(), (int)event.getY())Steve
tried this solution, this solve the title information but it will cause the thumb size to change unexpectedly such as larger thumb sizeAcetabulum
D
8

Check out this other thread:

User can not interact with the Seekbar

I tried the following and it works well. Note my seekbar has android:max property set to 100 like this:


<SeekBar
android:id="@+id/EnableBar"
android:layout_span="2"
android:max="100"
/>

package com.androidbook.hiworld;

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


public class HiWorldActivity extends Activity {
    int originalProgress;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        SeekBar seek = (SeekBar)findViewById(R.id.EnableBar);

        seek.setOnSeekBarChangeListener(
        new SeekBar.OnSeekBarChangeListener() {
           public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
               ((TextView)findViewById(R.id.SeekTxt)).setText("Value: "+progress);
               if(fromTouch == true){
                  // only allow changes by 1 up or down
                  if ((progress > (originalProgress+24))
                       || (progress < (originalProgress-24))) {
                     seekBar.setProgress( originalProgress);
                  } else {
                      originalProgress = progress;
                  }
               } 
           }

           @Override
           public void onStopTrackingTouch(SeekBar seekBar) {
               //Nothing here..                
           }

           @Override
           public void onStartTrackingTouch(SeekBar seekBar) {
               originalProgress = seekBar.getProgress();
           }
        });
    }
}
Disconcerted answered 1/8, 2011 at 22:1 Comment(1)
why this value +/- 24?Specie
J
3

It's too late, but maybe someone will need the solution. I extend custom SeekBar and override onTouchEvent.

package com.timera.android.common.view;

import android.content.Context;
import android.util.AttributeSet; 
import android.view.MotionEvent;
import android.widget.SeekBar;

public class OnlySeekableSeekBar extends SeekBar {

public OnlySeekableSeekBar(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public OnlySeekableSeekBar(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public OnlySeekableSeekBar(Context context) {
    super(context);
}

@Override
public boolean onTouchEvent(MotionEvent event) {

    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
        final int width = getWidth();
        final int available = width - getPaddingLeft() - getPaddingRight();
        int x = (int) event.getX();
        float scale;
        float progress = 0;
        if (x < getPaddingLeft()) {
            scale = 0.0f;
        } else if (x > width - getPaddingRight()) {
            scale = 1.0f;
        } else {
            scale = (float) (x - getPaddingLeft()) / (float) available;
        }
        final int max = getMax();
        progress += scale * max;
        if (progress < 0) {
            progress = 0;
        } else if (progress > max) {
            progress = max;
        }

        if (Math.abs(progress - getProgress()) < 10)
            return super.onTouchEvent(event);
        else
            return false;
    default:
        return super.onTouchEvent(event);
    }
}
}
Jacindajacinta answered 28/11, 2013 at 14:7 Comment(0)
U
1

I came up with a solution to this, it's not very pretty, but it should do the trick...

Here's the idea:

  1. Create an invisible overlay that will cover all of the slider except the thumb-button

    (I used a blacked out seekbar to act as an overlay)

  2. When the thumb is pressed, call 'bringToFront' method on the slider

  3. When the thumb is released, call 'bringToFront' method on the invisible overlay

Note for this to work you must alter the size of the overlay so that it will cover everything but the thumb-button (I suggest using two overlays [one for each side of the thumb-button])

When you release the thumb-button you should then resize the overlays

... like I said, it ain't pretty. I'm sure there are much better ways to do it, but if you must have it done, I would try this.

   yourBarChangeListener yourBarChangeListener = new SeekBar.OnSeekBarChangeListener() { 

    @Override 
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
     } 

    @Override 
    public void onStartTrackingTouch(SeekBar seekBar) {
     // YOUR CODE HERE 
    } 
    @Override 
    public void onStopTrackingTouch(SeekBar seekBar) { 
    } 
}; 
yourBar.setOnSeekBarChangeListener(yourBarChangeListener);
Ursas answered 22/4, 2011 at 19:8 Comment(2)
How do you detect when the thumb is pressed?Perineurium
yourBarChangeListener yourBarChangeListener = new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { // YOUR CODE HERE } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }; yourBar.setOnSeekBarChangeListener(yourBarChangeListener);Ursas
L
1

This one perfectly work with the event listener inside SeekBar.

PS. I improved this code from Slim to make it more generalize.

/**
 * ProtectedSeekBar
 * 01/27/15
 */
public class ProtectedSeekBar extends SeekBar {

    private Drawable mThumb;

    public ProtectedSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public ProtectedSeekBar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ProtectedSeekBar(Context context) {
        super(context);
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_DOWN) {
            if( event.getX() < mThumb.getBounds().left ||
                event.getX() > mThumb.getBounds().right ||
                event.getY() > mThumb.getBounds().bottom ||
                event.getY() < mThumb.getBounds().top) {
                return false;
            }
        }
        return super.onTouchEvent(event);
    }
}
Lukelukens answered 27/1, 2015 at 15:31 Comment(2)
SeekBar exposes a getThumb method; does that remove the need to store the thumb drawable yourself?Longlived
@Longlived SeekBar.getThumb exposed in API >= 16. so yes it's possible to remove the method if you don't need the backward compatibility.Lukelukens
M
0
    int oldProgress;
    boolean isOn = true;
    vSeek.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            oldProgress = seekBar.getProgress();
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            if (oldProgress == seekBar.getProgress()) {
                if (isOn) {
                    seekBar.setThumb(getResources().getDrawable(R.drawable.blck_btn));
                    isOn = false;
                } else {
                    seekBar.setThumb(getResources().getDrawable(R.drawable.blck_btn_selected));
                    isOn = true;
                }
            }
        }

You can compare progress when stop tracking.

Mordant answered 26/2, 2013 at 18:24 Comment(0)
H
0

looking at @lordmegamax code, i found somethings that doesnt work.

  1. int oldProgress cant stay inside onCreate, you need declare it outside.
  2. if you seekBar always start from the beginning, onStartTrackingTouch will always return 0, so your onStopTrackingTouch will never work.

Thinking a little bit, i found a solution.

//SeekBar Slide
    skbLogin.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
        //Force Slide From Beginning
        public void onStartTrackingTouch(SeekBar seekBar) { continuosProgress = false; }
        //Execute when reach the max
        public void onStopTrackingTouch(SeekBar seekBar) {
            if(continuosProgress)
                if(seekBar.getProgress() == 100) btnLogin();
                else skbRollBack();
            else
                seekBar.setProgress(0);
        }
        //Check Slide from Beginning
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            if(progress < 10) continuosProgress = true;
        }
    });

Hope it helps. best regards

PS: this is my first post, sorry if I made something wrong.

Hauteloire answered 26/4, 2013 at 17:52 Comment(0)
H
0

By this code you can disable thumb to move by user

     SeekBar progress = (SeekBar) findViewById(R.id.timer);
      seekBar.setProgress(time);

where "time" is a integer variable and hold the progress

     progress.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {

        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {

        }

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress,
                boolean fromUser) {
            if(fromUser){
                seekBar.setProgress(time);
            }

        }
    });
Hepner answered 9/4, 2015 at 13:10 Comment(0)
P
0
private class UpdateListener implements OnSeekBarChangeListener {

    // max step user can change at once
    private static final int THUMB_MAX_MOVE = 5;

    // current seekbar value (50 is initial seekbar value as defined in xml)
    private int currentProgress = 50;

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

        // save current value before user jumps around
        currentProgress = seekBar.getProgress();
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {}

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

        // check if we've jumped too far
        if (Math.abs(currentProgress - progress) > THUMB_MAX_MOVE) {

            // if so, revert to last progress and return
            seekBar.setProgress(currentProgress);
            return;
        }

        // if not, move was valid and update current progress
        currentProgress = progress;

        // do anything more here
    }
}
Profit answered 17/5, 2016 at 5:19 Comment(0)
L
0

Another option that I don't see suggested yet: a subclass that translates received motion events in order to make every gesture appear to start at the center of the thumb. This has the following impacts on the underlying seek bar behavior:

  • tapping in place produces no movement of the thumb (same as other proposed solutions);
  • performing a swipe gesture that starts away from the thumb can still smoothly translate the thumb (different from other proposed solutions).

Code

/**
 * Translates every gesture to be centered at the current thumb location.
 */
public final class ThumbCentricSeekBar extends SeekBar {

    // Constructors go here...

    private Float offsetX;
    private Float offsetY;

    @Override
    public boolean onTouchEvent(final MotionEvent event) {
        if (event.getAction() == ACTION_DOWN && offsetX == null) {
            offsetX = getThumb().getBounds().centerX() - event.getX();
            offsetY = getThumb().getBounds().centerY() - event.getY();
        }

        event.offsetLocation(offsetX, offsetY);

        if (event.getAction() == ACTION_UP) {
            offsetX = null;
            offsetY = null;
        }

        return super.onTouchEvent(event);
    }

}

Demo

Touch location is shown to illustrate the behavior I described.

enter image description here

Longlived answered 2/3, 2017 at 20:16 Comment(1)
Note that this solution does not protect against jumps when a second pointer is introduced (but it could be updated to do so without much difficulty).Longlived
A
0

For disable single tap on seebBar you may watch time between DOWN and UP events, Usually duration of single tap less than 100 ms.

seekBar.setOnTouchListener(new View.OnTouchListener() {
      private long startTime = 0;
      private long endTime = 0;

      @Override
      public boolean onTouch(View view, MotionEvent motionEvent) {

        if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
          startTime = motionEvent.getEventTime();
          return true;
        }
        if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
          endTime = motionEvent.getEventTime();
          if (Math.abs(startTime-endTime)<=100)
            return true;
          else
            return false;
        }
        Log.d(TAG, String.valueOf(motionEvent.getEventTime()));
        return false;
      }
    });
Alfredalfreda answered 22/9, 2017 at 12:51 Comment(0)
I
0

The situation I was facing is that the SeekBar was created by another piece of code so I couldn't create a subclass with the onTouchEvent() handler. Instead I overrode the OnTouchListener for the instance I wanted to mess with...

SeekBar sb = (SeekBar) findViewById(R.id.seekbar);

sb.setOnTouchListener(new OnTouchListener()
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Drawable thumb = ((SeekBar) v).getThumb();

        if (   event.getX() < thumb.getBounds().left
            || event.getX() > thumb.getBounds().right
            || event.getY() < thumb.getBounds().top
            || event.getY() > thumb.getBounds().bottom) {
            // event occurred outside the thumb slider so ignore it
            return true;
        } else {
            // event occurred inside the thumb slider so pass it on
            return false;
        }
    }
});

Yes, I could simply return the contents of the "if" expression, but that wouldn't make the code readable.

Inseparable answered 21/11, 2018 at 15:10 Comment(0)
I
0

See answer here. It only allows progress to be changed via the thumb rather than the progress drawable. https://mcmap.net/q/813287/-android-seekbar-solution

Irrational answered 3/6, 2020 at 4:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.