您的位置:首页 > 移动开发 > Android开发

关于Android的Touch事件的分发机制

2015-04-22 10:37 330 查看
首先,有三篇非常好的文章,值得一看。

Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制

Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

在此基础上,我自己又添加了一点理解,我是小白,可能有错,有人看的话直接指出来,别客气。

view的dispatchTouchEvent上面文章说得很清楚了,迷惑人的是当有好多层viewgroup的时候事件的分发。所以我就主要关心了事件到底找到谁来执行。比如,现在我弄了个tabhost中有viewpager,listview,还能滑动切换tab,右划出menu,结果往往彼此冲突!

public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
//A点,这一句将会层层调用,最后到达ACTION_DOWN区域最底层的那个view
// 若是返回了true,那么将会层层往上返回true
//若返回了false,将会执行到B点
//若果看完所有分析再回到这里,你会发现,原来片头所列文章中
//的为什么动作是一系列的,
//又为什么返回false和返回true那么难理解,
//button可以执行到upimageview,不行,
//其实这就是一个关键就在这里和B点,
//只有成为taget的才能分发到下一个动作!
if (child.dispatchTouchEvent(ev))  {
mMotionTarget = child;
return true;
}
}
}
}
}
}
//判断是否为ACTION_UP或者ACTION_CANCEL
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
//如果是ACTION_UP或者ACTION_CANCEL,
//将disallowIntercept设置为默认的false
//假如我们调用了requestDisallowInterceptTouchEvent()方法
//来设置disallowIntercept为true
//当我们抬起手指或者取消Touch事件的时候要将disallowIntercept重置为false
//所以说上面的disallowIntercept默认在我们每次ACTION_DOWN的时候都是false
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
//以下几句最后再看
//E点,最关键的时刻到了,如果一直分发下去,最后一个target代表的viewgroup
//(上一个拦截的vieroup)没有target的!
//调用父元素的super.dispatchTouchEvent()。
// 即view的dispatchTouchEvent()
// 现在就可以去讨论view的dispatchTouchEvent()方法了
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
//B点,由于Child没有将ACTION_DOWN消费,自己看是否消费,
// 若返回true,则上一级viewgroup执行到A点就层层往上返回true,
// 返回false,上一级viewgroup向下执行至此
// 以此循环直到返回到根节点
// 这里很关键的是直接给出了此viewgroup的dispatchTouchEvent(ev)的返回;
// 也就是说,若是ACTION_DOWN没能找到target给mMotionTarget赋值,其他动作都不能被分发!
// target的意义就在于此了,
// 正因为如此在每一个ACtionDown开始时先mMotionTarget=null

return super.dispatchTouchEvent(ev);
}
//由上分析可见,到此为止,一定找到了target,接下来的代码才有意义,
// 而ACTION_DOWN的所有判断和执行都已经完成,若没有这个
// 这个target一定是当前viewgroup的子view

//下面就是move和up了

//还有这里最多执行到倒数第二层,最下面的view的执行都是在C点和D点
//但是我们发现viewgroup先执行onInterceptTouchEvent(ev)然后执行C和D
// 也就是说我们可以同时在child和parent中进行操作,

// 很简单,拦截就到C,不拦截就到D
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
//将事件设为取消!!!!
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
//C点,当拦截move或up时,在这里调用target的dispatchTouchEvent(ev),但只是执行一次
// 无论返回什么,这个viewgroup都返回true,在上一级viewgroup中
// 即此viewgroup消费了此touch
// 调用一次target.dispatchTouchEvent(ev),执行的动作为取消!!!
if (!target.dispatchTouchEvent(ev)) {
}
//一旦拦截将 mMotionTarget = null,也就是说当下一个动作将执行final View target = mMotionTarget;
// 到if (target == null) 从而调用view的dispatchTouchEvent();
// 一定要记住的是move往往是一串!!!拦截后
//当前这个viewgroup就成为最后一个target,
//自己的target=null,将执行E点的parent.dispatchtouchEvent(ev)
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}

//D点,在此viewgroup不拦截的情况下,这一句是实际的处理move和up

//当我们的target!=null,这里就会层层向下分发
//直到taaget=null,执行E点
return target.dispatchTouchEvent(ev);
}


从上面我们已经可以看见,关键点就是ABCDE五处,ACTION_DOWN在AB两处找到target,C处的拦截会改变target(比如你在listview的所在viewgroup做move拦截,你会发现,好多点击都不灵了!,因为你手抖了,down找到的item不在是target,而单击的执行在up,结果没有效了)D处将把非ACTION_DOWN的事件层层向下发,直到没有target的那一个viewGROUP,这个在这里调用VIEW的dispatchTouchEvent(ev)方法。

至于怎么执行看viewgroup是否重写了view的ontouch()和onTouchEvent()方法。(重写这个是为了实现自定义的动作)

而往往我们注册的listener中就重写了这些方法,像listview(其实是abslistview)也重写这些方法。我们自定义view的时候也会重写他们。

而onInterceptTouchEvent(ev)的重写将直接关系到事件能不能向下分发下去!(重写这个是为了区分谁来消费动作)

其实这样想down-up,就是个点击,往往是直接到了最小的那个view,这正是我们想了,但down-move-up的时候我们一定是想在move的时候做判断,拦截。这样就能实现各种效果了。

也就是重写onInterceptTouchEvent(ev)和requestDisallowInterceptTouchEvent(boolen)。

至于怎么在move的时候拦截我还在学习中,我是初学,若有理解不到位或错误请指出。

现在我弄了个tabhost中有viewpager,listview,还能滑动切换tab,右划出menu,结果往往彼此冲突!

继续和这个纠缠。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: