onProgressChanged запускается, даже если нет изменений прогресса

У меня есть приложение для Android с двумя панелями поиска, и я настроил их так, чтобы приложение не реагировало, пока они оба не будут нажаты. Моя проблема в том, что когда они оба нажаты, метод onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) начинает непрерывно работать в бесконечном цикле. Это происходит, несмотря на то, что я держу пальцы совершенно неподвижно на полосах (это означает, что прогресс не меняется ни на одной из полос, и поэтому метод onProgressChanged вызывать не следует). Кто-нибудь знает, почему это может происходить?

Вот соответствующий код:

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");
        }
    });
}

Также обратите внимание, что я использовал пользовательский класс Bool вместо примитивного boolean, чтобы обойти ограничение, согласно которому все переменные за пределами OnSeekBarChangeListener должны быть помечены final. (Мне нужно отправить информацию между onSeekBarChangeListeners, чтобы выяснить, держит ли пользователь пальцы одновременно на обеих панелях поиска) У меня есть подозрение, что это может быть причиной моих проблем, но я не вижу как.

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; }
}

РЕДАКТИРОВАТЬ: я также отмечу, что я использую пользовательскую вертикальную панель поиска. Вот код:

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;
    }
}

person TheIronKnuckle    schedule 04.03.2016    source источник
comment
sb.setProgress (9); вызовет его onChangeListener   -  person Sanket Kachhela    schedule 04.03.2016
comment
@SanketKachhela да, но этот вызов происходит только один раз в начале приложения, а не 1000 раз в бесконечном цикле. Кроме того, это происходит до того, как я установил пользовательский OnSeekBarChangeListener   -  person TheIronKnuckle    schedule 04.03.2016


Ответы (1)


Я думаю, что проблема в onTouchEvent реализации вашего VerticalSeekBar, потому что вы обрабатываете каждый полученный MotionEvent.ACTION_MOVE.

Из документации:

Новый onTouchEvent() запускается с событием ACTION_MOVE всякий раз, когда изменяется текущая позиция сенсорного контакта, давление или размер. Как описано в разделе «Обнаружение распространенных жестов», все эти события записываются в параметре MotionEvent функции onTouchEvent().

Поскольку прикосновение пальцами не всегда является самой точной формой взаимодействия, обнаружение событий прикосновения часто основывается больше на движении, чем на простом контакте. Чтобы помочь приложениям различать жесты, основанные на движении (например, смахивание), и жесты, не связанные с движением (например, одно касание), Android включает понятие «прикосновение». Шаг касания относится к расстоянию в пикселях, на которое может отклониться касание пользователя, прежде чем жест будет интерпретирован как жест, основанный на движении. Дополнительные сведения об этой теме см. в разделе Управление сенсорными событиями в ViewGroup.

То есть вы думаете, что ваши пальцы полностью неподвижны, но на панели поиска поступает ACTION_MOVE событий.

В вашем случае аппроксимация «сенсорного спада» теперь является хорошей идеей, потому что рассчитанный сенсорный слоп огромен для ваших целей, как отсутствие касания определяется как:

«Скольжение касания» относится к расстоянию в пикселях, на которое может отклониться касание пользователя, прежде чем жест будет интерпретирован как прокрутка. Touch Slop обычно используется для предотвращения случайной прокрутки, когда пользователь выполняет какую-либо другую сенсорную операцию, например касание элементов на экране.

Чтобы решить вашу проблему, вы можете рассчитать расстояние между последней управляемой позицией и текущей, чтобы вызвать 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);
}
person antonio    schedule 04.03.2016