博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 源码分析(十一) 事件传递机制
阅读量:6645 次
发布时间:2019-06-25

本文共 15971 字,大约阅读时间需要 53 分钟。

一.介绍

  Android三种事件类型:ACTION_DOWN,ACTOIN_MOVE,ACTION_UP。

  事件传递的三个阶段:
    分发(Dispatch)
      方法:public boolean dispatchTouchEvent(MotionEvent ev)
    拦截(Intercept)
      方法:public boolean onInterceptTouchEvent(MotionEvent ev)
    消费(Consume)
      方法:public boolean onTouchEvent(MotionEvent event)

二.源码分析

//Activity.java       /**     * Called to process touch screen events.  You can override this to     * intercept all touch screen events before they are dispatched to the     * window.  Be sure to call this implementation for touch screen events     * that should be handled normally.     *     * @param ev The touch screen event.     *     * @return boolean Return true if this event was consumed.     */public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            //当此activity在栈顶时,用户对手机:触屏点击,按home,back,menu键都会触发此方法。            //注:下拉statubar,旋转屏幕,锁屏,不会触发此方法.            onUserInteraction();        }        //查看getWindow().对event做了什么分发?        if (getWindow().superDispatchTouchEvent(ev)) {            return true;        }        return onTouchEvent(ev);}Window.java 是一个抽象类。在Activity里attach()查看Window创建过程final void attach(...){    attachBaseContext(context);        mFragments.attachHost(null /*parent*/);        //PhoneWindow 是 getWindow()的具体实现类。进去查看它的superDispatchTouchEvent()        mWindow = new PhoneWindow(this, window, activityConfigCallback);        mWindow.setWindowControllerCallback(this);        mWindow.setCallback(this);        mWindow.setOnWindowDismissedCallback(this);        mWindow.getLayoutInflater().setPrivateFactory(this);        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {            mWindow.setSoftInputMode(info.softInputMode);        }        if (info.uiOptions != 0) {            mWindow.setUiOptions(info.uiOptions);        }        mUiThread = Thread.currentThread();        mMainThread = aThread;    ...}
//PhoneWindow.java extends Window    @Override    public boolean superDispatchTouchEvent(MotionEvent event) {        //PhoneWindow Event 分发操作有在DecorView实现了,继续进去看看DecorView类。        return mDecor.superDispatchTouchEvent(event);    }// This is the top-level view of the window, containing the window decor.    private DecorView mDecor;
//DecorView.java      public boolean superDispatchTouchEvent(MotionEvent event) {         //DecorView就是activity的顶级view,         //点击事件已经从activity传到View当中了,接下来要分析的就是顶级View把事件如何分发到各个子view中         //DecorView extends FrameLayout        return super.dispatchTouchEvent(event);    }         @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        final Window.Callback cb = mWindow.getCallback();        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);    }
//FrameLayout.java    //FrameLayout extends ViewGroup 到 ViewGroup里看看    public class FrameLayout extends ViewGroup {}
//ViewGroup.java    public boolean dispatchTouchEvent(MotionEvent ev) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);        }        // If the event targets the accessibility focused view and this is it, start        // normal event dispatch. Maybe a descendant is what will handle the click.        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {            ev.setTargetAccessibilityFocus(false);        }        boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;            // Handle an initial down.            //当点击事件是MotionEvent.ACTION_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;            }            // If intercepted, start normal event dispatch. Also if there is already            // a view that is handling the gesture, do normal event dispatch.            if (intercepted || mFirstTouchTarget != null) {                ev.setTargetAccessibilityFocus(false);            }            // 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;            //没有被取消并且没有被拦截,事件传递到子View             if (!canceled && !intercepted) {                // If the event is targeting accessiiblity focus we give it to the                // view that has accessibility focus and if it does not handle it                // we clear the flag and dispatch the event to all children as usual.                // We are looking up the accessibility focused host to avoid keeping                // state since these events are very rare.                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()                        ? findChildWithAccessibilityFocus() : null;                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 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 there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); 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(); 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; } } } // 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; }

ViewGroup 分发事件总结: 

 1. android的事件传递是先传递到父类,再到子类的。

 2. ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,但是子View可以通过requestDisallowInterceptTouchEvent(boolean disallowIntercept)控制父类的拦截事件是否调用。
 3. 子View消耗掉点击事件后,父类onTouchEvent方法不会调用,子View不消耗点击事件,会传到父类onTouchEvent方法,父类onTouchEvent方法返回false,则最终传递到activity的onTouchEvent方法。
 4. ViewGroup一旦调用onInterceptTouchEvent方法拦截点击事件后,本次点击序列事件则都交于该ViewGroup处理,并且onInterceptTouchEvent将不再执行。
 5. 当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发下一个action.也就是说,子view 未消耗点击事件,dispatchTouchEvent返回false,这样mFirstTouchTarget =null,则后续action直接由ViewGroup执行,不传递给子View。

public boolean onInterceptTouchEvent(MotionEvent ev) {        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)                && ev.getAction() == MotionEvent.ACTION_DOWN                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)                && isOnScrollbarThumb(ev.getX(), ev.getY())) {            return true;        }        return false;    }
@Override    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {            // We're already in this state, assume our ancestors are too            return;        }        if (disallowIntercept) {            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;        } else {            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;        }        // Pass it up to our parent        if (mParent != null) {            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);        }    }

三.总结

一张图介绍PhoneWindow,DecorView,Activity,ViewGroup,View 关系

在来一张图介绍下ViewGroup里的事件分发过程:(该图片来自https://www.jianshu.com/p/cf22ea3b09a5)

注:此图横着分析看,由上到下,由左到右。几个概念注意下 :UserActivity(=Activiyt),ViewParent(=ViewGoup),ChildView(=View)。

 

转载于:https://www.cnblogs.com/bugzone/p/touchEvent.html

你可能感兴趣的文章
怎么看iOS human interface guidelines中的user control原则
查看>>
Mac OS10.11更新ruby,gem,安装cocoapods
查看>>
qhfl-6 购物车
查看>>
双十一错题集
查看>>
iframe中有ajax,设置iframe自适应高度
查看>>
Oracle配置网络服务
查看>>
usermod更改用户家目录
查看>>
CATALINA_BASE与CATALINA_HOME的区别
查看>>
简单的mvvm light 应用
查看>>
关于IE浏览器里修改元素style属性的问题
查看>>
什么是PT,PT和BT有什么不同?
查看>>
mac操作快捷键
查看>>
Ubuntu16.04安装RoboCup3D比赛的仿真平台
查看>>
Android重写getResources规避用户调整系统字体大小影响Android屏幕适配
查看>>
C#委托-事件示例
查看>>
3DES 加密 解密
查看>>
python中pydoc用法
查看>>
笔试题(企鹅)
查看>>
设计模式 — 模板模式
查看>>
重审自己
查看>>