Android View事件传播机制

来源:互联网 发布:钱江晚报微信矩阵 编辑:程序博客网 时间:2024/05/18 21:06

AndroidView是最常用的一个类,很多控件都是继承它的,接下来主要介绍一些view的传播机制,这个对于处理各种点击事件还是很重要的,下面就拿android N的代码看一下view上面touch event的传播过程

 

一、View中的dispatchTouchEvent方法 

public boolean dispatchTouchEvent(MotionEvent event) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }         if (onFilterTouchEventForSecurity(event)) {            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                return true;            }             if (onTouchEvent(event)) {                return true;            }        }         if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);        }        return false;}

dispatchTouchEvent是你触碰屏幕后第一个进入的函数,所以从这边开始分析

首先看到这个条件   

 li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)

其中mViewFlags & ENABLED_MASK)代表这个控件是否enablemOnTouchListener.onTouch(this, event)这个就是我们看到的onTouch函数,你可以覆盖这个函数,默认情况下这个函数返回false,也就是上面的条件如果成立,那么就不会进入onTouchEvent这个函数,这个函数里面就是我们常见的onClickonLongClick这些事件 

 

二、ViewGroup中的dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent ev) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);        }         boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;             // Handle an initial down.            if (actionMasked == MotionEvent.ACTION_DOWN) {                // Throw away all previous state when starting a new touch gesture.                // The framework may have dropped the up or cancel event for the previous gesture                // due to an app switch, ANR, or some other state change.                cancelAndClearTouchTargets(ev);                resetTouchState();            }             // Check for interception.            final boolean intercepted;            if (actionMasked == MotionEvent.ACTION_DOWN                    || mFirstTouchTarget != null) {                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;            }             // Check for cancelation.            final boolean canceled = resetCancelNextUpFlag(this)                    || actionMasked == MotionEvent.ACTION_CANCEL;             // Update list of touch targets for pointer down, if needed.            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;            TouchTarget newTouchTarget = null;            boolean alreadyDispatchedToNewTouchTarget = false;            if (!canceled && !intercepted) {                if (actionMasked == MotionEvent.ACTION_DOWN                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                    final int actionIndex = ev.getActionIndex(); // always 0 for down                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)                            : TouchTarget.ALL_POINTER_IDS;                     // Clean up earlier touch targets for this pointer id in case they                    // have become out of sync.                    removePointersFromTouchTargets(idBitsToAssign);                     final int childrenCount = mChildrenCount;                    if (newTouchTarget == null && childrenCount != 0) {                        final float x = ev.getX(actionIndex);                        final float y = ev.getY(actionIndex);                        // Find a child that can receive the event.                        // Scan children from front to back.                        final View[] children = mChildren;                         final boolean customOrder = isChildrenDrawingOrderEnabled();                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = customOrder ?                                    getChildDrawingOrder(childrenCount, i) : i;                            final View child = children[childIndex];                            if (!canViewReceivePointerEvents(child)                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                continue;                            }                             newTouchTarget = getTouchTarget(child);                            if (newTouchTarget != null) {                                // Child is already receiving touch within its bounds.                                // Give it the new pointer in addition to the ones it is handling.                                newTouchTarget.pointerIdBits |= idBitsToAssign;                                break;                            }                             resetCancelNextUpFlag(child);                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                // Child wants to receive touch within its bounds.                                mLastTouchDownTime = ev.getDownTime();                                mLastTouchDownIndex = childIndex;                                mLastTouchDownX = ev.getX();                                mLastTouchDownY = ev.getY();                                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                alreadyDispatchedToNewTouchTarget = true;                                break;                            }                        }                    }                     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;                    }                }            }             // Dispatch to touch targets.            if (mFirstTouchTarget == null) {                // No touch targets so treat this as an ordinary view.                handled = dispatchTransformedTouchEvent(ev, canceled, null,                        TouchTarget.ALL_POINTER_IDS);            } else {                // Dispatch to touch targets, excluding the new touch target if we already                // dispatched to it.  Cancel touch targets if necessary.                TouchTarget predecessor = null;                TouchTarget target = mFirstTouchTarget;                while (target != null) {                    final TouchTarget next = target.next;                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                        handled = true;                    } else {                        final boolean cancelChild = resetCancelNextUpFlag(target.child)                                || intercepted;                        if (dispatchTransformedTouchEvent(ev, cancelChild,                                target.child, target.pointerIdBits)) {                            handled = true;                        }                        if (cancelChild) {                            if (predecessor == null) {                                mFirstTouchTarget = next;                            } else {                                predecessor.next = next;                            }                            target.recycle();                            target = next;                            continue;                        }                    }                    predecessor = target;                    target = next;                }            }             // Update list of touch targets for pointer up or cancel, if needed.            if (canceled                    || actionMasked == MotionEvent.ACTION_UP                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                resetTouchState();            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {                final int actionIndex = ev.getActionIndex();                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);                removePointersFromTouchTargets(idBitsToRemove);            }        }         if (!handled && mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);        }        return handled; }

同样来分析一下这个函数,首先你会发现这个函数比View中的同样函数内容多了好多,这是因为涉及到事件传播机制了,继承View的都是像button这种没有子view的控件,所以没有事件传播,但是ViewGroup就不一样了,它需要进行传播,接下来看一下怎么传播吧

1、首先它会判断是否ACTION_DOWN或者是否有目标View了(ACTION_DOWN会产生目标view链表,链表不为空说明有目标view),如果是的话再判断是否拦截,拦截函数是onInterceptTouchEvent,下面是它的代码:

 public boolean onInterceptTouchEvent(MotionEvent ev) {        return false;}

可以看到很简单,true代表拦截,false代表不拦截,默认是不拦截的,如果拦截的话就调用View类的dispatchTouchEvent,不再传播事件给子view

 

2、如果不拦截的话那么就要开始传播这个事件了,选择子view有几个条件,像子view必须可见,而且点击的坐标在子view的范围内等,如果这些条件都满足,就会开始调用dispatchTransformedTouchEvent函数,下面是它的源码:

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; }

可以看到它里面主要调用了dispatchTouchEvent函数,这个函数会根据这个View有没有子view来决定调用View里的dispatchTouchEvent还是ViewGroup里的dispatchTouchEvent,这个就回到了上面介绍的方法了,从上面也可以看到如果dispatchTouchEvent返回false,那么接下来的事件都不会传给它了,可以看一下源码:

//add view to TouchTarget if dispatchTransformedTouchEvent return trueresetCancelNextUpFlag(child);if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {         // Child wants to receive touch within its bounds.         mLastTouchDownTime = ev.getDownTime();         mLastTouchDownIndex = childIndex;         mLastTouchDownX = ev.getX();         mLastTouchDownY = ev.getY();         newTouchTarget = addTouchTarget(child, idBitsToAssign);         alreadyDispatchedToNewTouchTarget = true;         break;}...

// deliver action to only view of TouchTargetwhile (target != null) {   final TouchTarget next = target.next;   if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {        handled = true;   } else {        final boolean cancelChild = resetCancelNextUpFlag(target.child)                          || intercepted;       if (dispatchTransformedTouchEvent(ev, cancelChild,                                target.child, target.pointerIdBits)) {           handled = true;       }       if (cancelChild) {            if (predecessor == null) {                    mFirstTouchTarget = next;            } else {                  predecessor.next = next;            }                  target.recycle();                  target = next;                  continue;            }       }      predecessor = target;      target = next; }

首先ACTION_DOWN时会检测所有的child view,如果dispatchTransformedTouchEvent返回false,那么这个view就不会加入到TouchTarget链表里,这样后面的ACTION_MOVE等其他事件只传播给TouchTarget里面的view,这样就轮不到那些不在TouchTarget里的view

 

3、通过分析还有一个地方需要注意,如果ACTIONACTION_DOWN或者ACTION_CANCEL都会重置状态,这个就是出现在ACTION被前面的view拦截后会收到一个ACTION_CANCEL事件来通知后面的view,这样后面的view就会重置状态。




原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 我收到了方正的提示函怎么办 淘宝刷q币单被骗了怎么办 中通快递已签收但是东西丢了怎么办 手机不版本低不支持微信下载怎么办 淘宝虚拟商品不支持7天退货怎么办 卖虚拟物品遇到恶意退款买家怎么办 淘宝极速退款后卖家拒绝退款怎么办 我的天猫积分不让换券了怎么办 微信手机话费充错了怎么办 自己进货在淘宝卖被投诉假货怎么办 京东买的电器售后后服务差怎么办 京东到家申请退款卖家不处理怎么办 天猫买了假货商品下架了怎么办 淘宝本地生活服务不能入驻了怎么办 淘宝店铺名在电脑上搜索不到怎么办 已经将退货寄回店家硬说没有怎么办 微信申诉账号短信验证失败怎么办 京东账号换手机号收不到短信怎么办 我的手机收不到短信通知怎么办? 淘宝卖家发货物流单号写错了怎么办 商铺买东西不给调换大小怎么办 圆通快递物流信息一直没更新怎么办 中通快递三天没更新物流信息怎么办 快递已经到了物流信息不更新怎么办 天天快递查询不更新物流信息怎么办 买车下个月分期全部付清怎么办手续 天猫客服介入以后商家不退款怎么办 淘宝上买代购奢侈品买到假货怎么办 淘宝退货卖家收到货拒绝退款怎么办 没收到货但申请了退货退款怎么办 小米商城预约中德手机没货怎么办 电脑用百度网盘下载速度超慢怎么办 ios网盘下载速度太慢怎么办 小米手机4x卡机了怎么办 小米手机4x屏幕点不动了怎么办 苹果手机连接u盘没反应怎么办 苹果官网储蓄卡分期额度不够怎么办 京东买东西发票信息填写错了怎么办 华为v9手机激活密码忘了怎么办 公司报销发票纸质的丢了怎么办 在京东上买的小天才手表坏了怎么办