![](/img/trans.png)
[英]How to disable the click/touch events for parent and child recyclerview(nested recyclerview) but scrolling needs to work
[英]Nested RecyclerView. How to prevent parent RecyclerView from getting scrolled while child RecyclerView is scrolling?
我想實現一個水平recyclerview
和每個項目recyclerview
將是一個垂直recyclerview
與網格布局。 我面臨的問題是,當我嘗試垂直滾動子recyclerview
有時父recyclerview
會滾動並開始水平滾動。 我試圖解決這個問題的方法是,
recyclerview
setNestedScrollingEnabled(false)
上的setNestedScrollingEnabled(false)
recyclerview
onTouch()
中,我通過調用requestdisallowinterceptTouchevent(false)
禁用父recyclerview
requestdisallowinterceptTouchevent(false)
上的觸摸事件上述解決方案都沒有為問題提供完美的解決方案。 任何幫助表示贊賞
這個問題對我來說似乎很有趣。 所以我嘗試實現,這就是我所實現的(你也可以在這里看到視頻),非常流暢。
所以你可以嘗試這樣的事情:
定義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
,定義擴展GridLayoutManager
自定義CustomGridLayoutManager
:
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
水平滾動,子RecyclerView
垂直滾動。
除此之外,如果您還想處理對角滑動(幾乎不偏向垂直或水平),您可以在父RecylerView
包含一個手勢偵聽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)來處理滾動事件。 如果子視圖處理,那么父級什么都不做,父級最終會處理滾動。
父回收站視圖上的 setNestedScrollingEnabled(false)
您可以嘗試在子RecyclerView 上使用setNestedScrollingEnabled(false)
如果有)。 RecyclerView 的nestedscroll-ness 是一個孩子的(這就是它實現NestedScrollingChild
的原因)。
在子回收器視圖的 onTouch() 中,我通過調用 requestdisallowinterceptTouchevent(false) 禁用父回收器視圖上的觸摸事件
這應該可行,但您應該做的是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);
}
});
我在一個類似的項目中通過對你(以及這里的其他人)采取相反的方法來解決這個問題。
我沒有讓孩子告訴父母什么時候停止查看事件,而是讓父母決定什么時候忽略(根據方向)。 這種方法需要一個自定義視圖,但這可能需要更多的工作。 下面是我創建的將用作外部/父視圖的內容。
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's 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's 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
}
}
現在你可以試試android:nestedScrollingEnabled
因為谷歌修復了使用nestedScrollingEnabled
的崩潰(問題197932)
將偵聽器設置為嵌套的 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);
試試這個。 對於我的用例,它起作用了:
nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
試試下面的代碼,希望它會起作用。
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;
}
});
嘗試下面的代碼來滾動內部 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;
}
});
IMO,您可以在外部 RecyclerView 的 Adapter 中嘗試以下操作:
@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
已被棄用。
另一種選擇是使用addOnScrollListener
而不是setOnScrollListener
希望有幫助!
使用此代碼關閉recyclerview
滾動:
recyclerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
你應該這樣做:
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;
}
});
希望這會幫助你。
我對單元使用卡片視圖,並在子適配器中使用子單元setOnClickListener
停用父回收器視圖滾動。
holder.itemView.setOnTouchListener { view, _ ->
view.parent.parent.requestDisallowInterceptTouchEvent(true)
false
}
我們呼吁view.parent.parent
因為事實itemView
是一元布局和它的母公司是我們的孩子recyclerView
而且,我們需要達到的孩子recyclerView的父母,防止家長recyclerView
滾動。
該問題存在於 Android 對 RecyclerView 的 onInterceptTouchEvent() 方法的實現中。 這個博客指出了這個問題並修復了它 - http://nerds.headout.com/fix-horizontal-scrolling-in-your-android-app/ 。 唯一的區別是父級垂直滾動而子級水平滾動。 但是該解決方案注意它也應該適用於您的情況。
像這樣擴展自定義布局管理器
public class CustomLayoutManager extends LinearLayoutManager {
private boolean isScrollEnabled = true;
public CustomGridLayoutManager(Context context) {
super(context);
}
@Override
public boolean canScrollVertically() {
return false;
}
}
將布局管理器設置為此“自定義布局管理器”
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.