我正在尝试实现水平recyclerview
,recyclerview
的每个项目都是带有网格布局的垂直recyclerview
。我面临的问题是,当我尝试垂直滚动子recyclerview
时,父recyclerview
采用滚动并开始水平滚动。我尝试解决这个问题的方法是,
setNestedScrollingEnabled(false)
上的recyclerview
onTouch()
recyclerview
我通过调用recyclerview
requestdisallowinterceptTouchevent(false)
上的触摸事件
醇>
上述解决方案均未提供问题的完美解决方案。任何帮助表示赞赏
答案 0 :(得分:26)
这个问题对我很有意思。所以我试图实现,这就是我所取得的成果(你也可以看到video here),这非常流畅。
所以你可以尝试这样的事情:
像这样定义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
{/ p>}
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
中包含手势监听器。 / p>
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)来处理scroll事件。如果子视图处理,则父进程不执行任何操作,父进程最终处理滚动。
答案 1 :(得分:7)
父回收者视图上的setNestedScrollingEnabled(false)
您可以尝试在子 RecyclerView上setNestedScrollingEnabled(false)
,如果有的话。 RecyclerView的nestedscroll-ness是一个孩子的(这就是它实现NestedScrollingChild
的原因)。
在child recyclerview的onTouch()中,我禁用了触摸事件 通过调用父回收者视图 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);
}
});
答案 2 :(得分:7)
我通过对你(以及其他所有人)采取相反的方法,在类似的项目中解决了这个问题。
我不让孩子告诉父母何时停止查看事件,而是让父母决定何时忽略(基于方向)。这种方法需要一个自定义视图,虽然可以多做一点工作。下面是我创建的将用作外部/父视图的内容。
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
}
}
答案 3 :(得分:5)
将侦听器设置为嵌套的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);
答案 4 :(得分:4)
现在您可以尝试android:nestedScrollingEnabled
,因为Google使用nestedScrollingEnabled
(Issue 197932)
答案 5 :(得分:3)
试试这个。对于我的用例,它起作用了:
nestedRecyclerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
答案 6 :(得分:3)
尝试下面的代码,希望它能运作。
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;
}
});
答案 7 :(得分:2)
尝试以下代码滚动内部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;
}
});
答案 8 :(得分:2)
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
已被弃用。
另一种选择是使用addOnScrollListener
而不是setOnScrollListener
希望它有所帮助!
答案 9 :(得分:2)
你应该这样做:
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;
}
});
希望这会对你有所帮助。
答案 10 :(得分:1)
使用此代码关闭recyclerview
上的滚动:
recyclerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
答案 11 :(得分:1)
Android为RecyclerView实现onInterceptTouchEvent()方法存在问题。 这个博客提出了问题并修复了它 - http://nerds.headout.com/fix-horizontal-scrolling-in-your-android-app/。 唯一的区别是父母垂直滚动和儿童水平滚动。但是解决方案也要注意它应该适合你的情况。
答案 12 :(得分:1)
我在单元格中使用了卡视图,并在带有子单元itemView setOnClickListener
的子适配器中停用了父回收器视图滚动。
holder.itemView.setOnTouchListener { view, _ ->
view.parent.parent.requestDisallowInterceptTouchEvent(true)
false
}
我们之所以打电话给view.parent.parent
是因为itemView
是一个单元格布局,其父级是我们的子级recyclerView
,此外,我们还需要联系recyclerView子级的父级来防止父级{ {1}}滚动。
答案 13 :(得分:-1)
像这样扩展自定义布局管理器
public class CustomLayoutManager extends LinearLayoutManager {
private boolean isScrollEnabled = true;
public CustomGridLayoutManager(Context context) {
super(context);
}
@Override
public boolean canScrollVertically() {
return false;
}
}
将布局管理器设置为此“自定义布局管理器”