项目里有个需求,类似今日头条新闻主界面,新闻中包含抖音小视频的样式。具体点: 项目主界面时用 ViewPager + Fragment 来实现, Fragment 中 使用了 ListView控件来展示内容,其中 ListView 的一种 item 类型是个横滑的 RecyclerView ,使用默认控件实现了此功能,但有个问题就是 RecyclerView 可以左右滑动,但是手指上下滑动时,外层的 ListView 没有滑动,因为 RecyclerView 把事件给消费了,为了解决这个问题,自定了个 RecyclerView, 重写了事件分发的细节,增加了判断,根据 getParent().requestDisallowInterceptTouchEvent() 这个方法来请求父容器是否拦截触摸事件
RecyclerView :
int lastX = 0;
int lastY = 0;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getRawX();
int y = (int) ev.getRawY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 保证子View能够接收到Action_move事件
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int dealtX = Math.abs(x - lastX);
int dealtY = Math.abs(y - lastY);
// 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截, 加2的原因是为了更倾向于横滑
if (dealtX >= dealtY / 2) {
getParent().requestDisallowInterceptTouchEvent(true);
} else {
getParent().requestDisallowInterceptTouchEvent(false);
}
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return super.dispatchTouchEvent(ev);
}
这样,要求就基本实现了。如果是横滑,然后竖滑,是可以让 ListView 上下滑动的;但如果先是竖滑,ListView 上下滑动了,再横滑,RecyclerView 是不会左右滑动的,原因是在之前的事件分发机制中有介绍。
上述代码初步实现功能,但还有个问题。就是 RecyclerView 左右滑动时,假如滑到了最右边,此刻所在的 Fragment 是ViewPager中的第一个,ViewPager 中还有5个 Fragment,此时RecyclerView 滑到了最右边,手指继续向左滑动,RecyclerView 已经到边了,此时我们想让 ViewPager 滑动,显示出下一个 Fragment,怎么办? 此时就要开动脑筋了,首先要判断是否滑到了 RecyclerView 的最左边或最右边,如果是,则把事件处理权交割给父容器,一旦事件处理权交给父容器,则没自己什么事了。如果没到左右边缘,则还是自己消费事件。 横滑的 RecyclerView 使用的是 LinearLayoutManager,它里面恰好提供了第一个或最后一个完整显示view的索引的方法,即 findFirstCompletelyVisibleItemPosition() 、findLastVisibleItemPosition() 方法
RecyclerView :
int lastX = 0;
int lastY = 0;
int type = 0;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getRawX();
int y = (int) ev.getRawY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 保证子View能够接收到Action_move事件
getParent().requestDisallowInterceptTouchEvent(true);
type = 1;
lastX = x; // 可以省略
lastY = y; // 可以省略
mStartXIntercept = x;
break;
case MotionEvent.ACTION_MOVE:
if (type == 1) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
if (x - lastX > 5 && linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
type = 2;
getParent().requestDisallowInterceptTouchEvent(false);
} else if (x - lastX < -5 && linearLayoutManager.findLastVisibleItemPosition() == getAdapter().getItemCount() - 1) {
type = 2;
getParent().requestDisallowInterceptTouchEvent(false);
} else {
type = 0;
}
} else if (type == 0) {
int dealtX = Math.abs(x - lastX);
int dealtY = Math.abs(y - lastY);
// 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截
if (dealtX >= dealtY / 2) {
getParent().requestDisallowInterceptTouchEvent(true);
} else {
getParent().requestDisallowInterceptTouchEvent(false);
}
}
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return super.dispatchTouchEvent(ev);
}
这样,当 RecyclerView 滑到左右边缘时,就会请求父容器拦截触摸事件,从而把触摸事件的处理权交给父容器,这样就达到了滑到边缘,ViewPager 开始左右滑动的功能。