Вложенное RecyclerView. Как предотвратить прокрутку родительского RecyclerView во время прокрутки дочернего элемента RecyclerView?

Я пытаюсь реализовать горизонтальный recyclerview, и каждый элемент recyclerview будет вертикальным recyclerview с макетом сетки. Проблема, с которой я сталкиваюсь, заключается в том, что при попытке прокрутки дочернего элемента recyclerview по вертикали иногда родительский recyclerview выполняет прокрутку и начинает прокручивать по горизонтали. Подходы, которые я пытался исправить, следующие:

  • setNestedScrollingEnabled(false) в родительском recyclerview
  • В onTouch() дочернего recyclerview я отключу события касания родительского recyclerview, вызванного requestdisallowinterceptTouchevent(false)

Ни одно из вышеперечисленных решений не является идеальным решением проблемы. Любая помощь приветствуется

Ответы

Ответ 1

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

введите описание изображения здесь

Итак, вы можете попробовать что-то вроде этого:

Определите CustomLinearLayoutManager расширение LinearLayoutManager следующим образом:

public class CustomLinearLayoutManager extends LinearLayoutManager {

    public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    @Override
    public boolean canScrollVertically() {
        return false;
    }
}

и установите для этого CustomLinearLayoutManager родительский RecyclerView.

RecyclerView parentRecyclerView = (RecyclerView)findViewById(R.id.parent_rv);
CustomLinearLayoutManager customLayoutManager = new CustomLinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false);
parentRecyclerView.setLayoutManager(customLayoutManager);
parentRecyclerView.setAdapter(new ParentAdapter(this)); // some adapter

Теперь для дочернего RecyclerView определите пользовательский CustomGridLayoutManager расширение GridLayoutManager:

public class CustomGridLayoutManager extends GridLayoutManager {

    public CustomGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public CustomGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public CustomGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }

    @Override
    public boolean canScrollHorizontally() {
        return false;
    }
}

и установите его как layoutManger дочернему элементу RecyclerView:

childRecyclerView = (RecyclerView)itemView.findViewById(R.id.child_rv);
childRecyclerView.setLayoutManager(new CustomGridLayoutManager(context, 3));
childRecyclerView.setAdapter(new ChildAdapter()); // some adapter

Таким образом, основной родительский RecyclerView выполняет только горизонтальные свитки, а child RecyclerView выполняет только вертикальные свитки.

Кроме того, если вы также хотите обработать диагональный саблей (который немного искажен вертикально или горизонтально), вы можете включить прослушиватель жестов в родительский RecylerView.

public class ParentRecyclerView extends RecyclerView {

    private GestureDetector mGestureDetector;

    public ParentRecyclerView(Context context) {
        super(context);
        mGestureDetector = new GestureDetector(this.getContext(), new XScrollDetector());
       // do the same in other constructors
    }

   // and override onInterceptTouchEvent

   @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
    }

}

Где XScrollDetector есть

class XScrollDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            return Math.abs(distanceY) < Math.abs(distanceX);
        }
}

Таким образом, ParentRecyclerView запрашивает дочернее представление (в нашем случае VerticalRecyclerView) для обработки события прокрутки. Если дочерний вид обрабатывает, то родитель не делает ничего другого, в конце концов, обрабатывает прокрутку.

Ответ 2

Я исправил эту проблему в аналогичном проекте, обратив противоположный подход к вам (и всем остальным здесь).

Вместо того, чтобы позволить ребенку указывать родительскому лицу, чтобы прекратить просмотр событий, я разрешаю родителям решать, когда игнорировать (в зависимости от направления). Такой подход требует индивидуального представления, хотя это может быть немного больше. Ниже приводится то, что я создал, который будет использоваться в качестве представления Outer/Parent.

public class DirectionalRecyclerView extends RecyclerView {

    private static float LOCK_DIRECTION_THRESHOLD; //The slop
    private float startX;
    private float startY;
    private LockDirection mLockDirection = null;

    public DirectionalRecyclerView(Context context) {
        super(context);
        findThreshold(context);
    }

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

    public DirectionalRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        findThreshold(context);
    }

    private void findThreshold(Context context) {
        //last number is number of dp to move before deciding that a direction not a tap, you might want to tweak it
        LOCK_DIRECTION_THRESHOLD = context.getResources().getDisplayMetrics().density * 12;
    }

    //events start at the top of the tree and then pass down to
    //each child view until they reach where they were initiated
    //unless the parent (this) method returns true for this visitor
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = event.getX();
                startY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (mLockDirection == null) {
                    float currentX = event.getX();
                    float currentY = event.getY();
                    float diffX = Math.abs(currentX - startX);
                    float diffY = Math.abs(currentY - startY);
                    if (diffX > LOCK_DIRECTION_THRESHOLD) {
                        mLockDirection = LockDirection.HORIZONTAL;
                    } else if (diffY > LOCK_DIRECTION_THRESHOLD) {
                        mLockDirection = LockDirection.VERTICAL;
                    }
                } else {
                    //we have locked a direction, check whether we intercept
                    //the future touches in this event chain
                    //(returning true steals children events, otherwise we'll
                    // just let the event trickle down to the child as usual)
                    return mLockDirection == LockDirection.HORIZONTAL;
                }
                break;
            case MotionEvent.ACTION_UP:
                mLockDirection = null;
                break;
        }
        //dispatch cancel, clicks etc. normally
        return super.onInterceptTouchEvent(event);
    }

    private enum LockDirection {
        HORIZONTAL,
        VERTICAL
    }

}

Ответ 3

setNestedScrollingEnabled (false) в родительском recyclerview

Что вы можете попробовать: setNestedScrollingEnabled(false) для дочернего RecyclerView, если таковой имеется. RecyclerView nestedscroll-ness - это тип дочернего элемента (для чего он реализует NestedScrollingChild).

В onTouch() дочернего recyclerview я отключу события касания на родительский recyclerview, вызванный requestdisallowinterceptTouchevent (ложь)

Это должно работать, но вам нужно сделать requestDisallowInterceptTouchEvent(true), а не false. Если вы подклассифицируете RecyclerView, вы можете переопределить onTouchEvent:

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP) {
        // ensure we release the disallow request when the finger is lifted
        getParent().requestDisallowInterceptTouchEvent(false);
    } else {
        getParent().requestDisallowInterceptTouchEvent(true);
    }
    // Call the super class to ensure touch handling
    return super.onTouchEvent(event);
}

Или, при прослушивании прикосновений снаружи,

child.setOnTouchListener(new View.OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (v.getId() == child.getId()) {
            if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP) {
                // ensure we release the disallow request when the finger is lifted
                child.getParent().requestDisallowInterceptTouchEvent(false);
            } else {
                child.getParent().requestDisallowInterceptTouchEvent(true);
            }
        }
        // Call the super class to ensure touch handling
        return super.onTouchEvent(event);
    }
});

Ответ 4

Установите прослушиватель вложенный RecyclerView

 View.OnTouchListener listener = new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_MOVE
                            ) {
                        v.getParent().requestDisallowInterceptTouchEvent(true);

                    } else {
                        v.getParent().requestDisallowInterceptTouchEvent(false);

                    }
                    return false;
                }
            };

            mRecyclerView.setOnTouchListener(listener);

Ответ 5

Попробуйте это. Для моего случая использования он работал:

nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return true;
    }
});

Ответ 6

Теперь вы можете попробовать android:nestedScrollingEnabled, потому что Google зафиксировал сбой при использовании nestedScrollingEnabled (выпуск 197932)

Ответ 7

попробуйте приведенный ниже код, надейтесь, что он сработает.

nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    int action = event.getAction();
                   switch (action) {
                  case MotionEvent.ACTION_DOWN:
                     // Disallow parent to intercept touch events.
                     v.getParent().requestDisallowInterceptTouchEvent(true);
                     break;

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

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

Ответ 8

Попробуйте ввести код для прокрутки внутреннего RecyclerView.

innerRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {

        @Override
        public void onTouchEvent(RecyclerView recycler, MotionEvent event) {
            // Handle on touch events here
            int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    // Disallow Parent RecyclerView to intercept touch events.
                    recycler.getParent().requestDisallowInterceptTouchEvent(true);
                    break;

                case MotionEvent.ACTION_UP:
                    // Allow Parent RecyclerView to intercept touch events.
                    recycler.getParent().requestDisallowInterceptTouchEvent(false);
                    break;
            }


        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView recycler, MotionEvent event) {
            return false;
        }

    });

Ответ 9

IMO, вы можете попробовать следующее внутри адаптера внешнего RecyclerView:

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview, parent, false);

        RVAdapter2 recyclerViewAdapter2 = new RVAdapter2();
        RecyclerView innerRV = (RecyclerView) v.findViewById(R.id.rv2);
        // Setup layout manager for items
        LinearLayoutManager layoutManager2 = new LinearLayoutManager(parent.getContext());
        // Control orientation of the items
        layoutManager2.setOrientation(LinearLayoutManager.VERTICAL);
        innerRV.setLayoutManager(layoutManager2);
        innerRV.setAdapter(recyclerViewAdapter2);

        innerRV.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                recyclerView.getParent().requestDisallowInterceptTouchEvent(true);
            }
        });

        return new MyViewHolder(v);
    }

Для API23 вы также можете попробовать innerRV.setOnScrollChangeListener, потому что setOnScrollListener устарел.

UPDATE:

Другим вариантом является addOnScrollListener вместо setOnScrollListener

Надеюсь, что это поможет!

Ответ 10

Вы должны сделать так:

innerRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {

        @Override
        public void onTouchEvent(RecyclerView recycler, MotionEvent event) {
            // Handle on touch events here
            int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:

                    recycler.getParent().requestDisallowInterceptTouchEvent(true);

                    break;

                case MotionEvent.ACTION_UP:

                    recycler.getParent().requestDisallowInterceptTouchEvent(false);

                    break;

                case MotionEvent.ACTION_MOVE:

                    recycler.getParent().requestDisallowInterceptTouchEvent(true);

                    break;
            }


        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView recycler, MotionEvent event) {
            return false;
        }

    });

Надеюсь, это поможет вам.

Ответ 11

Используйте этот код, чтобы отключить прокрутку на recyclerview:

recyclerView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return true;
    }
});

Ответ 12

Проблема в реализации Android для метода onInterceptTouchEvent() для RecyclerView. Этот блог вызывает проблему и исправляет ее также - http://nerds.headout.com/fix-horizontal-scrolling-in-your-android-app/. Единственное отличие состоит в том, что родительский свиток вертикально и дочерний по горизонтали. Но решение позаботится о том, чтобы оно работало и для вашей ситуации.

Ответ 13

расширьте свой собственный менеджер компоновки

 public class CustomLayoutManager extends LinearLayoutManager {
 private boolean isScrollEnabled = true;

 public CustomGridLayoutManager(Context context) {
 super(context);
        }
  @Override
 public boolean canScrollVertically() {
 return false;
 }
}

Установите диспетчер компоновки в этот "Менеджер пользовательских макетов"