问题
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?
回答1:
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
回答2:
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();
}
});
}
}
回答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);
}
}
}
回答4:
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
*
* @author Jetsada Machom <jim@imjim.im>
*/
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);
}
}
回答5:
I came up with a solution to this, it's not very pretty, but it should do the trick...
Here's the idea:
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)
When the thumb is pressed, call 'bringToFront' method on the slider
- 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);
回答6:
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.
回答7:
looking at @lordmegamax code, i found somethings that doesnt work.
- int oldProgress cant stay inside onCreate, you need declare it outside.
- 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.
回答8:
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);
}
}
});
回答9:
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
}
}
回答10:
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.
回答11:
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;
}
});
回答12:
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.
来源:https://stackoverflow.com/questions/5126091/prevent-single-tap-from-changing-seekbar-progress