Как исправить проблему с прокруткой горизонтальных ViewPager2 и RecyclerView, которые находятся внутри вертикального RecyclerView? - PullRequest
1 голос
/ 20 февраля 2020

У меня есть RecyclerView (скажем, rootRecyclerView), который может иметь различные типы строк в зависимости от ответа API. Я реализовал один из них - горизонтальный ViewPager2, а другой - горизонтальный RecyclerView (скажем, `childRecyclerView).

rootRecyclerView смахивает по вертикали, тогда как viewPager2 и childRecyclerView смахивает по горизонтали.

Проблема:

Когда я проводя по экрану, если на viewPager2 или childRecyclerView проведите пальцем по прямой линии go по горизонтали. В противном случае они не будут прокручиваться горизонтально; проведите пальцем по rootRecyclerView, и вы увидите вертикальное движение.

Итак, это происходит потому, что ваш большой палец будет двигаться в изогнутом / круговом направлении, создавая движение как по оси X, так и по оси Y, и поэтому rootRecyclerView перехватывает свайп, создавая этот неприятный пользовательский опыт.

Я попытался решить проблему, например, добавив OnItemTouchListener к childRecyclerView следующим образом:

    private float Y_BUFFER = 10;
    private float preX = 0f;
    private float preY = 0f;
childRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
                if(e.getAction()==MotionEvent.ACTION_DOWN){
                    childRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
                }
                if(e.getAction() == MotionEvent.ACTION_MOVE){
                    if (Math.abs(e.getX() - preX) > Math.abs(e.getY() - preY)) {
                        childRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
                    } else if (Math.abs(e.getY() - preY) > Y_BUFFER) {
                        childRecyclerView.getParent().requestDisallowInterceptTouchEvent(false);
                    }
                }
                preX = e.getX();
                preY = e.getY();
                return false;
            }
// ... rest of the code

Это решает проблему только для childRecyclerView, но я не могу решить ее для ViewPager2.

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

Кто-нибудь может мне помочь?

1 Ответ

0 голосов
/ 20 февраля 2020

Хорошо, после некоторого исследования я пришел к выводу о замене моего ViewPager2 на recyclerView, который будет «вести себя как» viewPager: /.

Сначала я заменил свой viewPager2 с горизонтальным recyclerView. Чтобы заставить его вести себя как видовой пейджер, используйте SnapHelper.

RecyclerView childRecyclerView2 = findVIewById(R.id.previously_viewPager);
// other init like setup layout manager, adapter etc 
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(replacedRecyclerView); // <-- this makes out rv behave like a viewPager

. После этого вам нужно добавить OnItemTouchListener и переопределить onInterceptTouchEvent так же, как сегмент кода в моем вопросе:

childRecyclerView2.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
// same as the code segment in the question, 
//so skipping this part. 
//just copy it from my question
          }
// ...
}

Необязательно:

В viewPager2 вы можете получить текущий фокус с помощью getCurrentItem(), но так как мы заменили viewpager2 на recyclerview, у нас нет этого метода. Итак, нам нужно реализовать собственную эквивалентную версию. Если вы парень Kotlin, вы можете сразу перейти к ссылке 2 и пропустить эту часть. Вот версия java, если вам нужно, но я пропущу объяснение. Создайте SnapHelperExt.java

public class SnapHelperExt {

    public SnapHelperExt(){}

    public int getSnapPosition(RecyclerView recyclerView, SnapHelper snapHelper){
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        View snapView =  snapHelper.findSnapView(layoutManager);
        if (snapView != null) {
            return layoutManager.getPosition(snapView);
        }else{
            return -1;
        }
    }
}

Далее создайте интерфейс OnSnapPositionChangeListener как наш слушатель:

public interface OnSnapPositionChangeListener {
    void onSnapPositionChange(int position);
}

После этого создайте SnapOnScrollListener.java:

public class SnapOnScrollListener extends RecyclerView.OnScrollListener {

    public enum Behavior {
        NOTIFY_ON_SCROLL,
        NOTIFY_ON_SCROLL_STATE_IDLE
    }

    private SnapHelperExt snapHelperExt;
    private SnapHelper snapHelper;
    private Behavior behavior;
    private OnSnapPositionChangeListener onSnapPositionChangeListener;
    private int snapPosition = RecyclerView.NO_POSITION;


    public SnapOnScrollListener(SnapHelper snapHelper, Behavior behavior, OnSnapPositionChangeListener onSnapPositionChangeListener){
        this.snapHelper = snapHelper;
        this.behavior = behavior;
        this.onSnapPositionChangeListener = onSnapPositionChangeListener;
        this.snapHelperExt = new SnapHelperExt();
    }

    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if (behavior == Behavior.NOTIFY_ON_SCROLL) {
            maybeNotifySnapPositionChange(recyclerView);
        }
    }

    @Override
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        if (behavior == Behavior.NOTIFY_ON_SCROLL_STATE_IDLE
                && newState == RecyclerView.SCROLL_STATE_IDLE) {
            maybeNotifySnapPositionChange(recyclerView);
        }
    }

    private void maybeNotifySnapPositionChange(RecyclerView recyclerView){
        int prevPosition = this.snapHelperExt.getSnapPosition(recyclerView, snapHelper);
        boolean snapPositionIsChanged = (this.snapPosition!=prevPosition);

        if(snapPositionIsChanged){
            onSnapPositionChangeListener.onSnapPositionChange(prevPosition);
            this.snapPosition = prevPosition;
        }
    }
}

Наконец, используйте его следующим образом:

            SnapOnScrollListener snapOnScrollListener = new SnapOnScrollListener(
                    snapHelper,
                    SnapOnScrollListener.Behavior.NOTIFY_ON_SCROLL,
                    position -> {   
Log.e(TAG, "currently focused page no = "+position);    
// your code here, do whatever you want
}
            );

            childRecyclerView2.addOnScrollListener(snapOnScrollListener);

Ссылки:

  1. create-viewpager-using-recyclerview
  2. Обнаружение мгновенных изменений с помощью андроидов-переработчиков
...