在App中,可以滑动的控件随处可见(viewPager,listView),毕竟在移动设备上,屏幕就那么大,要想给用户呈现更多的内容,我们的view就得支持滑动了,多个可以滑动的View嵌套在一起,就会引发一个问题,那就是一个事件(MotionEvent)到来时,到底哪一个view响应呢?这时应用的选择困难症犯病了,它会变得抓狂,我们的应用就会失常,这时我们要做的就是帮助它做出选择。废话不多说,下面我们就分析一下不同场景下滑动冲突的解决方案(全是套路啊)。
滑动冲突是View体系中一个深入的话题,理解滑动冲突的解决方法,需要对view的事件分发体制有一定的了解。
滑动冲突场景
滑动嘛,不是左右滑动,就是上下滑动,冲突场景无非就是这两种情况的组合,最常见的冲突场景主要有以下三种: (1)内外两层view滑动方向不同
这种冲突场景也是最简单的一种了,在这里,外层view为水平方向的scrollView,内层view为listview。下面就用“外部拦截法”和“内部拦截法”分别对这种情景进行分析。 ① 外部拦截法: 可想而知,这种方法是对外部view做处理,内部view不需做处理。由外部view来决定事件是否传递到内部view(当然,事件得能传递到外部view),如果外部view决定消耗此事件,则外部view进行事件拦截,反之,外部view不拦截事件,将事件传递到内部view进行处理。 需要说一下,处理滑动冲突的策略并不是唯一的,只要可以解决问题即可,对于此种冲突场景,制定如下处理策略: 我们可以计算滑动在x,y方向上的偏移量(当然也可以通过滑动角度),如果abs(dx)>abs(dy),说明用户左右滑动的趋势较为明显,外层view拦截并消耗事件,若abs(dx)<abs(dy),说明用户上下滑动的趋势较为明显,外层view不拦截事件,由内层view处理事件。 我们需要实现外层view的InterceptTouchEvent(MotionEvent ev),来决定是否拦截事件。
float latestX;
float latestY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean isIntercept = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
isIntercept = false;
break;
case MotionEvent.ACTION_MOVE:
float dx = Math.abs(ev.getRawX() - latestX);
float dy = Math.abs(ev.getRawY() - latestY);
if (dx > dy) {
isIntercept = true;
} else {
isIntercept = false;
}
break;
case MotionEvent.ACTION_UP:
isIntercept = false;
break;
}
latestX = ev.getRawX();
latestY = ev.getRawY();
return isIntercept;
}
② 内部拦截法:
外层view默认不拦截,由内层view决定事件是由自己消耗掉还是推给外层view处理,这种方法外层view和内层view都需要做处理。外层view需要实现onInterceptTouchEvent(MotionEvent ev),内层view需要实现dispatchTouchEvent(MotionEvent ev)。
外层view:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean isIntercept = false;
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
isIntercept = false;
} else {
isIntercept = true;
}
return isIntercept;
}
内层view:
float latestX;
float latestY;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
float dx = Math.abs(ev.getRawX() - latestX);
float dy = Math.abs(ev.getRawY() - latestY);
if (dx > dy) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
latestX = ev.getRawX();
latestY = ev.getRawY();
return super.dispatchTouchEvent(ev);
}
(2)内外两层view滑动方向相同
处理这种冲突,就需要视业务需求而看了,我们的业务需求决定什么条件下让外层view消耗事件,什么条件下让内层view消耗事件,核心思想与上面的情景很类似,就是决定条件发生变化而已,这里就不重复了。
(3)上面两种的组合
这种多重嵌套的情景就更复杂了,但是,归根结底,其处理思想与第一种相似,就需要看具体业务需求了。