Android touch事件处理流程 -- MotionEvent

  前面我们看了key事件的处理流程,相信大家对此已经有了新的认识,这篇文章我打算带领大家来看看稍微复杂些的touch

事件的处理流程。说它复杂是因为key事件本身就key down,up,long pressed这几种,而touch事件支持多指触摸,给人的

感觉好像同时在发生多个touch事件一样,所以要处理的手指是多个而不是固定的一个,逻辑上当然也就复杂些了。不过本质

上还都是down、up、long pressed,touch的话还有move事件。接下来让我们直接进入本文的正题。

  我们选择直接从touch事件被分发到view层次结构的根节点DecorView开始说起,代码如下:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Callback cb = getCallback(); // 和key事件类似,优先派发给Callback,否则直接交给view层次结构处理
        return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
                : super.dispatchTouchEvent(ev);
    }

在前面介绍key事件的处理流程时我们说过,Callback接口的实现一般是Activity或Dialog,这里我们看看Activity的实现:

    /**
     * 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) {
            onUserInteraction(); // 一开始down事件的时候,调用此callback
        }
        if (getWindow().superDispatchTouchEvent(ev)) { // 交给了此Activity关联的window处理,实际是派发到view层次结构中
            return true; // 如果被window处理掉了(消费了)则返回true,
        }
        return onTouchEvent(ev); // 否则调用Activity自己的onTouchEvent进行最后的处理
    }

紧接着,我们看看交给window处理的逻辑,这里我们不绕弯了,直接看最终调用到的代码:

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event); // window直接交给了DecorView处理
    }

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event); // DecorView直接交给了其super,即ViewGroup处理
    }

  接下来ViewGroup对touch事件的处理才是重中之重,不过在看容器类对touch事件处理之前,我觉得有必要先来看看单一的View类

对touch事件的处理,毕竟简单些,而且ViewGroup自身也依赖这些实现,代码如下:

    /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    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)) { // 先在ENABLED状态下尝试调用onTouch方法
                return true; // 如果被onTouch处理了,则直接返回true
            }
            // 从这里我们可以看出,当你既设置了OnTouchListener又设置了OnClickListener,那么当前者返回true的时候,            // onTouchEvent没机会被调用,当然你的OnClickListener也就不会被触发
            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false; // 上面的都没处理,则返回false
    }

    /**
     * Implement this method to handle touch screen motion events.
     * <p>
     * If this method is used to detect click actions, it is recommended that
     * the actions be performed by implementing and calling
     * {@link #performClick()}. This will ensure consistent system behavior,
     * including:
     * <ul>
     * <li>obeying click sound preferences
     * <li>dispatching OnClickListener calls
     * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
     * accessibility features are enabled
     * </ul>
     *
     * @param event The motion event.
     * @return True if the event was handled, false otherwise.
     */
    public boolean onTouchEvent(MotionEvent event) { // View对touch事件的默认处理逻辑
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) { // DISABLED的状态下
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false); // 复原如果之前是PRESSED状态
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn‘t respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE || // CLICKABLE或LONG_CLICKABLE的view标记为对事件处理了,
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); // 只不过是以do nothing的方式处理了。
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) { // 如果有TouchDelegate的话,优先交给它处理
                return true; // 处理了返回true,否则接着往下走
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE || // View能对touch事件响应的前提要么是CLICKABLE要么是LONG_CLICKABLE
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP: // UP事件                    // 如果外围有可以滚动的parent的话,当按下时会设置这个标志位
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // 按下了或者预按下了
                        // take focus if we don‘t have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;                        // 这行代码就是我们上篇博客中说的,设置了FocusableInTouchMode后,View在点击的时候就会                        // 尝试requestFocus(),并将focusToken设置为true
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus(); // 能进来这个if,一般都会返回true
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true); // 设置为按下状态,会刷新View的状态
                       }

                        if (!mHasPerformedLongPress) { // 如果没有长按发生的话
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback(); // 移除长按callback

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) { // 看到没,focusTaken是false才会进去下面的if语句
                                // 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)) { // 如果post失败了,则直接调用performClick()方法
                                    performClick(); // 这2行代码会触发onClickListener
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState(); // unset按下状态的
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we‘re inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) { // 如果是在可以滚动的container里面的话
                        mPrivateFlags |= PFLAG_PREPRESSED; // 设置pre按下标志位
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        } // 延迟pressed feedback
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true); // 否则直接显示pressed feedback
                        checkForLongClick(0); // 并启动长按检测
                    }
                    break;

                case MotionEvent.ACTION_CANCEL: // 针对CANCEL事件的话,恢复各种状态,移除各种callback
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;

                case MotionEvent.ACTION_MOVE: // MOVE事件
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) { // 如果移动到view的边界之外了,
                        // Outside button
                        removeTapCallback(); // 则取消Tap callback,这样当你松手的时候onClick不会被触发
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // 当已经是按下状态的话
                            // Remove any future long press/tap checks
                            removeLongPressCallback(); // 移除长按callback

                            setPressed(false); // 恢复按下状态
                        }
                    }
                    break;
            }
            return true; // 最后返回true,表示对touch事件处理过了,消费了
        }

        return false; // 既不能单击也不能长按的View,返回false,表示不处理touch事件
    }

其中涉及到好多小的方法,都非常简单,不再一一例出。

  在开始介绍ViewGroup对touch事件的处理之前,我们还得先看看ViewGroup的一个内部类TouchTarget,因为它描述的就是被

touch的view和touch的手指相关的信息,代码如下:

    /* 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() {
        }
        // 看到这个有没有很眼熟?是的Message里也有类似的实现,我们在之前介绍Message的文章里详细地分析过
        public static TouchTarget obtain(View child, int pointerIdBits) {
            final TouchTarget target;
            synchronized (sRecycleLock) {
                if (sRecycleBin == null) { // 没有可以回收的目标,则new一个返回
                    target = new TouchTarget();
                } else {
                    target = sRecycleBin; // 重用当前的sRecycleBin
                    sRecycleBin = target.next; // 更新sRecycleBin指向下一个
                     sRecycledCount--; // 重用了一个,可回收的减1
                    target.next = null; // 切断next指向
                }
            }
            target.child = child; // 找到合适的target后,赋值
            target.pointerIdBits = pointerIdBits;
            return target;
        }

        public void recycle() { // 基本是obtain的反向过程
            synchronized (sRecycleLock) {
                if (sRecycledCount < MAX_RECYCLED) {
                    next = sRecycleBin; // next指向旧的可回收的头
                    sRecycleBin = this; // update旧的头指向this,表示它自己现在是可回收的target(第一个)
                    sRecycledCount += 1; // 多了一个可回收的
                } else {
                    next = null; // 没有next了
                }
                child = null; // 清空child字段
            }
        }
    }

现在是时候来直面ViewGroup的dispatchTouchEvent()方法了,代码如下:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) { // view没有被遮罩,一般都成立
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) { // 一堆touch事件(从按下到松手)中的第一个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(); // 作为新一轮的开始,reset所有相关的状态
            }

            // Check for interception.
            final boolean intercepted; // 检查是否要拦截
            if (actionMasked == MotionEvent.ACTION_DOWN // down事件
                    || mFirstTouchTarget != null) { // 或者之前的某次事件已经经由此ViewGroup派发给children后被处理掉了
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) { // 只有允许拦截才执行onInterceptTouchEvent方法
                    intercepted = onInterceptTouchEvent(ev); // 默认返回false,不拦截
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false; // 不允许拦截的话,直接设为false
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                // 在这种情况下,actionMasked != ACTION_DOWN && mFirstTouchTarget == null
                // 第一次的down事件没有被此ViewGroup的children处理掉(要么是它们自己不处理,要么是ViewGroup从一
                // 开始的down事件就开始拦截),则接下来的所有事件
                // 也没它们的份,即不处理down事件的话,那表示你对后面接下来的事件也不感兴趣
                intercepted = true; // 这种情况下设置ViewGroup拦截接下来的事件
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL; // 此touch事件是否取消了

            // Update list of touch targets for pointer down, if needed.
            // 是否拆分事件,3.0(包括)之后引入的,默认拆分
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null; // 接下来ViewGroup判断要将此touch事件交给谁处理
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) { // 没取消也不拦截,即是个有效的touch事件
                if (actionMasked == MotionEvent.ACTION_DOWN // 第一个手指down
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) // 接下来的手指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; // 不满足这2个条件直接跳过,看下一个child
                            }

                            // child view能receive touch事件而且touch坐标也在view边界内

                            newTouchTarget = getTouchTarget(child);// 查找child对应的TouchTarget
                            if (newTouchTarget != null) { // 比如在同一个child上按下了多跟手指
                                // 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; // newTouchTarget已经有了,跳出for循环
                            }

                            resetCancelNextUpFlag(child);
                            // 将此事件交给child处理
                            // 有这种情况,一个手指按在了child1上,另一个手指按在了child2上,以此类推
                            // 这样TouchTarget的链就形成了
                            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();
                                // 如果处理掉了的话,将此child添加到touch链的头部
                                // 注意这个方法内部会更新 mFirstTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true; // down或pointer_down事件已经被处理了
                                break; // 可以退出for循环了。。。
                            }
                        }
                    }

                    // 本次没找到newTouchTarget但之前的mFirstTouchTarget已经有了
                    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;
                        }
                        // while结束后,newTouchTarget指向了最初的TouchTarget
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
            // 非down事件直接从这里开始处理,不会走上面的一大堆寻找TouchTarget的逻辑
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // 没有children处理则派发给自己处理
                // 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) { // 遍历TouchTarget形成的链表
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true; // 已经处理过的不再让其处理事件
                    } else {
                        // 取消child标记
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // 如果ViewGroup从半路拦截了touch事件则给touch链上的child发送cancel事件
                        // 如果cancelChild为true的话
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true; // TouchTarget链中任意一个处理了则设置handled为true
                        }
                        if (cancelChild) { // 如果是cancelChild的话,则回收此target节点
                            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) {
                // 取消或up事件时resetTouchState
                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; // 返回处理的结果
    }

接下来我们看看关于拦截的方法,代码如下:

    /**
     * Implement this method to intercept all touch screen motion events.  This
     * allows you to watch events as they are dispatched to your children, and
     * take ownership of the current gesture at any point.
     *
     * <p>Using this function takes some care, as it has a fairly complicated
     * interaction with {@link View#onTouchEvent(MotionEvent)
     * View.onTouchEvent(MotionEvent)}, and using it requires implementing
     * that method as well as this one in the correct way.  Events will be
     * received in the following order:
     *
     * <ol>
     * <li> You will receive the down event here.
     * <li> The down event will be handled either by a child of this view
     * group, or given to your own onTouchEvent() method to handle; this means
     * you should implement onTouchEvent() to return true, so you will
     * continue to see the rest of the gesture (instead of looking for
     * a parent view to handle it).  Also, by returning true from
     * onTouchEvent(), you will not receive any following
     * events in onInterceptTouchEvent() and all touch processing must
     * happen in onTouchEvent() like normal.
     * <li> For as long as you return false from this function, each following
     * event (up to and including the final up) will be delivered first here
     * and then to the target‘s onTouchEvent().
     * <li> If you return true from here, you will not receive any
     * following events: the target view will receive the same event but
     * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
     * events will be delivered to your onTouchEvent() method and no longer
     * appear here.
     * </ol>
     *
     * @param ev The motion event being dispatched down the hierarchy.
     * @return Return true to steal motion events from the children and have
     * them dispatched to this ViewGroup through onTouchEvent().
     * The current target will receive an ACTION_CANCEL event, and no further
     * messages will be delivered here.
     */
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false; // 其默认直接返回false,表示不拦截
    }

系统的ScrollView重载了此方法,以便检测是滚动的时候直接拦截此事件自己处理而不是一味的下发给其children。最后为了完整性,

我这里把TouchTarget相关的方法都列一下以便大家参考:

    /**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    /**
     * Resets the cancel next up flag.
     * Returns true if the flag was previously set.
     */
    private static boolean resetCancelNextUpFlag(View view) {
        if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
            view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
            return true;
        }
        return false;
    }

    /**
     * Clears all touch targets.
     */
    private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }

    /**
     * Cancels and clears all touch targets.
     */
    private void cancelAndClearTouchTargets(MotionEvent event) {
        if (mFirstTouchTarget != null) {
            boolean syntheticEvent = false;
            if (event == null) {
                final long now = SystemClock.uptimeMillis();
                event = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
                syntheticEvent = true;
            }

            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
                resetCancelNextUpFlag(target.child);
                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
            }
            clearTouchTargets();

            if (syntheticEvent) {
                event.recycle();
            }
        }
    }

    /**
     * Gets the touch target for specified child view.
     * Returns null if not found.
     */
    private TouchTarget getTouchTarget(View child) {
        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            if (target.child == child) {
                return target;
            }
        }
        return null;
    }

    /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    private TouchTarget addTouchTarget(View child, int pointerIdBits) {
        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

    /**
     * Removes the pointer ids from consideration.
     */
    private void removePointersFromTouchTargets(int pointerIdBits) {
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            if ((target.pointerIdBits & pointerIdBits) != 0) {
                target.pointerIdBits &= ~pointerIdBits;
                if (target.pointerIdBits == 0) {
                    if (predecessor == null) {
                        mFirstTouchTarget = next;
                    } else {
                        predecessor.next = next;
                    }
                    target.recycle();
                    target = next;
                    continue;
                }
            }
            predecessor = target;
            target = next;
        }
    }

    // 类似从链表中删除某个特定的节点
    private void cancelTouchTarget(View view) {
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            if (target.child == view) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();

                final long now = SystemClock.uptimeMillis();
                MotionEvent event = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
                view.dispatchTouchEvent(event);
                event.recycle();
                return;
            }
            predecessor = target;
            target = next;
        }
    }

  当递归调用一层层向上返回的时候,如果view层次结构没有处理某个touch事件则最终交给了Activity.onTouchEvent方法,这里

我们一并看下:

    /**
     * Called when a touch screen event was not handled by any of the views
     * under it.  This is most useful to process touch events that happen
     * outside of your window bounds, where there is no view to receive it.
     *
     * @param event The touch screen event being processed.
     *
     * @return Return true if you have consumed the event, false if you haven‘t.
     * The default implementation always returns false.
     */
    public boolean onTouchEvent(MotionEvent event) { // 没啥需要多说的,好好看方法doc吧
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

  至此所有touch事件的处理流程就分析完毕了,最后也推荐大家看看园友的一篇同主题文章:

http://www.cnblogs.com/frydsh/archive/2012/11/08/2760408.html

这篇文章主要是结论性的,适合有个宏观上的认识,作为开始你可以先看看,非常容易理解。当然如果你对内部的实现原理很感兴趣,

最终还是要去看源码来获得更深层次的认识,enjoy。。。

Android touch事件处理流程 -- MotionEvent

时间: 2024-10-18 14:04:08

Android touch事件处理流程 -- MotionEvent的相关文章

Android按键事件处理流程 -- KeyEvent

刚接触Android开发的时候,对touch.key事件的处理总是一知半解,一会是Activity里的方法,一会是各种View 中的,自己始终不清楚到底哪个在先哪个在后,总之对整个处理流程没能很好的把握.每次写这部分代码的时候都有些心虚, 因为我不是很清楚什么时候.以什么样的顺序被调用,大都是打下log看看,没问题就算ok了.但随着时间流逝,这种感觉一直 折磨着我.期间也在网上搜索了相关资料,但总感觉不是那么令人满意.自打开始研究Android源码起,这部分内容的分析早就 被列在我的TODO l

Cocoa Touch事件处理流程--响应者链

Cocoa Touch事件处理流程--响应者链 一.事件分类 对于IOS设备用户来说,他们操作设备的方式主要有三种:触摸屏幕.晃动设备.通过遥控设施控制设备.对应的事件类型有以下三种: 1.触屏事件(Touch Event) 2.运动事件(Motion Event) 3.远端控制事件(Remote-Control Event) 今天以触屏事件(Touch Event)为例,来说明在Cocoa Touch框架中,事件的处理流程.首先不得不先介绍响应者链这个概念: 二.响应者链(Responder

Android touch事件的派发流程

http://blog.csdn.net/xyz_lmn/article/details/12517911 通过流程图了解touch事件派发过程. http://blog.csdn.net/stonecao/article/details/6759189 从代码的层面分析,尽管目前代码已经变化了,但是作者的分析对Android touch事件派发流程的理解还是很有帮助的. http://www.2cto.com/kf/201504/388625.html 通过实例了解Button的touch事件

Android热插拔事件处理详解

一.Android热插拔事件处理流程图 Android热插拔事件处理流程如下图所示: 二.组成 1. NetlinkManager: 全称是NetlinkManager.cpp位于Android 4.x 源码位置/system/vold/NetlinkManager.cpp.该类的主要通过引用NetlinkHandler类中的onEvent()方法来接收来自内核的事件消息,NetlinkHandler位于/system/vold/NetlinkHandler.cpp. 2. VolumeMana

Android的Touch事件处理机制

Android的Touch事件处理机制比较复杂,特别是在考虑了多点触摸以及事件拦截之后. Android的Touch事件处理分3个层面:Activity层,ViewGroup层,View层. 首先说一下Touch事件处理的几条基本规则. 1.如果在某个层级没有处理ACTION_DOWN事件,那么该层就再也收不到后续的Touch事件了直到下一次ACTION_DOWN事件. 说明:a.某个层级没有处理某个事件指的是它以及它的子View都没有处理该事件. b.这条规则不适用于Activity层(它是顶

Android Touch事件传递机制解析

开篇语:最近程序在做一个小效果,要用到touch,结果整得云里面雾里的,干脆就好好把android touch机制好好看了一下,呵呵.. android系统中的每个ViewGroup的子类都具有下面三个和TouchEvent处理密切相关的方法: 1)public boolean dispatchTouchEvent(MotionEvent ev)          这个方法用来分发TouchEvent 2)public boolean onInterceptTouchEvent(MotionEv

Android Touch事件传递机制解析 (推荐)

最近新闻列表里的下拉 down up  move 等等让我十分头疼 ,无意间看到了一篇非常不错的帖子,转载如下: 开篇语:最近程序在做一个小效果,要用到touch,结果整得云里面雾里的,干脆就好好把android touch机制好好看了一下,呵呵.. android系统中的每个ViewGroup的子类都具有下面三个和TouchEvent处理密切相关的方法: 1)public boolean dispatchTouchEvent(MotionEvent ev)          这个方法用来分发T

Android Touch事件分发过程

虽然网络上已经有非常多关于这个话题的优秀文章了,但还是写了这篇文章,主要还是为了加强自己的记忆吧,自己过一遍总比看别人的分析要深刻得多.那就走起吧. 简单演示样例 先看一个演示样例 : 布局文件 : <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id=&q

React-Native系列Android——Touch事件原理及状态效果

Native原生相比于Hybrid或H5最大优点是具有流畅和复杂的交互效果,触摸事件便是其中重要一项,包括点击(Click).长按(LongClick).手势(gesture)等. 以最简单常见的点击(Click)为例,Native组件可以自定义selector,使得被点击的组件具有动态效果,Android 5.0以上甚至可以有涟漪效果(Material Design).而这些在Hybrid或H5中很难实现,很多时候区分它们与原生最简单的方法就是检验点击交互效果. React-Native的强大