大学IT网 - 最懂大学生的IT学习网站! QQ资料交流群:367606806
当前位置:大学IT网 > Android技巧 > Android touch事件处理流程

Android touch事件处理流程(1)

关键词:流程事件Androidtouch  阅读(1911) 赞(20)

[摘要]本文是对Android touch事件处理流程的讲解,对学习Android编程技术有所帮助,与大家分享。

  前面我们看了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也就不会被触发;另外还有个区别就是onTouch里可以
// 收到每次touch事件,而onClickListener只是在up事件到来时触发。 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.
// 在前面down事件的时候我们延迟显示view的pressed状态 setPressed(true); // 直到up事件到来的时候才显示pressed状态 } 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.
             // 也就是说在touch mode下,不take focus的view第一次点击的时候才会触发onClick事件 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: // 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; // 设置PREPRESSED标志位 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字段 } } }
«上一页123下一页»


相关评论