Horizontal RecyclerView 滚动行为中的 Vertical RecyclerView 内部的 Horizontal ScrollView
Horizontal ScrollView inside Vertical RecyclerView inside Horizontal RecyclerView scrolling behavior
我下面有一个 Activity
:
<RecyclerView> // with horizontal LinearLayoutManager and PagerSnapHelper
<RecyclerView> // with vertical LinearLayoutManager
<RecyclcerView /> // with horizontal LinearLayoutManager
<HorizontalScrollView />
// ... and there are other normal list items
</RecyclerView>
<RecyclerView> // another RV with vertical LinearLayoutManager
</RecyclerView>
</RecyclerView>
问题是当我水平滚动时,内部水平 RecyclerView
和 HorizontalScrollView
没有按预期滚动。
为什么我必须这样做:
我有几个 ListFragment
,每个都从不同的数据源获取数据,但具有相同类型的列表项。我不想要 ViewPager
,它要么在创建 ViewPager
后立即保留所有页面(这可能会导致 OutOfMemoryError),要么在用户滚动到其他页面并向后滚动后从网络重新加载数据。此外,使用嵌套 RV,页面可以共享一个相同的 RecyclerViewPool
,这有助于减少所有页面中的项目查看总数。
我在外横房车里做了什么:
private static final int INVALID_POINTER = -1;
private int mActivePointerId = INVALID_POINTER;
private float lastX;
private GestureDetector mVerticalScrollGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return Math.abs(distanceX) < Math.abs(distanceY);
}
});
private GestureDetector mHorizontalScrollGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return Math.abs(distanceX) > Math.abs(distanceY);
}
});
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (mVerticalScrollGestureDetector.onTouchEvent(e) || !mAllowPageScroll) {
return false;
}
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = e.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
int pointerIndex = e.findPointerIndex(mActivePointerId);
float x = e.getX(pointerIndex);
float dx = x - lastX;
lastX = x;
if (childCanScroll(this, false, (int) dx, (int) x)) {
Log.d("ScrollX", "not intercept");
return false;
}
Log.d("ScrollX", "maybe intercept");
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mActivePointerId = INVALID_POINTER;
break;
}
return super.onInterceptTouchEvent(e);
}
private boolean childCanScroll(View v, boolean considerSelf, int dx, int x) {
if (v instanceof ViewGroup) {
ViewGroup group = (ViewGroup) v;
int scrollX = v.getScrollX();
for (int i = group.getChildCount() - 1; i >= 0; i--) {
View child = group.getChildAt(i);
if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
&& childCanScroll(child, true, dx, x + scrollX - child.getLeft())) {
return true;
}
}
}
return considerSelf && v.canScrollHorizontally(-dx);
}
现在它与内部水平 RV 一起工作,偶尔不滚动,根本不与 HorizontalScrollView
一起工作 .
如何实现让内部水平滚动视图先滚动的目标?
我犯了两个错误。
在 ACTION_DOWN
的情况下,我需要记录 lastX
变量以及在 ACTION_MOVE
的情况下,以便 dx
的第一步] 是一个适当的值。这解释了为什么 childCanScroll
方法不适用于 ScrollView
(因为 RecyclerView
的 getScrollX()
总是 return 0)。
在方法childCanScroll
中,里面的if
语句不仅要考虑x坐标,还要考虑y坐标。如果不考虑 y,当我在内部水平滚动视图外滚动时,该方法也会 return true
,这会使事情无法正常工作。
改正这两个错误后一切正常
我下面有一个 Activity
:
<RecyclerView> // with horizontal LinearLayoutManager and PagerSnapHelper
<RecyclerView> // with vertical LinearLayoutManager
<RecyclcerView /> // with horizontal LinearLayoutManager
<HorizontalScrollView />
// ... and there are other normal list items
</RecyclerView>
<RecyclerView> // another RV with vertical LinearLayoutManager
</RecyclerView>
</RecyclerView>
问题是当我水平滚动时,内部水平 RecyclerView
和 HorizontalScrollView
没有按预期滚动。
为什么我必须这样做:
我有几个 ListFragment
,每个都从不同的数据源获取数据,但具有相同类型的列表项。我不想要 ViewPager
,它要么在创建 ViewPager
后立即保留所有页面(这可能会导致 OutOfMemoryError),要么在用户滚动到其他页面并向后滚动后从网络重新加载数据。此外,使用嵌套 RV,页面可以共享一个相同的 RecyclerViewPool
,这有助于减少所有页面中的项目查看总数。
我在外横房车里做了什么:
private static final int INVALID_POINTER = -1;
private int mActivePointerId = INVALID_POINTER;
private float lastX;
private GestureDetector mVerticalScrollGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return Math.abs(distanceX) < Math.abs(distanceY);
}
});
private GestureDetector mHorizontalScrollGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return Math.abs(distanceX) > Math.abs(distanceY);
}
});
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (mVerticalScrollGestureDetector.onTouchEvent(e) || !mAllowPageScroll) {
return false;
}
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = e.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
int pointerIndex = e.findPointerIndex(mActivePointerId);
float x = e.getX(pointerIndex);
float dx = x - lastX;
lastX = x;
if (childCanScroll(this, false, (int) dx, (int) x)) {
Log.d("ScrollX", "not intercept");
return false;
}
Log.d("ScrollX", "maybe intercept");
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mActivePointerId = INVALID_POINTER;
break;
}
return super.onInterceptTouchEvent(e);
}
private boolean childCanScroll(View v, boolean considerSelf, int dx, int x) {
if (v instanceof ViewGroup) {
ViewGroup group = (ViewGroup) v;
int scrollX = v.getScrollX();
for (int i = group.getChildCount() - 1; i >= 0; i--) {
View child = group.getChildAt(i);
if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
&& childCanScroll(child, true, dx, x + scrollX - child.getLeft())) {
return true;
}
}
}
return considerSelf && v.canScrollHorizontally(-dx);
}
现在它与内部水平 RV 一起工作,偶尔不滚动,根本不与 HorizontalScrollView
一起工作 .
如何实现让内部水平滚动视图先滚动的目标?
我犯了两个错误。
在
ACTION_DOWN
的情况下,我需要记录lastX
变量以及在ACTION_MOVE
的情况下,以便dx
的第一步] 是一个适当的值。这解释了为什么childCanScroll
方法不适用于ScrollView
(因为RecyclerView
的getScrollX()
总是 return 0)。在方法
childCanScroll
中,里面的if
语句不仅要考虑x坐标,还要考虑y坐标。如果不考虑 y,当我在内部水平滚动视图外滚动时,该方法也会 returntrue
,这会使事情无法正常工作。
改正这两个错误后一切正常