前言
在上一篇《SwipeLayout解析-动画篇》中,我们了解到其动画的实现主要依赖于ViewDragHelper来完成。而本文将结合《View事件分发浅析》,来分析在SwipeLayout中是如何处理事件的冲突的。
SwipeLayout事件冲突处理
在SwipeLayout中,对其内部的子View做滑动的处理,即重写onTouchEvent方法,onTouchEvent方法的部分代码如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
//1.如果屏蔽了抽屉效果,直接运行父类的onTouchEvent并返回
if (!isSwipeEnabled()) {
return super.onTouchEvent(event);
}
//2.处理点击事件
int action = event.getActionMasked();
gestureDetector.onTouchEvent(event);
//3.交由DragHelper,完成子View的动画更新
switch (action) {
case MotionEvent.ACTION_DOWN:
mDragHelper.processTouchEvent(event);
sX = event.getRawX();
sY = event.getRawY();
case MotionEvent.ACTION_MOVE: {
//the drag state and the direction are already judged at onInterceptTouchEvent
checkCanDrag(event);
if (mIsBeingDragged) {
getParent().requestDisallowInterceptTouchEvent(true);
mDragHelper.processTouchEvent(event);
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged = false;
mDragHelper.processTouchEvent(event);
break;
default://handle other action, such as ACTION_POINTER_DOWN/UP
mDragHelper.processTouchEvent(event);
}
return super.onTouchEvent(event) || mIsBeingDragged || action == MotionEvent.ACTION_DOWN;
}
在该方法中主要分为三个部分,首先,会去判断是否开启了抽屉效果,如果未开启就会去调用父类View下的onTouchEvent,否则就执行SwipeLayout下的处理方法,然后,调用gestureDetector中的方法,这里主要做了对点击事件的处理,比如click,longClick等监听事件的响应,最后会做一个Switch判断,其调用了DragHelper的processTouchEvent,即将手势事件传递到ViewDragHelper中处理。
在onTouchEvent中,如果要消耗点击事件,就需要在返回值中设置为true,而在SwipeLayout中重写的onTouchEvent,我们可以看到由三个值决定,并且是或关系,对于ACTION_DOWN事件,会返回true值进行事件的处理和消耗,因此,在开启了SwipeEnabled的情况下,当事件传递到SwipeLayout上时,所有的手势都会在onTouchEvent被消耗。
在SwipeLayout重载了onTouchEvent后,我们可以在Activity中使用了,如果我们仅仅是单独使用SwipeLayout,会发现一起都很顺利,左滑,下滑等等操作都能实现对应的效果。但是,当我们需要嵌套SwipeLayout的时候,或者在其子View中添加点击事件时,就会发现对于SwipeLayout的手势动画全部无效了,这就是事件冲突了。我们需要使用onInterceptTouchEvent结合oequestDisallowInterceptTouchEvent方法组合来解决该类问题。
通过View事件分发浅析,我们可以知道,如果ViewGroup的onInterceptTouchEvent方法返回了True,那么该事件就会被ViewGroup拦截,交由其onTouchEvent处理,并且不会再传递到子View中。因此,我们可以再SwipeLayout中设置该方法来达到分事件拦截的目的。该控件的作者在该方法做了如下实现:
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(mForceInterceptAble){
return mForceIntercept;
}
if (!isSwipeEnabled()) {
return false;
}
if (mClickToClose && getOpenStatus() == Status.Open && isTouchOnSurface(ev)) {
return true;
}
for (SwipeDenier denier : mSwipeDeniers) {
if (denier != null && denier.shouldDenySwipe(ev)) {
return false;
}
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDragHelper.processTouchEvent(ev);
mIsBeingDragged = false;
sX = ev.getRawX();
sY = ev.getRawY();
//if the swipe is in middle state(scrolling), should intercept the touch
if (getOpenStatus() == Status.Middle) {
mIsBeingDragged = true;
}
break;
case MotionEvent.ACTION_MOVE:
boolean beforeCheck = mIsBeingDragged;
checkCanDrag(ev);
if (mIsBeingDragged) {
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
if (!beforeCheck && mIsBeingDragged) {
//let children has one chance to catch the touch, and request the swipe not intercept
//useful when swipeLayout wrap a swipeLayout or other gestural layout
return false;
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mIsBeingDragged = false;
mDragHelper.processTouchEvent(ev);
break;
default://handle other action, such as ACTION_POINTER_DOWN/UP
mDragHelper.processTouchEvent(ev);
}
return mIsBeingDragged;
}
首先,根据代码可以知道,在不同的手势事件下有不同返回值。以SwipeLaytout A嵌套SwipeLayout B为例。
Down事件:该事件我们必须返回false,不然所有的事件都无法传递到子View。而在SwipeLayout中只有在动画执行中返回true。
MOVE事件:事件处理的重点在于MOVE事件,可以通过该手势来判断是点击还是拖拽操作,在SwipeLayout中,通过beforeCheck和mIsBeingDragged两个标志为来决定是否拦截,从代码可知,前者是mIsBeingDragged的初始值,当用户滑动的时候,mIsBeingDragged会在checkCanDrag(ev)被置为True,然后就会调用requestDisallowInterceptTouchEvent方法,该方法可以使父类的onInterceptTouchEvent值无效,紧接着做了一个判断:
if (!beforeCheck && mIsBeingDragged) {
//let children has one chance to catch the touch, and request the swipe not intercept
//useful when swipeLayout wrap a swipeLayout or other gestural layout
return false;
}
该方法是解决嵌套冲突的关键,其核心思想是在第一次判断为滑动的情况下不进行拦截,继续传递,所以SwipeLayout A在第一次Move事件中不进行拦截,将事件传递到SwipeLayout B(下一层View)中,这样便为SwipeLayout B提供了一次执行requestDisallowInterceptTouchEvent方法的机会,也以此来解决了嵌套的冲突情况。
UP和CANCEL事件:该事件并未做特殊处理,只是将事件交由ViewDragHelper处理。
通过以上的设置,就可以解决嵌套的事件冲突了,效果如下图:
![](https://img.haomeiwen.com/i1802592/d4e0420fba5385b0.gif)
但是在实际的使用过程中,发现可能出现滑动混乱的现象,在代码中的Down事件下mIsBeingDragged被置为True,这就导致了所有事件被拦截,具体原因初步判断时SwipeLayout的OpenStatus没有正确设置。
关于SwipeLayout控件,我Fork到了自己的github上(ZhangFengG/AndroidSwipeLayout),并做了一些设置项的修改,添加了对onInterceptTouchEvent的手动设置,这样可以更直观的观察该方法在ViewGroup中的作用:
![](https://img.haomeiwen.com/i1802592/6a84985e01da9249.png)
相关资料
end
网友评论