最近在学习Android事件分发机制,也参考了网上许多文章。特别是Android事件分发机制详解:史上最全面、最易懂这篇文章,分析的特别全面、详细,所以在此文章基础上对分发机制做个总结。

1、activity事件分发机制

 在activity中,事件分发是从dispatchTouchEvent开始的,这个方法实现也很简单,代码如下:

    public boolean dispatchTouchEvent(MotionEvent ev) {        // 监听手指按下事件        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            onUserInteraction();        }        //交给PhoneWindow去处理,如果返回true则代表已经消费事件,否则activity自己处理事件。        if (getWindow().superDispatchTouchEvent(ev)) {            return true;        }        return onTouchEvent(ev);    }

 这里有一个很有意思的方法onUserInteraction,它是一个空实现,但它可以用来实现屏保功能且当点击Home、Back、Recent 时会调用栈顶activity的onUserInteraction方法。然后调用getWindow().superDispatchTouchEvent(ev)将事件交给DecorView处理,DecorView继承自FrameLayout,所以就将事件交给了ViewGroup处理。

来自于Android事件分发机制详解:史上最全面、最易懂

2、ViewGroup事件分发机制

 来看ViewGroup中dispatchTouchEvent的实现。

    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        ...        //默认返回false        boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;            //当手指按下时            if (actionMasked == MotionEvent.ACTION_DOWN) {                  //在开始新的触摸手势时丢弃所有先前的状态。 由于应用程序切换,ANR或某些其他状态更改,framework可能已经删除了先前手势的up或cancel事件。                cancelAndClearTouchTargets(ev);                resetTouchState();            }            //检查是否拦截事件            final boolean intercepted;            //监听手指按下、移动、抬起等事件,如果mFirstTouchTarget为null则可以认为没有子View响应事件了            if (actionMasked == MotionEvent.ACTION_DOWN                    || mFirstTouchTarget != null) {                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;                if (!disallowIntercept) {                    //调用onInterceptTouchEvent来判断是否需要拦截事件,默认是返回false                    intercepted = onInterceptTouchEvent(ev);                    ev.setAction(action); // restore action in case it was changed                } else {                    intercepted = false;                }            } else {                //当前ViewGroup消费事件                intercepted = true;            }            ...            if (!canceled && !intercepted) {                ...                if (actionMasked == MotionEvent.ACTION_DOWN                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                    ...                    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.                        //拿到处于重叠状态的所有View并倒序排列,因为我们是要响应最上面的View                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();                        final boolean customOrder = preorderedList == null                                && isChildrenDrawingOrderEnabled();                        final View[] children = mChildren;                        //倒序遍历ViewGroup的所有子View                        for (int i = childrenCount - 1; i >= 0; i--) {                            ...                            //进行事件分发                            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();                                //找到对应的子View,这里之所以是一个链表,是因为有时候会多手指触发事件                                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();                    }                    ...                }            }            //mFirstTouchTarget == null可以理解为没有子View响应事件,需要当前ViewGroup自己处理事件            if (mFirstTouchTarget == null) {                //进行事件分发,传递的子View为null,则代表需要当前ViewGroup来处理事件                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 {                        //判断子View是否需要取消事件                        final boolean cancelChild = resetCancelNextUpFlag(target.child)                                || intercepted;                        //进行事件分发                        if (dispatchTransformedTouchEvent(ev, cancelChild,                                target.child, target.pointerIdBits)) {                            handled = true;                        }                        //子View取消事件                        if (cancelChild) {                            if (predecessor == null) {                                mFirstTouchTarget = next;                            } else {                                predecessor.next = next;                            }                            target.recycle();                            target = next;                            continue;                        }                    }                    predecessor = target;                    target = next;                }            }            ...        }        ...        return handled;    }

 从上面代码可以看出,按下事件是无法被拦截的,其他后续事件都有可能被拦截,当不拦截的时候,都会调用onInterceptTouchEvent方法。当拦截事件时,会给子View传一个取消的事件,且将mFirstTouchTarget设置为null,后续的事件就都不会调用onInterceptTouchEventbuildTouchDispatchChildList这个方法需要注意一下,它主要处理View叠加的问题,具体实现是遍历所有子View然后将这个列表倒序返回,这是为什么尼?如果多个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;    }

 可以看出dispatchTransformedTouchEvent的实现逻辑很简单,就是将事件传递给当前ViewGroup来处理或者让子View继续进行分发,如果要处理事件就返回true,否则返回false。细心一点可以发现mFirstTouchTarget是一个单链表,那这里为什么要使用单链表尼?先来看看TouchTarget这个类,在这个类的开始部分有一段注释,大意就是最多支持32点在屏幕上触发事件,这也就是mFirstTouchTarget是一个单链表的原因了(如果是单点触摸则该链表只有一个元素)。

    /* Describes a touched view and the ids of the pointers that it has captured.     *     * This code assumes that pointer ids are always in the range 0..31 such that     * it can use a bitfield to track which pointer ids are present.     * As it happens, the lower layers of the input dispatch pipeline also use the     * same trick so the assumption should be safe here...     */    private static final class TouchTarget {        private static final int MAX_RECYCLED = 32;        private static final Object sRecycleLock = new Object[0];        private static TouchTarget sRecycleBin;        private static int sRecycledCount;        public static final int ALL_POINTER_IDS = -1; // all ones        // The touched child view.        public View child;        // The combined bit mask of pointer ids for all pointers captured by the target.        public int pointerIdBits;        // The next target in the target list.        public TouchTarget next;        private TouchTarget() {        }        public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {            if (child == null) {                throw new IllegalArgumentException("child must be non-null");            }            final TouchTarget target;            synchronized (sRecycleLock) {                if (sRecycleBin == null) {                    target = new TouchTarget();                } else {                    target = sRecycleBin;                    sRecycleBin = target.next;                     sRecycledCount--;                    target.next = null;                }            }            target.child = child;            target.pointerIdBits = pointerIdBits;            return target;        }        public void recycle() {            if (child == null) {                throw new IllegalStateException("already recycled once");            }            synchronized (sRecycleLock) {                if (sRecycledCount < MAX_RECYCLED) {                    next = sRecycleBin;                    sRecycleBin = this;                    sRecycledCount += 1;                } else {                    next = null;                }                child = null;            }        }    }

 到此ViewGroup的事件分发就分析完毕了,一般在使用过程中都会ViewGroup里放ViewGroup这样嵌套,但流程都是不变的。

来自于Android事件分发机制详解:史上最全面、最易懂

3、View事件事件分发机制

 View的事件分发主要是dispatchTouchEvent里实现。先来看一下该方法的具体实现

    public boolean dispatchTouchEvent(MotionEvent event) {        ...        boolean result = false;        ...        if (onFilterTouchEventForSecurity(event)) {            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {                result = true;            }            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            //从这里判断可以看出onTouch在onTouchEvent之前调用且如果onTouch返回了true,onTouchEvent就不会执行            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }            if (!result && onTouchEvent(event)) {                result = true;            }        }        ...        return result;    }

 View的事件分发实现还是比较简单的,没有ViewGroup那么复杂,从上面可以看出onTouch方法在onTouchEvent之前调用且如果onTouch返回true则onTouchEvent就不会执行。

public boolean onTouchEvent(MotionEvent event) {        ...        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {            switch (action) {                case MotionEvent.ACTION_UP:                    ...                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {                        ...                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {                            // This is a tap, so remove the longpress check                            removeLongPressCallback();                            // Only perform take click actions if we were in the pressed state                            if (!focusTaken) {                                // Use a Runnable and post this rather than calling                                // performClick directly. This lets other visual state                                // of the view update before click actions start.                                if (mPerformClick == null) {                                    mPerformClick = new PerformClick();                                }                                //点击事件                                if (!post(mPerformClick)) {                                    performClick();                                }                            }                        }                        ...                        removeTapCallback();                    }                    mIgnoreNextUpEvent = false;                    break;                 ...                //当ViewGroup拦截事件时,会传递给子View一个ACTION_CANCEL事件                case MotionEvent.ACTION_CANCEL:                    ...                    break;            }            return true;        }        return false;    }

 从上面可以看出点击事件及长按事件优先级别是最低的,如果子类重写了onTouchEvent则该View的点击、长按等事件就无效了,需要在子类里调用主动调用super.onTouchEvent或者performClick

来自于Android事件分发机制详解:史上最全面、最易懂

 到此事件分发机制就总结完毕了,最后来一张总的流程图。

参考

Android事件分发机制详解:史上最全面、最易懂

更多相关文章

  1. 《Android秘籍.第一卷》
  2. 浅谈Android系统启动过程
  3. [转]Android(安卓)DNS 代码分析
  4. Android(安卓)嵌套滑动机制(NestedScrolling)
  5. Android(安卓)Studio引入.so文件的正确姿势 以及调用.so 文件时
  6. Android的Media架构介绍
  7. View事件分发机制
  8. 浅谈Android系统启动过程
  9. Unity3d调用android中的方法

随机推荐

  1. 【转】使用Intent将图片或文字分享到新浪
  2. 使用apktool工具遇到could not decode ar
  3. 【Android(安卓)Studio】AS 使用记录06「
  4. Android(安卓)字体大小怎么自适应不同分
  5. Android(安卓)ListView下拉刷新上拉自动
  6. android RefBase
  7. No resource found that matches the giv
  8. Android绚丽加载效果视图(loading)控件
  9. 深入学习百度地图Android(安卓)SDK v4.0.
  10. Android(安卓)JNI使用(Android(安卓)Stud