Android 事件分发

1.事件分发:
分类
Activity ,ViewGroup ,View
a.Activity中包含 dispatchTouchEvent(MotionEvent ev), onTouchEvent(MotionEvent ev);
b.ViewGroup中包含 dispatchTouchEvent(MotionEvent ev) ,onInterceptTouchEvent(MotionEvent ev), onTouchEvent(MotionEvent ev);
c.View 中包含 dispatchTouchEvent(MotionEvent ev) ,onTouchEvent(MotionEvent ev);
所有与屏幕触发的点都封装到MotionEvent类中。

分析:
Activity:
public boolean dispatchTouchEvent(MotionEvent ev){
if(ev.getActionDown() == MotionEvent.ACTION_DOWN){
onUserInteraction(); //空方法 -子类可以重写
}
if(getWindow().superDispatchTouchEvent(ev)){ //getWindow() 具体事例是PhoneWindow
return true;
}
return onTouchEvent(ev);
}
在PhoneWindow中调动DectorView调用 superDispatchTouchEvent(MotionEvent ev),这个方法没有做处理,直接调用了super.dispatchTouchEvent(ev)方法,所以最终从 Activity中的dispatchTouchEvent(MotionEvent ev)方法进入了ViewGroup中的dispatchTouchEvent(ev)方法中,中间过程是通过PhoneWindow进行了桥接。
ViewGroup:
在谈ViewGroup中的 dispatchTouchEvent(MotionEvent ev) 方法之前,先介绍一下MotionEvent这个类:
部分属性:
MotionEvent.ACTION_MASK = 0xFF //二进制 1111 1111

MotionEvent.ACTION_DOWN =0 // 二进制 0000 0000
MotionEvent.ACTION_UP = 1 // 二进制 0000 0001
MotionEvent.ACTION_MOVE =2 // 二进制 0000 0010
MotionEvent.ACTION_CANCEL=3 //二进制 0000 0011

所以通过上述属性,需要明白的一点:
ACTION_DOWN&ACTION_MASK = 0000 0000 —>ACTION_DOWN
只要其他具体的ACTION_XXXX & ACTION_MASK 的结果就是 ACTION_XXXX
另外:一个完整的事件流程从MotionEvent.ACTION_DOWN开始,执行多个ACTION_MOVE,然后
执行ACTION_UP 结束,恰好对应开始触摸屏幕,在屏幕上滑动,离开屏幕的过程,但是对于代码中的dispatchTouchEvent(MotionEvent ev)方法到底执行了几次?以及MotionEvent对象是同一个吗?
我的答案:dispatchTouchEvent方法执行了1次ActionDown ,1次ActionUp, 另外还有n次ActionMove。
那么又会产生一个问题每一次传入的MotionEvent都是不一样的吗?每次都要创建新的MotionEvent对象吗,这里给出答案:MotionEvent会进行复用,但是里面的值比如getX(),getY(),getRawX(),getRawY() 等内部值都是更新的,
好了,接下来继续分析ViewGroup中的dispatchTouchEvent()
1.只分析源代码中ACTION_DOWN事件:
原则:ACTION_DOWN在每次事件流程中只执行一次
public boolean dispatchTouchEvent(MotionEvent ev) {
… //省略部分代码
boolean handled = false;
if (ev.getAction() & MotionEvent.ACTION_MASK == MotionEvent.ACTION_DOWN) {
/**
*因为每一次的ActionDown都是整个事件的流程开始,所以要重置状态
*重置状态包括:清除mFirstTouchTarget链表,清除FLAG_DISALLOW_INTERCEPT,即清除拦截状态。
*/
cancelAndClearTouchTargets(ev);
resetTouchState();
}
if (ev.getAction() == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//这块要说明一下 mGroups & FLAG_DISALLOW_INTERCEPT 计算结果意义:
//如果是 FLAG_DISALLOW_INTERCEPT则不为0,返回true,反之。
//如果返回true说明不允许拦截,默认情况返回是false, 即我们并未在代码设置
//FLAG_DISALLOW_INTERCEPT 状态(详细查看=
// requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法)
//所以对于事件的Action_down而言,默认是执行onInterceptTouchEvent()方法的
final boolean disallowIntercept = (mGroups & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//在ActionDown时如果该viewGroup没有进行调用,requestDisallowInterceptTouchEvent(true);
//该方法会默认执行
intercept = onInterceptTouchEvent(event);
}
}
//如果事件没有被取消且没有被拦截
if (!cancel && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//这里默认ViewGroup中子view和绘制顺序一致,customOrder = false
final ArrayList preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//倒序遍历
for (int i = childrenCount - 1; i >= 0; i–) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);

//关键代码
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}

                    // The accessibility focus didn't handle the event, so clear
                    // the flag and do a normal dispatch to all children.
                    ev.setTargetAccessibilityFocus(false);
                }
                if (preorderedList != null) preorderedList.clear();
            }

            if (newTouchTarget == null && mFirstTouchTarget != null) {
                // Did not find a child to receive the event.
                // Assign the pointer to the least recently added target.
                newTouchTarget = mFirstTouchTarget;
                while (newTouchTarget.next != null) {
                    newTouchTarget = newTouchTarget.next;
                }
                newTouchTarget.pointerIdBits |= idBitsToAssign;
            }
        }
    }
}

分析:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;

    // Canceling motions is a special case.  We don't need to perform any transformations
    // or filtering.  The important part is the action, not the contents.
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    // Calculate the number of pointers to deliver.
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    // If for some reason we ended up in an inconsistent state where it looks like we
    // might produce a motion event with no pointers in it, then drop the event.
    if (newPointerIdBits == 0) {
        return false;
    }

    // If the number of pointers is the same and we don't need to perform any fancy
    // irreversible transformations, then we can reuse the motion event for this
    // dispatch as long as we are careful to revert any changes we make.
    // Otherwise we need to make a copy.
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);

                handled = child.dispatchTouchEvent(event);

                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }

    // Perform any necessary transformations and dispatch.
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }

        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

这里做一下总结:
正常流程:
1.执行ACTION_DOWN事件,首先重置状态,重置状态包括将mFirstTouchTarget清空,正常流程是不执行拦截,那么不拦截的话,标识是否拦截的标识变量intercepter 为false, 继而向下分发去寻找消费该事件的View,然后生成mFirstTouchTarget。
a.如果没找到,即子view遍历之后,mFirstTouchTarget依然为空的话,该view则会执行super.dispatchTouchEvent,即而执行onTouchEvent()方法
如果没有找到的话,在执行ACTION_MOVE事件时,直接在顶层的DectorView处即执行了super.DispatchTouchEvent()方法,不会下发到我们的自定义view处的dispatchTouchEvent()事件了,因此我们打印log,也不会发现dipatchTouchEvent()事件打印。
b.如果找到了,那么mFirstTouchEvent不会为空,当事件为ACTION_MOVE时,执行dispatchTouchEvent()方法,不会去循环遍历查找子view啦,直接去找到mFirstTouchTarget中保存的view,去执行 dispatchTouchEvent()方法,这里的描述或许会引起别人迷惑,什么是直接找mFirstTouchTarget中保存的view呢?举个例子

我点击了图中红色位置区域,事件的流程来理解这个mFirstTouchTarget。
这里的前提是:最外层的viewGroup是我们xml文件中最外层的ViewGroup,不是DectorView ,也是
PhoneWindow中的mContentParent。
首先是Activity中 dispatchTouchEvent事件的执行,然后调到getWindow.superDispatchTouchEvent(),然后是mDectorView中的superDispatchTouchEvent(),即调用了mDectorView中的 dispatchTouchEvent(),当然mDectorView中的dispatchTouchEvent实现在其父类ViewGroup中。好了,以上是流程,接下来就是事件分发比较容易迷惑的地方了。
那么还是从 MotionEvent.ACTION_DOWN开始,mDectorView中的dispatchTouchEvent()方法,会
循环子View找到mContentParent,然后 mContentParent也是ViewGroup,即执行mContentParent的dispatchtouchEvent(),然后执行ViewGroup1中的dispatchTouchEvent, —>ViewGroup2中的dispatchTouchEvent() -->执行Button(Button是个View)即执行View的dispatchTouchEvent();
然后执行Button中的onTouchEvent()方法
1.如果button中的onTouchEvent()方法消费了该事件即返回了true,则ViewGroup2中的dispatchTouchEvent()中则会生成mFristTouchTarget,这个mFirstTouchTarget里面中的View就是Button,接下来是ViewGroup2中的dispatchTouchEvent()方法也会生成一个mFirstTouchTarget,
那么这个mFirstTouchTarget中的View就是ViewGroup2,依次往上推。。。

以上是正常情况,那么接下来说一下拦截情况:
1.也是就是说,我们直接在ViewGroup2中的onIntercepterTouchEvent()方法返回了true,发生了什么:标识是否被拦截的变量 intercepted为true,不会遍历子view了,直接调用该视图的
super.dispatchTouchEvent(),即View的 dispatchTouchEvent。
但是有人讲,ViewGroup2中有个 requestDisallowInterceptTouchEvent()方法,设为true,表示不拦截,false为拦截,查看源码:
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// requestDisallowInterceptTouchEvent()方法参数传入true, (mGroupFlags & //FLAG_DISALLOW_INTERCEPT) 值为FLAG_DISALLOW_INTERCEPT ,disallowIntercept = true,
// 反之 disallowIntercept = false
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}

如上注释,总结两点,1.对于actionDown而言,
调用requestDisallowInterceptTouchEvent()传入的boolean值不会影响intercepted 的值,均为false
2.调用requestDisallowInterceptTouchEvent()传入的boolean值在Action_down事件是影响onInterceptTouchEvent()的执行。

2.如果我在ACTION_MOVE方法中调用requestDisallowInterceptTouchEvent()方法,效果是什么样的呢,查看源码:
如果我的事件流程没有任何View消费,即使再Action_Move方法中调用了requestDisallowInterceptTouchEvent()方法,没有影响,因为事件流程没有任何View消费,mFirstTouchTarget == null 啊,如果有view消费了呢,
比如说上图的button消费了,我在button中的dispatchTouchEvent()中的action_move中调用
getParent().requestDisallowInterceptTouchEvent()方法传入了true,有什么影响?
这里直接给结论了:
1.button中的dispatchTouchEvent()中的这次action_move调用getParent().requestDisallowInterceptTouchEvent()不会影响这次的MotionEvent了,这个button中的这个motionEvent就是从parent中传递过来的,但是因为在这次action_down中设置了requestDisallowInterceptTouchEvent(true)方法,会影响parent下次的ACTION_MOVE和ACTION_Up,所以分析ViewGroup2中下一次的ACTION_MOVE方法执行情况,因为设为true,所以
onInterceptTouchEvent不会执行,反之则会执行。
以上整个事件分发路程就结束了。
大家可以去git地址下载源代码,做一下简单的调试验证。

最后一点就是说MotionEvent整个类,这里不分析他的属性,也不分析方法。只解答一个问题,因为事件的分发,一次action_down,一次 action_up,多次action_move, 即其实dispatchTouchEvent()执行的次数就是n次action_move+2 ,那么MotionEvent作为传入的参数,每次都会创建一个新的吗,每次都new出来吗,这么考虑,如果每次都new的话,则最终要回收,消耗性能。
查看源码发现,其实MotionEvent构造和Message(消息机制的Message)是一样的,通过链表保存,有obtain()方法,有reccyle()方法,每次MotionEvent结束会被recycle();
@Override
public final void recycle() {
super.recycle();

    synchronized (gRecyclerLock) {
        if (gRecyclerUsed < MAX_RECYCLED) {
            gRecyclerUsed++;

//gRecyclerTop是static修饰的哦 ,插入队首。
mNext = gRecyclerTop;
gRecyclerTop = this;
}
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值