我们在使用RecyclerView嵌套RecyclerView时,会觉得卡顿,操作不是很顺畅
RecyclerView嵌套RecyclerView滑动.gif
在这个现象中,我们想要水平滑动时,却竖直滑动了
我们观察RecyclerView的源码,在onInterceptTouchEvent方法中对事件做了处理
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (mLayoutFrozen) {
// When layout is frozen, RV does not intercept the motion event.
// A child view e.g. a button may still get the click.
return false;
}
if (dispatchOnItemTouchIntercept(e)) {
cancelTouch();
return true;
}
if (mLayout == null) {
return false;
}
final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
final boolean canScrollVertically = mLayout.canScrollVertically();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(e);
final int action = MotionEventCompat.getActionMasked(e);
final int actionIndex = MotionEventCompat.getActionIndex(e);
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mIgnoreMotionEventTillDown) {
mIgnoreMotionEventTillDown = false;
}
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
if (mScrollState == SCROLL_STATE_SETTLING) {
getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
// Clear the nested offsets
mNestedOffsets[0] = mNestedOffsets[1] = 0;
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis);
break;
case MotionEventCompat.ACTION_POINTER_DOWN:
mScrollPointerId = e.getPointerId(actionIndex);
mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
break;
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(mScrollPointerId);
if (index < 0) {
Log.e(TAG, "Error processing scroll; pointer index for id " +
mScrollPointerId + " not found. Did any MotionEvents get skipped?");
return false;
}
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
if (mScrollState != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
} break;
case MotionEventCompat.ACTION_POINTER_UP: {
onPointerUp(e);
} break;
case MotionEvent.ACTION_UP: {
mVelocityTracker.clear();
stopNestedScroll();
} break;
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
}
}
return mScrollState == SCROLL_STATE_DRAGGING;
}
主要看MOVE事件,RecyclerView中只做了距离判断,只要滑动的距离大于系统的默认滑动距离阈值,则本身进行滑动,这就导致了为什么我们水平滑动的时候,子RecyclerView接收不到Touch事件的原因,我们针对这点进行优化
/**
* 解决嵌套RecyclerView滑动问题
*/
public class BetterRecyclerView extends RecyclerView {
private int mTouchSlop;
private int mScrollPointerId = 0;
private int mInitialTouchX;
private int mInitialTouchY;
public BetterRecyclerView(@NonNull Context context) {
this(context, null);
}
public BetterRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BetterRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final ViewConfiguration vc = ViewConfiguration.get(context);
mTouchSlop = vc.getScaledTouchSlop();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
//仿造源码
final int action = MotionEventCompat.getActionMasked(e);
final int actionIndex = MotionEventCompat.getActionIndex(e);
switch (action) {
case MotionEvent.ACTION_DOWN://记录按下的坐标
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = (int) (e.getY() + 0.5f);
break;
case MotionEvent.ACTION_POINTER_DOWN://这边处理多手指触摸,优先使用后面触摸的手指
mScrollPointerId = e.getPointerId(actionIndex);
mInitialTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = (int) (e.getY() + 0.5f);
break;
case MotionEvent.ACTION_MOVE://仿造源码
final int index = e.findPointerIndex(mScrollPointerId);
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
boolean startScroll = false;
if (getScrollState() != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
//这边增加拦截的难度,当滑动的水平距离大于滑动的垂直距离时,才拦截,当然了,如果自身又能竖直滑动的情况,就不判断两个滑动距离大小
if ((getLayoutManager().canScrollHorizontally() && Math.abs(dx) > mTouchSlop)
&& (getLayoutManager().canScrollVertically() || Math.abs(dx) > Math.abs(dy))) {
startScroll = true;
}
//这里是当滑动的水平距离小于滑动的垂直距离时,才拦截,如果自身又能水平滑动的情况,就不判断两个滑动距离大小
if ((getLayoutManager().canScrollVertically() && Math.abs(dy) > mTouchSlop)
&& (getLayoutManager().canScrollHorizontally() || Math.abs(dy) > Math.abs(dx))) {
startScroll = true;
}
}
return startScroll && super.onInterceptTouchEvent(e);
}
return super.onInterceptTouchEvent(e);
}
}
我们修改了拦截方法,只有当水平滑动距离大于竖直滑动距离时,才让RecyclerView拦截事件
RecyclerView嵌套RecyclerView滑动2.gif
这时,发现了另一个问题,当子RecyclerView快速滑动,还没有停止滚动时,我们去竖直滑动,发现父RecyclerView没法接受到Touch事件,我们找到requestDisallowInterceptTouchEvent方法并改写了它,解决了这个问题
/**
* 这个接口是不允许父类打断这个onTouch事件,一般被子类调用getParent.requestDisallowInterceptTouchEvent(true)
* 比如快速水平滑动子view的时候,这时我们想去竖直滑动父类,因为快速滑动有一个速度,子view还没停止滚动,所以子类禁止父类拦截
* 想要解决这个办法,就把这个方法改为空方法就行了
*
* @param disallowIntercept
*/
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
RecyclerView嵌套RecyclerView滑动3.gif
网友评论