简体   繁体   中英

onProgressChanged being run even when there is no progress change

I have an android app with two seekbars, and I've set them up so that the app doesn't respond unless both of them are pressed. My problem is that when they are both pressed down, the method onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) starts continuously running in an infinite loop. This happens even though I am holding my fingers completely still on the bars (which means that the progress is not changing on either of the bars, and therefore the onProgressChanged method shouldn't be being called). Does anyone have any insights as to why this could be happening?

Here is some relevant code:

void setupLRSpeedBars(final int UIID, final Bool bar1, final Bool bar2) {
    SeekBar sb = (SeekBar) findViewById(UIID);
    sb.setProgress(9);
    sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            if (bar1.isTrue()) {
                progress -= 9;
                transmit((UIID == R.id.leftSpeedBar ? "L" : "R") +
                         (progress >= 0 ? "+" : "") +
                          Integer.toString(progress));
            }
        }

        public void onStartTrackingTouch(SeekBar seekBar) {
            bar2.set(true);
        }

        public void onStopTrackingTouch(SeekBar seekBar) {
            bar2.set(false);
            seekBar.setProgress(9);
            //transmit((UIID == R.id.leftSpeedBar ? "L" : "R") + "+0");
            transmit("s");
        }
    });
}

Also note that I've used a custom Bool class instead of a primitive boolean , in order for me to get around the restriction that all variables outside of the OnSeekBarChangeListener be marked final . (I need to send information between the onSeekBarChangeListeners in order to work out whether the user is holding their fingers down on both seekbars at the same time) I have a hunch that this might be the cause of my problems, but I don't see how.

class Bool {
    private boolean bool;
    public Bool () {}
    public Bool(boolean bool) { this.bool = bool; }
    public void set (boolean bool) { this.bool = bool; }
    public boolean get () { return bool; }
    public boolean isTrue () { return bool; }
}

EDIT: I'll also note that I'm using a custom, vertical seekbar. Here is the code:

package android.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;

public class VerticalSeekBar extends SeekBar {

    private OnSeekBarChangeListener myListener;
    public VerticalSeekBar(Context context) {
        super(context);
    }

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

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

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(h, w, oldh, oldw);
    }

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(heightMeasureSpec, widthMeasureSpec);
        setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
    }

    @Override
    public void setOnSeekBarChangeListener(OnSeekBarChangeListener mListener){
        this.myListener = mListener;
    }

    @Override
    public synchronized void setProgress(int progress){
        super.setProgress(progress);
        onSizeChanged(getWidth(), getHeight(), 0, 0);
    }

    protected void onDraw(Canvas c) {
        c.rotate(-90);
        c.translate(-getHeight(), 0);

        super.onDraw(c);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled()) {
            return false;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if(myListener!=null)
                    myListener.onStartTrackingTouch(this);
                break;
            case MotionEvent.ACTION_MOVE:
                setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
                onSizeChanged(getWidth(), getHeight(), 0, 0);
                myListener.onProgressChanged(this, getMax() - (int) (getMax() * event.getY() / getHeight()), true);
                break;
            case MotionEvent.ACTION_UP:
                myListener.onStopTrackingTouch(this);
                break;

            case MotionEvent.ACTION_CANCEL:
                break;
        }
        return true;
    }
}

I think that the problem is in the onTouchEvent implementation of your VerticalSeekBar because you are processing every MotionEvent.ACTION_MOVE received.

From the documentation :

A new onTouchEvent() is triggered with an ACTION_MOVE event whenever the current touch contact position, pressure, or size changes. As described in Detecting Common Gestures, all of these events are recorded in the MotionEvent parameter of onTouchEvent().

Because finger-based touch isn't always the most precise form of interaction, detecting touch events is often based more on movement than on simple contact. To help apps distinguish between movement-based gestures (such as a swipe) and non-movement gestures (such as a single tap), Android includes the notion of "touch slop." Touch slop refers to the distance in pixels a user's touch can wander before the gesture is interpreted as a movement-based gesture. For more discussion of this topic, see Managing Touch Events in a ViewGroup.

That is, you think that your fingers are completely still but your seek bars are receiving ACTION_MOVE events.

In your case, the "touch slop" approximation is now a good idea because the calculated touch slop is huge for your purposes, as touch slop is defined as :

"Touch slop" refers to the distance in pixels a user's touch can wander before the gesture is interpreted as scrolling. Touch slop is typically used to prevent accidental scrolling when the user is performing some other touch operation, such as touching on-screen elements.

To solve your problem you can calculate the distance between the last managed position and the current one to trigger your onProgressChanged :

private static final float MOVE_PRECISION = 5; // You may want to tune this parameter
private float lastY;

// ...

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (!isEnabled()) {
        return false;
    }

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastY = event.getY();

            if (myListener != null)
                myListener.onStartTrackingTouch(this);
            break;
        case MotionEvent.ACTION_MOVE:
            if (calculateDistanceY(event) > MOVE_PRECISION) {
                setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
                onSizeChanged(getWidth(), getHeight(), 0, 0);
                myListener.onProgressChanged(this, getMax() - (int) (getMax() * event.getY() / getHeight()), true);

                lastY = event.getY();
            }
            break;
        case MotionEvent.ACTION_UP:
            myListener.onStopTrackingTouch(this);
            break;

        case MotionEvent.ACTION_CANCEL:
            break;
    }
    return true;
}

private float calculateDistanceY (MotionEvent event) {
    return Math.abs(event.getY() - lastY);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM