愿历尽千帆 归来仍少年

一个滑动冲突问题的分析流程

字数统计: 1,154阅读时长: 4 min
2018/06/06

问题需求
原先效果是用户在ViewPager上长按会弹出一个dialog,现在加了一个需求:用户有时候在长按时出现手指向左或向右微小滑动,此时也需要判断为长按并弹出dialog

初步分析
外边的ViewPager是可以左右滑动的,现在希望单个界面即子view接收到触摸事件后能接管本次事件序列中后续事件,即子View接收到触摸事件后,注意此时手指未松开,所以还处于一次完整的事件序列中,等待一段时间Android默认是400ms后会识别为长按事件,此时子View的长按事件被触发。那么问题来了,怎么才能在子view接收到触摸事件并能接管本次事件序列呢?
这就要说到老生常谈的事件拦截了,正常拦截事件有外部拦截和内部拦截两种,这里因为代码中子view已写好现成的手势判断的相关方法,故这里为简单起见,采用内部拦截法来实现。

修改方法
LauncherPagerAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private static final int mTouchSlop = 40;
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = LayoutInflater.from(mContext).inflate(R.layout.launcher_item_layout, null);
ViewGroup parent = (ViewGroup) view.getParent();
//省略部分代码
//Add begin
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
//省略部分代码
case MotionEvent.ACTION_MOVE:
if (Math.abs(event.getY() - mDownY) <= ((float) mTouchSlop) && Math.abs(event.getX() - mDownX) <= ((float) mTouchSlop)) {
view.getParent().requestDisallowInterceptTouchEvent(true); //当出现上下左右距离不超过40时,父view不拦截事件, 交给子view
}
break;
}else if(Math.abs(event.getX() - mDownX) > ((float) mTouchSlop)){
view.getParent().requestDisallowInterceptTouchEvent(false); //当出现左右距离超过40时,父view拦截事件并自行消耗
}
}
break;
default:
break;
}
return false;
}
});
//Add end
//省略部分代码
}

这样的话,再对子view添加长按事件处理,也能顺利的接收到长按事件了。效果上便达到了微小长按滑动也会进入长按事件的处理逻辑中,注意此时微小的向左或向右滑动,viewpager本身并不会移动了,修改之前Viewpager会跟着滑动。

这个问题里用的是内部拦截法,其实也可以自定义Viewpager中重写canScrool()方法,这里就不介绍了,相比而言稍稍麻烦了一点

下面顺便说下滑动冲突的一般解决思路,这是个老生常谈的话题,解决方法已形成固定的流程,需要根据具体的实际业务需求灵活改变。

先介绍几个事件分发处理的几个常见方法
dispatchTouchEvent
主要是用来分发事件
onInterceptTouchEvent
主要是用来拦截事件的(ViewGroup才有这个方法,View没有这个方法)
onTouchEvent
这个方法主要是用来处理事件的
requestDisallowInterceptTouchEvent(true)
这个方法能够影响父View是否拦截事件,true 表示父 View 不拦截事件,false 表示父 View 拦截事件

滑动冲突一般解决方法
1.外部拦截法
一般是通过重写父控件的onInterceptTouchEvent方法,然后根据具体的需求,来决定父控件是否拦截事件。如果拦截返回返回true,不拦截返回false,比如说希望上下滑动不要拦截,那么就在父控件的onInterceptTouchEvent方法里的MotionEvent.ACTION_MOVE这个case块里返回false就好。如果希望指定位置不拦截,则再进行位置判断即可。

对于外部拦截法我们的核心工作一般是做在ACTION_MOVE中,不过需要注意的是不要在ACTION_DOWN中返回 true,这里一旦为true,则同一个事件序列ViewGroupdisPatchTouchEvent就不会再调用onInterceptTouchEvent方法了 。那么本次的事件序列后续都由父view接管,子view对这次事件序列也就没有了机会去消耗事件。归纳起来就是一句话: 父控件一旦拦截了事件,那么同一个事件序列的所有事件都将交给它处理。

2.内部拦截法
内部拦截法主要是通过调用父控件的 requestDisallowInterceptTouchEvent方法,传进去一个boolean参数值,true为请求父控件不拦截
需要注意的是父控件的onInterceptTouchEvent方法中的ACTION_DOWN事件不要拦截,一旦父控件拦截ACTION_DOWN事件,那么事件无法传递到子元素之中,内部拦截法也就无法起作用了

Good night!


住所,晚上,听着歌,洗漱完毕准备休息
CATALOG