Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android源碼分析(二):View的事件分發機制探析

Android源碼分析(二):View的事件分發機制探析

編輯:關於Android編程

Android應用開發時,自定義控件時少不了和View的觸摸點擊事件打交道。針對View的事件分發原理,也看過網上的一些博客,但是看歸看,看了之後時間一長就又忘記了,因此為了更好地記憶理解,痛下決心自己寫一篇關於View事件分發原理的博客。

1 示例代碼看起

1.1 重寫Button,代碼如下

public class MyButton extends Button{

    private static final String TAG = "MyButton";

    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(TAG, "dispatchTouchEvent: event.action = "+event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent: event.action = "+event.getAction());
        return super.onTouchEvent(event);
    }
}

代碼裡面只是重寫了2個方法,然後在裡面打印了一些log,用於看下觸摸事件的行走流程。

1.2 布置xml布局




    

很簡單,在LinearLayout裡面放置一個我們剛才自定義的MyButton就可以了。

1.3 在Activity的代碼設定

        MyButton myButton = (MyButton) findViewById(R.id.touchTest);

        myButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "onClick: myButton clicked!");
            }
        });

        myButton.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.d(TAG, "onTouch: myButton touched! event.action = "+event.getAction());
                return false;
            }
        });

1.4 測試示例效果,當精准的點擊MyButton的時候,打印結果如下

這裡寫圖片描述

從圖中看得到,當我們點擊MyButton的時候,執行順序是
dispatchTouchEvent(…)—>onTouch(…)—>onTouchEvent(…)—>onClick(…),那麼我們就按這個順序改變一下返回值再看下打印結果如何。

首先進入的是boolleab dispatchTouchEvent(…)函數,它返回的是boolean類型的值,那我們改下返回值,看看結果如何,改動如下:

  @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(TAG, "dispatchTouchEvent: event.action = "+event.getAction());
        return false;
    }

就是把返回值改為false,再看下打印皆若如何呢?

08-27 14:05:38.323 303-303/? D/MyButton: dispatchTouchEvent: event.action = 0

只打印了一行數據,就是ACTION_DOWN的時候,具體什麼原因,待會進入分析流程的時候具體的研究一下。

那要是把返回值改為true,結果如何呢?

08-27 14:14:41.646 3558-3558/? D/MyButton: dispatchTouchEvent: event.action = 0
08-27 14:14:41.687 3558-3558/? D/MyButton: dispatchTouchEvent: event.action = 1

dispatchTouchEvent(..)函數方法中,action事件全部走齊了,但是其余的函數並沒有進入,所以我們猜測其余的事件函數有可能是在dispatchTouchEvent(..)函數做的調用。

好了,到這裡,我們再看一個信息,就是boolean onTouch(…)函數,這個是在myButton設置OnTouchListener事件監聽的時候的回調方法,一開始的時候,我們返回的是false,那我們改為true試試結果如何呢?

08-27 14:19:57.185 4720-4720/? D/MyButton: dispatchTouchEvent: event.action = 0
08-27 14:19:57.186 4720-4720/? D/MyButton: onTouch: myButton touched! event.action = 0
08-27 14:19:57.269 4720-4720/? D/MyButton: dispatchTouchEvent: event.action = 1
08-27 14:19:57.269 4720-4720/? D/MyButton: onTouch: myButton touched! event.action = 1

結果是onTouchEvent(…)與onClick(…)函數並沒有執行,這個時候我們猜測,當onTouch(…)返回true時,阻止了事件的傳遞,所以上面的2個函數就收不到事件。

接下來再看boolean onTouchEvent(…)方法,也是一樣的,具有boolean類型的返回值,同樣的,改變一下返回值,看下具體的結果如何

當返回值為false時,打印結果如下:

08-27 14:09:03.576 1570-1570/? D/MyButton: dispatchTouchEvent: event.action = 0
08-27 14:09:03.576 1570-1570/? D/MyButton: onTouch: myButton touched! event.action = 0
08-27 14:09:03.576 1570-1570/? D/MyButton: onTouchEvent: event.action = 0

打印結果也令人匪夷所思,只是打印了ACTION_DOWN的時候,以後的所有action事件並沒有執行,為什麼呢?先在這打個大大的問號。

當返回值為true時,打印結果如下:

08-27 14:11:44.671 2412-2412/? D/MyButton: dispatchTouchEvent: event.action = 0
08-27 14:11:44.671 2412-2412/? D/MyButton: onTouch: myButton touched! event.action = 0
08-27 14:11:44.671 2412-2412/? D/MyButton: onTouchEvent: event.action = 0
08-27 14:11:44.729 2412-2412/? D/MyButton: dispatchTouchEvent: event.action = 1
08-27 14:11:44.729 2412-2412/? D/MyButton: onTouch: myButton touched! event.action = 1
08-27 14:11:44.729 2412-2412/? D/MyButton: onTouchEvent: event.action = 1

越來越接近剛開始的打印結果了,這次除了onClick()事件裡面的log沒有輸出,其余的都有log信息了。

總結一下如下:

(1)當 dispatchTouchEvent(…)函數不管返回具體的true or false,事件都不會分發到下一個函數中,並且為true的時候,才會監聽到下一個action事件。

(2)當設置了OnTouchListener監聽之後,在OnTouch(…)函數體中,返回true,則阻止事件的往下傳遞,後面的事件函數不在執行

(3)當onTouchEvent(…)函數不管直接true or false,事件都不會執行到onClick(…)裡面,並且同dispatchTouchEvent(…)一樣,只有返回true的時候,才能在監聽下一個action事件。

2 View觸摸事件傳遞源碼分析

我們就先按順序來看,當我們點擊myButton的時候,首先走的是dispatchTouchEvent(…)函數,那麼我們就從這個函數入手,一步步往下看。

一番繼承關系的查找之後,我們終於在View裡面發現了這個函數的代碼,進入函數內部來看下它到底干了啥事。

 public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        //首先判斷當前View是否可以接收此次action事件
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            //當前View沒有接收此次action事件的條件,返回false
            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();
        }

        //判斷是否被覆蓋了,沒有則返回true
        if (onFilterTouchEventForSecurity(event)) {
            //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;
    }

有些關鍵點已經有所注釋,下面看一下這個最是我們需要的部分

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語句的判斷,先是判斷 li 是否為null,當然不會為null,然後就是li.mOnTouchListener是否為null,那它是怎麼賦值的呢,搜索這個關鍵詞看看呢,你發現這麼一段代碼

 public void setOnTouchListener(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
    }

對了,就是在我們設置觸摸監聽的時候賦值的,當你調用setOnTouchListener(…)的時候就已經賦值了,下面就是對當前View是否ENABLE的位運算,默認是true,然後就是重點了,判斷li.mOnTouchListener.onTouch(this, event)的返回值,當返回true時,進入if語句內部,result=true,那麼接下來的if (!result && onTouchEvent(event)){…}就不會再執行了,否則就會執行if語句了,並且進入到onTouchEvent(…)函數內部,我們再進去看一下,它到底做了些什麼工作。

裡面做的工作有些復雜,但是具體的就是對各種action事件的處理,其中在ACTION_UP中,你會發現這麼一句話

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

看名字也應該想到是什麼了,是否要處理click事件,進performClick()看看呢,

 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);
        return result;
    }

看到沒,在if語句判斷之內,我們看到了onClick(…)方法的回調。很驚訝有沒有,onClick(…)是在onTouchEvent(…)裡面調用的。

3 分析結論

(1)當View被觸摸的時候,首先走的是dispatchTouchEvent方法

(2)在dispatchTouchEvent方法中先執行onTouch方法,然後執行onTouchEvent方法,最後再onTouchEvent裡面執行onClick方法

(3)當view控件為disable的或者mOnTouchListener=null或者onTouch方法返回false,才會調用onTouchEvent方法,並且dispatchTouchEvent返回值與onTouchEvent相同

(4)當dispatchTouchEvent進行事件分發的時候,只有上個事件返回true,才能接受下一個action事件

遺留問題待解決:

當dispatchTouchEvent返回false時候,下次事件為什麼不能觸發,在進行下一篇對ViewGroup事件分發的時候看看是否能找得到答案呢?

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved