Вложенное 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;
}
}
Установите диспетчер компоновки в этот "Менеджер пользовательских макетов"