一、常见的滑动冲突场景
场景1——外部滑动方向和内部滑动方向不一致,如:ViewPager中有多个fragment,而fragment中有ListView,这时ViewPager可以左右滑动,而ListView可以上下滑动,这就造成了滑动冲突。注意:这只是举个例子说明一下场景1,事实上ViewPager内部已经处理了这种滑动冲突,在采用ViewPager时,我们无需关注这个问题。
场景2——外部滑动方向和内部滑动方向一致。
场景3——上述两种场景的嵌套,即共有三层,外层与中层的滑动方向一致,而中层与内层的滑动方向不一致。
二、滑动冲突的处理规则
场景1的处理规则:
1、当用户左右滑动时,让外部的View拦截点击事件,当用户上下滑动时,让内部的View拦截点击事件;
2、判断用户的滑动方向(左右、上下):如果用户手指滑动的水平距离大于垂直距离,则左右滑动,反之,上下滑动;还可以根据角度、速度差来做判断;
场景2的处理规则:
无法根据滑动的角度、距离差、速度差来判断,因为场景2内部、外部的滑动方向一致;这时候一般都能在业务上找到突破点,如业务上规定:当处于某种状态需要外部View响应用户的滑动,而处于另一种状态时则需要内部View响应用户的滑动,所以我们可以根据业务的需求得出相应的处理规则。
场景3的处理规则:场景1的处理规则和场景2的处理规则一起用。
三、滑动冲突的解决方法
以下两种解决方法都是通用的方法:
1、外部拦截法
我们都知道点击事件是先经过父容器的拦截处理的,所以我们就可以在父容器的拦截方法中写下自己的逻辑代码即可,相当于我们要重写一个布局Layout,使其继承ViewGroup或其它的布局(LinearLayout等),然后重写其onInterceptTouchEvent方法。外部拦截法的伪代码如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int)ev.getX();
int y = (int)ev.getY();
switch (ev.getAction()){
//父容器不需要拦截
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
//在此判断父容器是否需要拦截
case MotionEvent.ACTION_MOVE:
if (父容器需要当前点击事件){
intercepted = true;
}
//父容器不需要当前的点击事件
else {
intercepted = false;
}
break;
//父容器不需要拦截
case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;
}
//重置手指的起始位置
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
解释上述代码:
1、父容器不拦截ACTION_DOWN事件:那是因为一旦父容器拦截了ACTION_DOWN事件,后续的ACTION_MOVE、ACTION_UP事件都会直接交给父容器处理,这个时候事件无法传递给子元素了。
2、父容器不拦截ACTION_UP事件:首先我们要知道onClick 事件是在ACTION_UP事件之后执行的,那当子元素有一个onClick事件,而这时候父容器拦截了ACTION_UP事件,那子元素的onClick事件就无法执行了。
3、ACTION_MOVE事件:在这里可以根据我们的需求,来判断父容器是否需要拦截事件,需要则返回true,否则返回false。
总结,外部拦截法比较简单,实现起来较容易。
2、内部拦截法
首先我们了解下ViewGroup.requestDisallowInterceptTouchEvent(boolean)方法,此方法就是在子View中通知父容器拦截或不拦截点击事件,false ——拦截,true ——不拦截;
内部拦截法的思想是父容器先不拦截任何事件,即所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。这时我们就用到requestDisallowInterceptTouchEvent 方法了。
具体做法:我们重写子View的dispatchTouchEvent方法,即我们新建一个类继承子View,然后重写它的dispatchTouchEvent方法,伪代码如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//获得当前的位置坐标
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
//通知父容器不要拦截事件
horizontalScrollLayout.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要此事件){
//通知父容器拦截此事件
horizontalScrollLayout.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
//重置手指的初始位置
mLastY = y;
mLastX = x;
return super.dispatchTouchEvent(ev);
}
- 代码:
1、horizontalScrollLayout是此子View的父容器的对象,我们可以在子 View中定义一个方法:
public void setHorizontalScrollLayout(HorizontalScrollLayout horizontalScrollLayout) {
this.horizontalScrollLayout = horizontalScrollLayout;
}
此时,父容器拦截了除ACTION_DOWN以外的其它事件,那为什么这样做?
原因:只有这样,才能使的当子元素调用了requestDisallowInterceptTouchEvent(false)方法后,父容器才能继续拦截所需的事件。
3、那父容器为什么不能拦截ACTION_DOWN事件呢?
原因:因为ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父容器拦截了ACTION_DOWN事件,那么所有的事件都无法传递给子元素了,这样内部拦截就起不到作用了。
总结,内部拦截法比较复杂,在实际应用中,建议还是用外部拦截法。
四、举例说明
在举例的时候,我自定义了几个view,大家主要看其中的dispatchTouchEvent方法和onInterceptTouchEvent方法,至于自定View的方法会在之后的博客中详细描述。
场景1的解决方案:外部拦截和内部拦截的方法都有 。代码下载请点我
实例代码介绍:
我们的场景是一个水平滑动的HorizontalScrollLayout里面包含一个垂直滑动的listview,有两种做法:一种是父控件拦截,一种是子控件拦截。
1.父控件拦截:
package com.example.baiyuanwei.slideclashtest;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
/**
* Created by baiyuanwei on 16/3/2.
* 外部拦截法
*/
public class HorizontalScrollLayout extends ViewGroup {
private static final String TAG = "HorizontalScrollLayout";
private int mChildrenSize;
private int mChildWidth;
private int mChildIndex;
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;
// 分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
public HorizontalScrollLayout(Context context) {
super(context);
init();
}
public HorizontalScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HorizontalScrollLayout(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted = true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
Log.d(TAG, "intercepted=" + intercepted);
mLastX = x;
mLastY = y;
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
scrollBy(-deltaX, 0);
break;
}
case MotionEvent.ACTION_UP: {
int scrollX = getScrollX();
int scrollToChildIndex = scrollX / mChildWidth;
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 50) {
mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
} else {
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
int dx = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dx, 0);
mVelocityTracker.clear();
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = 0;
int measuredHeight = 0;
final int childCount = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (childCount == 0) {
setMeasuredDimension(0, 0);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measuredWidth, heightSpaceSize);
} else {
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth() * childCount;
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(measuredWidth, measuredHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
mChildrenSize = childCount;
for (int i = 0; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
final int childWidth = childView.getMeasuredWidth();
mChildWidth = childWidth;
childView.layout(childLeft, 0, childLeft + childWidth,
childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
private void smoothScrollBy(int dx, int dy) {
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
protected void onDetachedFromWindow() {
mVelocityTracker.recycle();
super.onDetachedFromWindow();
}
}
这种方式,子控件不用做什么特殊处理。
2.字控件拦截
package com.example.baiyuanwei.slideclashtest;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ListView;
/**
* Created by baiyuanwei on 16/3/3.
* 采用内部拦截法解决滑动冲突
*/
public class MyListView extends ListView {
private final static String TAG = "MyListView";
//listView的容器
private HorizontalScrollLayout2 horizontalScrollLayout2;
//记录上次滑动的位置
private int mLastX = 0;
private int mLastY = 0;
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setHorizontalScrollLayout2(HorizontalScrollLayout2 horizontalScrollLayout2) {
this.horizontalScrollLayout2 = horizontalScrollLayout2;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//获得当前的位置坐标
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
//通知父容器不要拦截事件
horizontalScrollLayout2.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int delatX = x-mLastX;
int delatY = y - mLastY;
if (Math.abs(delatX)>Math.abs(delatY)){
//通知父容器拦截此事件
horizontalScrollLayout2.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
//重置手指的初始位置
mLastY = y;
mLastX = x;
return super.dispatchTouchEvent(ev);
}
}
package com.example.baiyuanwei.slideclashtest;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
/**
* 内部拦截法
*/
public class HorizontalScrollLayout2 extends ViewGroup {
private static final String TAG = "HorizontalScrollLayout2";
private int mChildrenSize;
private int mChildWidth;
private int mChildIndex;
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;
// 分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
public HorizontalScrollLayout2(Context context) {
super(context);
init();
}
public HorizontalScrollLayout2(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HorizontalScrollLayout2(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
mLastX = x;
mLastY = y;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
return true;
}
return false;
} else {
return true;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent action:" + event.getAction());
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY);
scrollBy(-deltaX, 0);
break;
}
case MotionEvent.ACTION_UP: {
int scrollX = getScrollX();
int scrollToChildIndex = scrollX / mChildWidth;
Log.d(TAG, "current index:" + scrollToChildIndex);
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 50) {
mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
} else {
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
int dx = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dx, 0);
mVelocityTracker.clear();
Log.d(TAG, "index:" + scrollToChildIndex + " dx:" + dx);
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = 0;
int measuredHeight = 0;
final int childCount = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (childCount == 0) {
setMeasuredDimension(0, 0);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measuredWidth, heightSpaceSize);
} else {
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth() * childCount;
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(measuredWidth, measuredHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.d(TAG, "width:" + getWidth());
int childLeft = 0;
final int childCount = getChildCount();
mChildrenSize = childCount;
for (int i = 0; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
final int childWidth = childView.getMeasuredWidth();
mChildWidth = childWidth;
childView.layout(childLeft, 0, childLeft + childWidth,
childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
private void smoothScrollBy(int dx, int dy) {
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
protected void onDetachedFromWindow() {
mVelocityTracker.recycle();
super.onDetachedFromWindow();
}
}
--------------------- 作者:struggle323 来源:CSDN 原文:https://blog.csdn.net/struggle323/article/details/50788136
==============================================
拓展内容:
修复垂直滑动RecyclerView嵌套水平滑动RecyclerView水平滑动不灵敏问题
在 Android 应用中,大部分情况下都会使用一个垂直滚动的 View 来显示内容(比如 ListView、RecyclerView 等)。但是有时候你还希望垂直滚动的View 里面的内容可以水平滚动。如果直接在垂直滚动的 View 里面使用水平滚动的 View,则滚动操作并不是很流畅。
比如下图中的示例:
为什么会出现这个问题呢?
上图中的布局为一个 RecyclerView 使用的是垂直滚动的 LinearLayoutManager 布局管理器,而里面每个 Item 为另外一个 RecyclerView 使用的是水平滚动的 LinearLayoutManager。而在 Android系统的事件分发 中,即使最上层的 View 只能垂直滚动,当用户水平拖动的时候,最上层的 View 依然会拦截点击事件。下面是 RecyclerView.java 中 onInterceptTouchEvent 的相关代码:
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
...
switch (action) {
case MotionEvent.ACTION_DOWN:
...
case MotionEvent.ACTION_MOVE: {
...
if (mScrollState != SCROLL_STATE_DRAGGING) {
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
...
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
...
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
} break;
...
}
return mScrollState == SCROLL_STATE_DRAGGING;
}
注意上面的 if 判断:
if(canScrollVertically && Math.abs(dy) > mTouchSlop) {...}
RecyclerView 并没有判断用户拖动的角度, 只是用来判断拖动的距离是否大于滚动的最小尺寸。 如果是一个只能垂直滚动的 View,这样实现是没有问题的。如果我们在里面再放一个 水平滚动的 RecyclerView ,则就出现问题了。
可以通过如下的方式来修复该问题:
if(canScrollVertically && Math.abs(dy) > mTouchSlop && (canScrollHorizontally || Math.abs(dy) > Math.abs(dx))) {...}
下面是一个完整的实现 BetterRecyclerView.java :
public class BetterRecyclerView extends RecyclerView{
private static final int INVALID_POINTER = -1;
private int mScrollPointerId = INVALID_POINTER;
private int mInitialTouchX, mInitialTouchY;
private int mTouchSlop;
public BetterRecyclerView(Contextcontext) {
this(context, null);
}
public BetterRecyclerView(Contextcontext, @Nullable AttributeSetattrs) {
this(context, attrs, 0);
}
public BetterRecyclerView(Contextcontext, @Nullable AttributeSetattrs, int defStyle) {
super(context, attrs, defStyle);
final ViewConfigurationvc = ViewConfiguration.get(getContext());
mTouchSlop = vc.getScaledTouchSlop();
}
@Override
public void setScrollingTouchSlop(int slopConstant) {
super.setScrollingTouchSlop(slopConstant);
final ViewConfigurationvc = ViewConfiguration.get(getContext());
switch (slopConstant) {
case TOUCH_SLOP_DEFAULT:
mTouchSlop = vc.getScaledTouchSlop();
break;
case TOUCH_SLOP_PAGING:
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(vc);
break;
default:
break;
}
}
@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 = MotionEventCompat.getPointerId(e, 0);
mInitialTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = (int) (e.getY() + 0.5f);
return super.onInterceptTouchEvent(e);
case MotionEventCompat.ACTION_POINTER_DOWN:
mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
mInitialTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
mInitialTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
return super.onInterceptTouchEvent(e);
case MotionEvent.ACTION_MOVE: {
final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
if (index < 0) {
return false;
}
final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
if (getScrollState() != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
final boolean canScrollHorizontally = getLayoutManager().canScrollHorizontally();
final boolean canScrollVertically = getLayoutManager().canScrollVertically();
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop && (Math.abs(dx) >= Math.abs(dy) || canScrollVertically)) {
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop && (Math.abs(dy) >= Math.abs(dx) || canScrollHorizontally)) {
startScroll = true;
}
return startScroll && super.onInterceptTouchEvent(e);
}
return super.onInterceptTouchEvent(e);
}
default:
return super.onInterceptTouchEvent(e);
}
}
}
其他问题
当用户快速滑动(fling)RecyclerView 的时候, RecyclerView 需要一段时间来确定其最终位置。 如果用户在快速滑动一个子的水平 RecyclerView,在子 RecyclerView 还在滑动的过程中,如果用户垂直滑动,则是无法垂直滑动的。原因是子 RecyclerView 依然处理了这个垂直滑动事件。
所以,在快速滑动后的滚动到静止的状态中,子 View 不应该响应滑动事件了,再次看看 RecyclerView 的 onInterceptTouchEvent() 代码:
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
...
switch (action) {
case MotionEvent.ACTION_DOWN:
...
if (mScrollState == SCROLL_STATE_SETTLING) {
getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
...
}
return mScrollState == SCROLL_STATE_DRAGGING;
}
可以看到,当 RecyclerView 的状态为 SCROLL_STATE_SETTLING (快速滑动后到滑动静止之间的状态)时, RecyclerView 告诉父控件不要拦截事件。
同样的,如果只有一个方向固定,这样处理是没问题的。
针对我们这个嵌套的情况,父 RecyclerView 应该只拦截垂直滚动事件,所以可以这么修改父 RecyclerView:
public class FeedRootRecyclerView extends BetterRecyclerView{
public FeedRootRecyclerView(Contextcontext) {
this(context, null);
}
public FeedRootRecyclerView(Contextcontext, @Nullable AttributeSetattrs) {
this(context, attrs, 0);
}
public FeedRootRecyclerView(Contextcontext, @Nullable AttributeSetattrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
/* do nothing */
}
}
下图为最终的结果:
如果感兴趣可以下载 示例项目 ,注意示例项目中使用 kotlin,所以需要配置 kotlin 插件。
原文:http://nerds.headout.com/fix-horizontal-scrolling-in-your-android-app/