Подтвердить что ты не робот

Горизонтальный RecyclerView внутри ListView не прокручивается очень плавно

У меня есть ListView, каждый из элементов которого является горизонтальным RecyclerView. Проблема, с которой я столкнулась, - горизонтальный свиток не очень гладкий. Если я прокручиваю/прокручиваю в совершенно горизонтальном направлении, то он отлично работает, но если свиток даже слегка не горизонтальный (скажем, под углом 10 градусов к горизонтали), он считает, что в качестве прокрутки вверх/вниз, а не влево/вправо, из-за родителя ListView. Я пробовал этот метод:

   recyclerView.setOnTouchListener(new FlingRecyclerView.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    int action = event.getAction();
                    switch (action) {
                        case MotionEvent.ACTION_DOWN:
                            // Disallow ScrollView to intercept touch events.
                            v.getParent().requestDisallowInterceptTouchEvent(true);
                            Log.i("Scroll down", "Intercept true");
                            break;

                        case MotionEvent.ACTION_UP:
                            // Allow ScrollView to intercept touch events.
                            v.getParent().requestDisallowInterceptTouchEvent(false);
                            break;

                        case MotionEvent.ACTION_MOVE:
                            // Allow ScrollView to intercept touch events.
                            v.getParent().requestDisallowInterceptTouchEvent(true);
                            break;
                    }

                    // Handle HorizontalScrollView touch events.
                    v.onTouchEvent(event);
                    return true;
                }
            });

Но это было не очень эффективно. На самом деле я не мог заметить большого улучшения. Это потому, что этот motionEvent вызывается только тогда, когда движение совершенно горизонтально - этот случай уже работает нормально. Затем я попытался сделать это:

class MyGestureDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            try {
                Log.i("TAG", "VelocityX " + Float.toString(velocityX) + "VelocityY " + Float.toString(velocityY));
                Log.i("TAG", "e1X " + Float.toString(e1.getX()) + "e1Y " + Float.toString(e1.getY()) + "e2X " + Float.toString(e2.getX()) + "e2Y " + Float.toString(e2.getY()));
                // downward swipe
                if (e2.getY() - e1.getY() > SWIPE_MAX_OFF_PATH && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
                    Toast.makeText(TestActivity.this, "Downward Swipe", Toast.LENGTH_SHORT).show();
                    Log.i("TAG", "Downward swipe");
                }
                else if (e1.getY() - e2.getY() > SWIPE_MAX_OFF_PATH && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
                    Toast.makeText(TestActivity.this, "Upward Swipe", Toast.LENGTH_SHORT).show();
                    Log.i("TAG", "Upward swipe");
                }
                    // right to left swipe
                else if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    Toast.makeText(TestActivity.this, "Left Swipe", Toast.LENGTH_SHORT).show();
                    Log.i("TAG", "Left swipe");
                }  else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    Toast.makeText(TestActivity.this, "Right Swipe", Toast.LENGTH_SHORT).show();
                    Log.i("TAG", "Right swipe");
                }
            } catch (Exception e) {
                // nothing
            }
            return false;
        }

    }

И я заметил, что он регистрирует события только тогда, когда я отпускаю палец, а не когда я начинаю салфетки. Он не работает, как только я касаюсь RecyclerView. И это тоже не привело к заметному улучшению. Я также заметил, что эти методы так или иначе зависят от давления. Когда я сделал легкий удар, ничего не случилось. То же самое делается на том же пути, при большем давлении производит свиток.

Есть ли способ сделать прокрутку гладкой, даже если путь салфетки не является горизонтально прямой или даже если движение является типом циркуляра? Любая помощь будет высоко оценен.

4b9b3361

Ответ 1

Джим Бака дал мне правильный путь, моя проблема заключалась в том, что родительский вид (вертикальная прокрутка) украл свиток из дочерних представлений (горизонтальный прокрутка). Это сработало для меня:

/**
 * This class is a workaround for when a parent RecyclerView with vertical scroll
 * is a bit too sensitive and steals onTouchEvents from horizontally scrolling child views
 */
public class NestedScrollingParentRecyclerView extends RecyclerView {
    private boolean mChildIsScrolling = false;
    private int mTouchSlop;
    private float mOriginalX;
    private float mOriginalY;

    public NestedScrollingParentRecyclerView(Context context) {
        super(context);
        init(context);
    }

    public NestedScrollingParentRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public NestedScrollingParentRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {
        ViewConfiguration vc = ViewConfiguration.get(context);
        mTouchSlop = vc.getScaledTouchSlop();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = MotionEventCompat.getActionMasked(ev);

        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            // Release the scroll
            mChildIsScrolling = false;
            return false; // Let child handle touch event
        }

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mChildIsScrolling = false;
                setOriginalMotionEvent(ev);
            }
            case MotionEvent.ACTION_MOVE: {
                if (mChildIsScrolling) {
                    // Child is scrolling so let child handle touch event
                    return false;
                }

                // If the user has dragged her finger horizontally more than
                // the touch slop, then child view is scrolling

                final int xDiff = calculateDistanceX(ev);
                final int yDiff = calculateDistanceY(ev);

                // Touch slop should be calculated using ViewConfiguration
                // constants.
                if (xDiff > mTouchSlop && xDiff > yDiff) {
                    mChildIsScrolling = true;
                    return false;
                }
            }
        }

        // In general, we don't want to intercept touch events. They should be
        // handled by the child view.  Be safe and leave it up to the original definition
        return super.onInterceptTouchEvent(ev);
    }

    public void setOriginalMotionEvent(MotionEvent ev) {
        mOriginalX = ev.getX();
        mOriginalY = ev.getY();
    }

    public int calculateDistanceX(MotionEvent ev) {
        return (int) Math.abs(mOriginalX - ev.getX());
    }

    public int calculateDistanceY(MotionEvent ev) {
        return (int) Math.abs(mOriginalY - ev.getY());
    }
}

Ответ 2

Проблема в том, что события касания не уверены, куда идти. Вероятно, вы захотите использовать onInterceptTouchEvent вместе с TouchSlop, чтобы определить, следует ли вам красть события касания от дочерних строк (просмотры recycler).

Функция onInterceptTouchEvent важна для того, чтобы ListView мог решить, должен ли он украсть события касания от детей.

Прикосновение является важным, поскольку оно определяет, сколько требуется движения, прежде чем мы должны предпринять действия (в этом случае, чтобы украсть события у ребенка). Это связано с тем, что при касании экрана пальцем (без видимого/предполагаемого движения) будут генерироваться события MotionEvent.ACTION_MOVE (вы можете убедиться в этом сами, добавив запись в MOTION_MOVE для распечатки событий getX и getY.

Если вы все еще сталкиваетесь с проблемами, убедитесь, что дети просмотра recycler также не ожидают событий движения. Если этим детям, возможно, потребуется использовать requestDisallowInterceptTouchEvent, чтобы предотвратить перехват их перехвата и списка просмотров.

Этот код был основан на коде, найденном на андроидных документах, вы можете увидеть оригинал и подробнее узнать о событиях касания здесь.

     MotionEvent mOriginal;
     mIsScrolling = false;
     // put these two lines in your constructor
     ViewConfiguration vc = ViewConfiguration.get(view.getContext());
     mTouchSlop = vc.getScaledTouchSlop();


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    /*
     * This method JUST determines whether we want to intercept the motion.
     * If we return true, onTouchEvent will be called and we do the actual
     * scrolling there.
     */


    final int action = MotionEventCompat.getActionMasked(ev);

    // Always handle the case of the touch gesture being complete.
    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
        // Release the scroll.

        mIsScrolling = false;
        return false; // Do not intercept touch event, let the child handle it
    }

    switch (action) {
        case MotionEvent.ACTION_DOWN:  {
            mIsScrolling = false;
            setOriginalMotionEvent(ev);
        }
        case MotionEvent.ACTION_MOVE: {
            if (mIsScrolling) {
                // We're currently scrolling, so yes, intercept the 
                // touch event!
                return true;
            }

            // If the user has dragged her finger horizontally more than 
            // the touch slop, start the scroll

            final int xDiff = calculateDistanceX(ev);
            final int yDiff = calculateDistanceY(ev);

            // Touch slop should be calculated using ViewConfiguration 
            // constants.
            if (yDiff > mTouchSlop &&  yDiff > xDiff) { 
                // Start scrolling!
                mIsScrolling = true;
                return true;
            }
            break;
        }
        ...
    }

    // In general, we don't want to intercept touch events. They should be 
    // handled by the child view.  Be safe and leave it up to the original definition
    return super.onInterceptTouchEvent(ev);
}

public void setOriginalMotionEvent(MotionEvent ev){
     mOriginal = ev.clone();
}

public int calculateDistanceX(ev){
    int distance = Math.abs(mOriginal.getX() - ev.getX());
    return distance;
}

public int calculateDistanceY(ev){
    int distance = Math.abs(mOriginal.getY() - ev.getY());
    return distance;
}

Ответ 3

IIRC вы можете переопределить метод onInterceptTouchEvent в ListView

private Point movementStart;

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean superResult = super.onInterceptTouchEvent(ev);
    if(movementStart == null) {
        movementStart = new Point((int)ev.getX(), (int)ev.getY());
    }
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            return false;
        case MotionEvent.ACTION_MOVE:
            //if movement is horizontal than don't intercept it
            if(Math.abs(movementStart.x - ev.getX()) > Math.abs(movementStart.y - ev.getY())){
                return false;
            }
                break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            movementStart = null;
            break;
    }
    return superResult;
}

Вы можете расширить код выше следующим образом:

private Point movementStart;
private boolean disallowIntercept = false;

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean superResult = super.onInterceptTouchEvent(ev);
    if(movementStart == null) {
        movementStart = new Point((int)ev.getX(), (int)ev.getY());
    }
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            return false;
        case MotionEvent.ACTION_MOVE:
            //if movement is horizontal than don't intercept it
            if(Math.abs(movementStart.x - ev.getX()) > Math.abs(movementStart.y - ev.getY())){
                disallowIntercept = true;
            }
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            movementStart = null;
            disallowIntercept = false;
            break;
    }

    if(disallowIntercept) {
        return false;
    } else {
        return superResult;
    }
}

Ответ 4

Пробовал ли вы verticalRow.setNestedScrollingEnabled(false);//Здесь verticalRow - экземпляр recylerView