1. 程式人生 > >Android筆記-View中的dispatchTouchEvent和onTouchEvent原始碼解析

Android筆記-View中的dispatchTouchEvent和onTouchEvent原始碼解析

View中的dispatchTouchEvent比較簡單,因為view只能是最底層的,所以沒有向下傳遞的機制,也沒有攔截機制。
其dispatchTouchEvent方法原始碼如下:

 /**
     * 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 the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { return
false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0
); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }

我們看關鍵部分

//過濾觸控事件以應用安全策略
 if (onFilterTouchEventForSecurity(event)) {
            //handleScrollBarDragging返回的是false,暫時忽略程式碼
            ...
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;//如果li.mOnTouchListener.onTouch消耗事件
            }
            //如果li.mOnTouchListener.onTouch沒有消耗事件
            if (!result && onTouchEvent(event)) {
                result = true;//如果onTouchEvent消耗事件
            }
        }

上面的程式碼中我們看到:onFilterTouchEventForSecurity方法是用來方法過濾觸控事件以應用安全策略。一般返回的是true。程式碼如下:

  /**
     * Filter the touch event to apply security policies.
     *
     * @param event The motion event to be filtered.
     * @return True if the event should be dispatched, false if the event should be dropped.
     *
     * @see #getFilterTouchesWhenObscured
     */
    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        //noinspection RedundantIfStatement
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }

繼續看程式碼:

//noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;//如果li.mOnTouchListener.onTouch消耗事件
            }
            //如果li.mOnTouchListener.onTouch沒有消耗事件
            if (!result && onTouchEvent(event)) {
                result = true;//如果onTouchEvent消耗事件
            }

這邊是判斷:如果給view設定了onTouchListener,則會優先執行onTouchListener的
onTouch方法,如果onTouch返回true,表示事件已被消費,不會執行下面的onTouchEvent方法,如果onTouch返回false,表示事件沒有被消費,會執行下面的onTouchEvent方法。

可在上一篇文章中知道:如果dispatchTouchEvent會返回result為(true),即通知上層控制元件事件已被消費,當上層控制元件接受到子view的dispatchTouchEvent返回true時候,不會執行自己的onTouchEvent方法。

我們再看onTouchEvent方法是如何消耗事件的:

/**
     * 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) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        //是否可以點選,由當前view的CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE決定
        //如果有一個為true則為true
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        //判斷是否DISABLED,可以看出DISABLED不會消耗事件。
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;//這邊看出DISABLED不會消耗事件。
        }
        //searchview中用到,這邊暫時不管
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        //     可以點選或者指示此檢視可以在懸停或長按時顯示工具提示。返回true
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    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;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        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, x, y);
                        }

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

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

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

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        checkForLongClick(0, x, y);
                        break;
                    }

                    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) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }

            return true;
        }

        return false;
    }

上程式碼簡單總結為:
1. 如果view的clickable為TRUE或者longclickable為true,則會消費事件。
2. view的disable屬性不會影響view的事件是否消費,仍然由clickable決定。
3. 在ACTION_UP中會觸發performClick方法,該方法會判斷是否設定了點選事件,如果有則會觸發點選事件。原始碼如下:


    /**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

這個方法雖然有返回boolean型別的值,但是這個boolean值只能表示點選事件是否觸發,在onTouchEvent裡面沒有用到。所以該方法也不會影響view中事件是否被消費。

至此整個事件分發機制解析完畢。

相關推薦

Android筆記-ViewdispatchTouchEventonTouchEvent原始碼解析

View中的dispatchTouchEvent比較簡單,因為view只能是最底層的,所以沒有向下傳遞的機制,也沒有攔截機制。 其dispatchTouchEvent方法原始碼如下: /** * Pass the touch screen

事件分發系列—ViewdispatchTouchEventonTouchEvent分析

dispatchTouchEvent 話不多說直接上原始碼 /** * Pass the touch screen motion event down to the target view, or this * view if

androidwindowwindowManager原始碼分析(android-api-23)

一、前言 在android中window無處不在,如activity、dialog、toast等。它是view所依附的載體,每個window都對應於一個View和一個ViewRootImpl。ViewRootImpl就是Window和view的連線紐帶。windowMana

Android重寫view時onAttachedToWindow () onDetachedFromWindow ()

intent action efault tor null lock 相對 ext.get pre 在重寫View的時候,會遇到這兩個方法 protected void onAttachedToWindow() Description copied from

Java7、8HashMapConcurrentHashMap原始碼閱讀

首先來看下HashMap的類繼承結構: public class HashMap extends AbstractMap<K,V> impement Map<K,V>,Coloneable,Serializable{ } 可以看出HashMap實現了Map介面。其裡面的方法都是

Linux系統下python學習筆記——Linux檔案目錄常用命令詳解

一、檢視目錄內容 ls命令說明: 英文單詞list的簡寫,功能為列出目錄的內容,是使用者最常用的命令字義    Linux下檔案和目錄的特點: Linux檔案或目錄名稱最長可以有256個字元 以 . 開頭的檔案為隱藏檔案,需要用-a引數才能顯示(all

Android PriorityQueuePriorityBlockingQueue原始碼解析

尊重原創,轉載請標明出處   http://blog.csdn.net/abcdef314159 原始碼:\sources\Android-25 PriorityQueue通過名字也可以看的出來,是優先佇列,PriorityBlockingQueue是優先阻

詳細解析AndroidView事件分發機制 附帶原始碼分析

前言 在Android中,事件分發機制是一塊很重要的知識點,掌握這個機制能幫你在平時的開發中解決掉很多的View事件衝突問題,這個問題也是面試中問的比較多的一個問題了,今天就來總結下這個知識點。 事件分發機制 事件分發原因 Android中頁面上的View是以

Mysql viewmergetemportary

merge: 引用檢視的語句和定義檢視的語句合併生成一條新的查詢語句,查詢實體表 temportary: 定義檢視時建立一張臨時表,搜尋時查詢的是該臨時表 通常不用定義,mysql 自己選擇 詳細參考 View

第014講:ScalaMapHashMap原始碼剖析及程式碼實踐(從1000個程式碼案例學習人工智慧大資料實戰)

第014講:Scala中Map和HashMap原始碼剖析及程式碼實踐/** * A generic trait for immutable maps. Concrete classes have to provide * functionality for the abs

Android的訊息處理機制:Message、HandlerheLooper原始碼解析

android的訊息處理有三個核心類:Looper,Handler和Message。其實還有一個Message Queue(訊息佇列),但是MQ被封裝到Looper裡面了,我們不會直接與MQ打交道,因此我沒將其作為核心類。下面一一介紹: 執行緒的魔法師 Looper Loo

錯誤筆記:JDBCStatementPreparedStatement對於Date型別寫入資料庫問題

今天寫JDBC使用Statement執行sql語句向oracle資料庫中插入Date型別資料時,遇到了一些問題: 首先Date類在java.util下和java.sql下都有,他們在控制檯上的答應分別是: java.util.date: java.sql.date:

android自動化測試hierarchyvieweruiautomatorviewer獲取控制元件資訊的方式比對

android自動化新人一個,目前研究PC端獲取android裝置的控制元件資訊有哪些方式。多多指教!         android的目錄tools下有2個自帶的工具hierarchyviewer和uiautomatorviewer,開啟後,如下所示:

android開發—FragmentonCreateView()onActivityCreated()的區別

在編寫Fragment時,在onCreateView()方法中啟動了一個初始化自定義View的方法 initView(),但是除錯時就崩潰,列印日誌發現是這裡出了問題,就將這個方法放到了onActivityCreated()方法中啟動,就沒有再崩潰過,不明白為什

Android的ViewRootImpl類原始碼解析

ViewRoot目前這個類已經沒有了,是老版本中的一個類,在Android2.2以後用ViewRootImpl代替ViewRoot,對應於ViewRootImpl.java,他是連結WindowManager和DecorView的紐帶,另外View的繪製也是通過ViewRootImpl來完成的。 它的主要作

android自動化測試hierarchyvieweruiautomatorviewer獲取控制元件資訊的方式比對(2)

       在上一篇我簡單的瞭解了一下hierarchyviewer和uiautomatorviewer,如需訪問,點選以下連結:        通過對hierarchyview的原始碼分析,我嘗試用java寫了一個測試工具,該測試工具簡單的實現了連線ViewServe

Android的gradlecompileprovided的區別

1.compile 'com.android.support:appcompat-v7:23.4.0' 2.provided 'com.squareup.dagger:dagger-compiler:1.2.1'(網路下載)   provided fileTree(incl

Spark學習筆記 --- SparkMapFlatMap轉換的區別

wechat:812716131 ------------------------------------------------------ 技術交流群請聯絡上面wechat ----------------------------------------------

android ndk開發charunsigned char問題

官方說明 看看cflags中 fsigned-char的說明: -fsigned-char — Allows the type char in the native libraries to be signed, like signed char. Each kind

android 開發 View _14 MotionEvent事件處理詳解,與實踐自定義滑動條View

MotionEvent MotionEvent物件是與使用者觸控相關的時間序列,該序列從使用者首次觸控式螢幕幕開始,經歷手指在螢幕表面的任何移動,直到手指離開螢幕時結束。手指的初次觸控(ACTION_DOWN操作),滑動(ACTION_MOVE操作)和擡起(ACTION