我相信了解它的工作原理对您很重要,因此我将解释我设计解决方案所遵循的整个过程。请注意,此示例仅适用于两个 RecyclerView,但使用更多 RecyclerView 就像使用 RecyclerView 数组一样简单。
想到的第一个选项是监听两个 ScrollView 上的滚动变化,当其中一个滚动时,在另一个上使用 scrollBy(int x, int y)。不幸的是,以编程方式滚动也会触发侦听器,因此您最终会陷入循环。
要解决这个问题,您需要设置一个 OnItemTouchListener,它在 RecyclerView 被触摸时添加适当的 ScrollListener,并在滚动停止时将其移除。这几乎完美无缺,但如果您在长 RecyclerView 中执行快速投掷,然后在完成之前再次滚动它,则只会传输第一个滚动。
要解决此问题,您需要确保仅在 RecyclerView 空闲时添加 OnScrollListener。
我们看一下源码:
public class SelfRemovingOnScrollListener extends RecyclerView.OnScrollListener {
@Override
public final void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
recyclerView.removeOnScrollListener(this);
}
}
}
这是您需要扩展 OnScrollListeners 的类。这样可以确保在需要时将其移除。
然后我有两个监听器,每个 RecyclerView 一个:
private final RecyclerView.OnScrollListener mLeftOSL = new SelfRemovingOnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
mRightRecyclerView.scrollBy(dx, dy);
}
}, mRightOSL = new SelfRemovingOnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
mLeftRecyclerView.scrollBy(dx, dy);
}
};
然后在初始化时您可以设置 OnItemTouchListeners。最好为整个视图设置一个监听器,但是 RecyclerView 不支持这个。 OnItemTouchListeners 无论如何都不会造成问题:
mLeftRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
private int mLastY;
@Override
public boolean onInterceptTouchEvent(@NonNull final RecyclerView rv, @NonNull final
MotionEvent e) {
Log.d("debug", "LEFT: onInterceptTouchEvent");
final Boolean ret = rv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
if (!ret) {
onTouchEvent(rv, e);
}
return Boolean.FALSE;
}
@Override
public void onTouchEvent(@NonNull final RecyclerView rv, @NonNull final MotionEvent e) {
Log.d("debug", "LEFT: onTouchEvent");
final int action;
if ((action = e.getAction()) == MotionEvent.ACTION_DOWN && mRightRecyclerView
.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {
mLastY = rv.getScrollY();
rv.addOnScrollListener(mLeftOSL);
}
else {
if (action == MotionEvent.ACTION_UP && rv.getScrollY() == mLastY) {
rv.removeOnScrollListener(mLeftOSL);
}
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(final boolean disallowIntercept) {
Log.d("debug", "LEFT: onRequestDisallowInterceptTouchEvent");
}
});
mRightRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
private int mLastY;
@Override
public boolean onInterceptTouchEvent(@NonNull final RecyclerView rv, @NonNull final
MotionEvent e) {
Log.d("debug", "RIGHT: onInterceptTouchEvent");
final Boolean ret = rv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
if (!ret) {
onTouchEvent(rv, e);
}
return Boolean.FALSE;
}
@Override
public void onTouchEvent(@NonNull final RecyclerView rv, @NonNull final MotionEvent e) {
Log.d("debug", "RIGHT: onTouchEvent");
final int action;
if ((action = e.getAction()) == MotionEvent.ACTION_DOWN && mLeftRecyclerView
.getScrollState
() == RecyclerView.SCROLL_STATE_IDLE) {
mLastY = rv.getScrollY();
rv.addOnScrollListener(mRightOSL);
}
else {
if (action == MotionEvent.ACTION_UP && rv.getScrollY() == mLastY) {
rv.removeOnScrollListener(mRightOSL);
}
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(final boolean disallowIntercept) {
Log.d("debug", "RIGHT: onRequestDisallowInterceptTouchEvent");
}
});
}
还要注意,在我的特殊情况下,RecyclerViews 不是第一个接收到触摸事件的,所以我需要拦截它。如果这不是您的情况,您可以(应该)将代码从 onInterceptTouchEvent(...) 合并到 onTouchEvent(...)。
最后,如果您的用户尝试同时滚动您的两个 RecyclerView,这将导致崩溃。这里可能的最佳工作质量解决方案是在包含 RecyclerViews 的直接父级中设置 android:splitMotionEvents="false"。
您可以查看此代码here 的示例。