I've got an android app with a TableLayout with each row having a SeekBar and a ToggleButton. The rows go beyond the visible range of the screen so it's scrollable. When I touch and drag to scroll the page up and in the process touch the SeekBar, it immediately changes the position of th开发者_如何学Pythone "thumb" instead of scrolling the page. However, the ToggleButton does not behave this way; instead, I can start the drag on the button and scroll then release without the ToggleButton changing state.
Is there anyway to get the SeekBar to behave this way, such that touching it to start a drag will not cause the bar to change positions but instead scroll the page?
I've had similar problems. The only solution I could find (when the seekbar is in a listview) was to disable the seekbar until the item is clicked.
Solution
In the ArrayAdapter I set both enabled and focusable to false and added the SeekBar listener, setting the attributes to false allowed me to use the list item onItemClicked listener. Inside the onItemCLickListener I retreived the seekbar, set the attributes to true, this meant it could be slid up or down. I then disabled it after the adjustment had been made. code below
ArrayAdapter Snippet
this code is inside the creation of the list item, in which the seekbar is housed
seekBar.setClickable(false);
seekBar.setFocusable(false);
seekBar.setEnabled(false);
/* attach listener */
attachProgressUpdatedListener(seekBar, positionOfItemInList);
AttachProgressUpdatedListener
this method attaches the listener to the seekbar, inside the arrayAdapter class
private void attachProgressUpdatedListener(SeekBar seekBar,
final int position) {
seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
public void onStopTrackingTouch(SeekBar seekBar) {
int progress = seekBar.getProgress();
int task_id = (Integer) seekBar.getTag();
TaskHandler taskHandler = new TaskHandler(DBAdapter
.getDBAdapterInstance(getContext()));
taskHandler.updateTaskProgress(task_id, progress);
mList.get(position).setProgress(progress);
//need to fire an update to the activity
notifyDataSetChanged();
seekBar.setEnabled(false);
}
public void onStartTrackingTouch(SeekBar seekBar) {
// empty as onStartTrackingTouch listener not being used in
// current implementation
}
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// empty as onProgressChanged listener not being used in
// current implementation
}
});
}
OnItemCLickListener
this snipped is from the activity which houses the list view.
taskListView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
SeekBar sb = (SeekBar) view.findViewById(R.id.seek);
sb.setFocusable(true);
sb.setEnabled(true);
}
});
I got it working by subclassing the seekbar and checking if the angle of motion is greater than 45 degrees. In that case ignore touches and return false so scroll view will do its thing.
Edit:
Here is an extended SeekBar which allows you to set a min/max in floats rather than integers.
public class SeekBarEx extends SeekBar implements
SeekBar.OnSeekBarChangeListener {
final int SEEK_POINTS = 0x10000;
final String TAG = "SeekBarEx";
public float mMax;
public float mMin;
public OnSeekBarExChangeListener delegate = null;
public interface OnSeekBarExChangeListener {
public void onSeekChanged(SeekBarEx seekBarEx, float value,
boolean fromUser);
public void onStartTrackingTouch(SeekBarEx seekBar);
public void onStopTrackingTouch(SeekBarEx seekBar);
}
public SeekBarEx(Context ctx, AttributeSet attr) {
super(ctx, attr);
super.setMax(SEEK_POINTS);
mMin = 0f;
mMax = 1.0f;
initAttributes(attr);
this.setOnSeekBarChangeListener(this);
}
public void setDelegate(OnSeekBarExChangeListener d) {
delegate = d;
}
public void initAttributes(AttributeSet attrSet) {
TypedArray a;
a = getContext().obtainStyledAttributes(attrSet, R.styleable.SeekBarEx);
final int N = a.getIndexCount();
int i;
for (i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.SeekBarEx_max:
mMax = a.getFloat(i, 1.0f);
Log.d(TAG, "maxSet " + mMax);
break;
case R.styleable.SeekBarEx_min:
mMin = a.getFloat(i, 0f);
Log.d(TAG, "minSet" + mMin);
break;
case R.styleable.SeekBarEx_value:
this.setValue(a.getFloat(i, 0));
break;
}
}
a.recycle();
}
@Override
public int getProgress() {
return super.getProgress();
}
public float getValue() {
float r;
float run;
r = (float) super.getProgress();
r = r / (float) SEEK_POINTS;
run = mMax - mMin;
r = r * run + mMin;
return r;
}
public void setValue(float v) {
if (Float.isNaN(v) || Float.isInfinite(v))
return;
if (v > mMax)
v = mMax;
if (v < mMin)
v = mMin;
float run;
int setv;
run = mMax - mMin;
v -= mMin;
setv = Math.round(v * (float) SEEK_POINTS / run);
super.setProgress(setv);
}
public boolean valueChanged = false;
public void cancelTracking() {
if (oldValue != Float.NaN) {
this.setValue(oldValue);
oldValue = Float.NaN;
valueChanged = false;
acceptTouches = false;
acceptChange = false;
}
}
// we override these methods so that when we forcully cancel
// on ontouches moved. We can revert back to the old value
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
Log.d(TAG, "SeekBar changed to " + progress);
if (delegate != null && acceptTouches) {
valueChanged = true;
delegate.onSeekChanged(this, this.getValue(), fromUser);
} else
cancelTracking();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
if (delegate != null)
delegate.onStartTrackingTouch(this);
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (delegate != null && valueChanged)
delegate.onStopTrackingTouch(this);
else
cancelTracking();
acceptChange = false;
valueChanged = false;
}
public float mY, mX;
public boolean acceptTouches = true;
// acceptChange never read todo: delete
public boolean acceptChange = false;
public float oldValue = Float.NaN;
public ScrollView getScrollView() {
View view;
view = this;
int maxUp;
maxUp = 5;
while (view != null && maxUp > 0) {
view = (View) view.getParent();
ScrollView scroller;
if (view instanceof ScrollView) {
scroller = (ScrollView) view;
return scroller;
}
maxUp--;
}
return null;
}
// **************************************
// This is the important part in achieving the effect in scroll
// view to be nice
@Override
public boolean onTouchEvent(MotionEvent event) {
int action;
action = event.getAction() & MotionEvent.ACTION_MASK;
ScrollView scroller = this.getScrollView();
boolean mayScroll;
mayScroll = true;
if (scroller == null)
mayScroll = false;
else {
int scrollAmount = scroller.getMaxScrollAmount();
if (scrollAmount == 0)
mayScroll = false;
}
switch (action) {
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, "got cancel touches");
cancelTracking();
super.onTouchEvent(event);
return true;
case MotionEvent.ACTION_DOWN:
mX = event.getX();
mY = event.getY();
acceptTouches = true;
acceptChange = false;
oldValue = this.getValue();
valueChanged = false;
break;
case MotionEvent.ACTION_MOVE:
float x;
float y;
x = event.getX();
y = event.getY();
float dx;
float dy;
dx = x - mX;
dy = y - mY;
if (dx < 0)
dx = -dx;
if (dy < 0)
dy = -dy;
y = this.getHeight() / 2 - y;
float angle;
float distance;
distance = dx * dx + dy * dy;
// I just realized this is wrong it should be
// angle = (float)Math.acos(Math.abs(dx)/Math.sqrt(distance))
// I'm leaving it until tested or someone can confirm
angle = (float) Math.atan(dy / dx);
int distanceLimit;
distanceLimit = this.getHeight() / 3;
distanceLimit *= distanceLimit;
// if we move at an angle of atleast 45degrees
// cancel
if (mayScroll && angle > Math.PI / 4.0) {
cancelTracking();
}
mX += 100000;
if (y < 0)
y = -y;
// if we moved finger too far just cancel
// cause the person may have wanted to scroll but
// failed so we revert back to the old value
if (y > this.getHeight() * 2) {
cancelTracking();
} else if (acceptTouches)
acceptChange = true;
break;
default:
break;
}
// if we accept touches do the usual otherwise
// return false so scrollView can do it's thing
if (acceptTouches)
return super.onTouchEvent(event);
return false;
}
}
seekbarex.xml is bellow. This is just to add min/max/value as floats.
<declare-styleable name="SeekBarEx">
<attr name="min" format="float"/>
<attr name="max" format="float"/>
<attr name="value" format="float"/>
</declare-styleable>
</resources>
精彩评论