Android事件分發機制[-View-] 源碼級

向那些曾經沒法跨越的鴻溝敬上----吾王已至git


開篇先明確幾點
1.有三東西挺長的,又長得挺像,看着晃眼且心煩,文中如下簡寫爲:
|-- 分發 dispatchTouchEvent = d16t       
|-- 截斷 onInterceptTouchEvent = o19t    
|-- 消費 onTouchEvent = o10t             


2.事件分發機制的參與者與各自擁有的回調方法:
|-- 灰色 Activity:  o10t    d16t        
|-- 紫色 ViewGroup: o10t    d16t   o19t
|-- 橙色 單體View:  o10t    d16t

3.MotionEvent的幾種常見時間
|-- MotionEvent.ACTION_DOWN = 0;    按下
|-- MotionEvent.ACTION_UP = 1;      擡起
|-- MotionEvent.ACTION_MOVE=2;      移動
|-- MotionEvent.ACTION_CANCEL=3;    取消       
複製代碼

圖例這裏給出來,瞭解一下
下面表示:觸點在Activity上,
按下事件(即0)觸發了 d16t(即dispatchTouchEvent) 
而後按下事件(即0)觸發了 o10t( 即onTouchEvent)
擡起事件(即1)觸發了 d16t(即dispatchTouchEvent) 
而後擡起事件(即1)觸發了 o10t( 即onTouchEvent)  ---我想這樣應該表述的淋漓盡致了
複製代碼

圖例.png


1、正常狀況下的事件傳遞

0.自定義兩個測試View

佈局樹以下:如今自定義一個單體View,一個ViewGroupgithub

測試佈局.png

/**
 * 做者:張風捷特烈<br/>
 * 時間:2019/2/21/021:9:11<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:事件測試Activity
 */
public class EventActivity extends AppCompatActivity {
    private static final String TAG = "EventTest";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_test);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent:--" + event.getAction() + " --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍");
        return super.onTouchEvent(event);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "dispatchTouchEvent:--" + ev.getAction() + "--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍");
        return super.dispatchTouchEvent(ev);
    }
}

/**
 * 做者:張風捷特烈<br/>
 * 時間:2019/2/21/021:9:06<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:事件測試View單體
 */
public class SonView extends View {
    private static final String TAG = "EventTest";
    public SonView(Context context) {
        super(context);
    }
    public SonView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setBackgroundColor(0xffE58F46);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e(TAG, "SonView--dispatchTouchEvent:" + event.getAction() + "-- ††††††††††††††††††††††††††††††††††††††††");
        return super.dispatchTouchEvent(event);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "SonView--onTouchEvent:--" + event.getAction() + "-- ††††††††††††††††††††††††††††††††††††††††");
        return super.onTouchEvent(event);
    }
}

/**
 * 做者:張風捷特烈<br/>
 * 時間:2019/2/21/021:9:06<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:事件測試ViewGroup
 */
public class FatherViewGroup extends FrameLayout {
    private static final String TAG = "EventTest";
    public FatherViewGroup(Context context) {
        super(context);
    }
    public FatherViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        setBackgroundColor(0xff9869B7);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "FatherViewGroup--dispatchTouchEvent:--" + ev.getAction() + " --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ ");
        return super.dispatchTouchEvent(ev);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TAG, "FatherViewGroup--onInterceptTouchEvent:--" + ev.getAction() + " -- ☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ ");
        return super.onInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "FatherViewGroup--onTouchEvent:--" + event.getAction() + " --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ ");
        return super.onTouchEvent(event);
    }
}
複製代碼

1.在兩View以外點擊

圖例部分說過了,這裏不廢話了編程

點擊Activity.png

點空白處.png

2019-02-21 10:13:25.773 : dispatchTouchEvent:--0--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 10:13:25.775 : onTouchEvent:--0 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 10:13:25.805 : dispatchTouchEvent:--1--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 10:13:25.805 : onTouchEvent:--1 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
複製代碼

2.點擊紫色ViewGroup

在Activity的d16t時,以後,事件到了ViewGroup裏bash

點擊ViewGroup_.png

點擊ViewGroup.png

2019-02-21 10:36:34.040 : dispatchTouchEvent:--0--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 10:36:34.040 : FatherViewGroup--dispatchTouchEvent:--0 --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 10:36:34.040 : FatherViewGroup--onInterceptTouchEvent:--0 -- ☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯  
2019-02-21 10:36:34.041 : FatherViewGroup--onTouchEvent:--0  --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 10:36:34.041 : onTouchEvent:--0 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 10:36:34.064 : dispatchTouchEvent:--1--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 10:36:34.064 : onTouchEvent:--1 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
複製代碼

3.點擊橙色View單體

在ViewGroup的o18t時,以後,事件到了View裏,
這裏感受像是...一根鏈條。萬一哪塊掉鏈子了會怎麼樣?微信

點擊子View.png

點擊子View_.png

2019-02-21 10:46:15.721 : dispatchTouchEvent:--0--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 10:46:15.722 : FatherViewGroup--dispatchTouchEvent:--0 --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 10:46:15.722 : FatherViewGroup--onInterceptTouchEvent:--0 -- ☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯  
2019-02-21 10:46:15.722 : SonView--dispatchTouchEvent:0-- ††††††††††††††††††††††††††††††††††††††††
2019-02-21 10:46:15.723 : SonView--onTouchEvent:--0--  ††††††††††††††††††††††††††††††††††††††††
2019-02-21 10:46:15.724 : FatherViewGroup--onTouchEvent:--0  --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 10:46:15.726 : onTouchEvent:--0 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 10:46:15.772 : dispatchTouchEvent:--1--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 10:46:15.773 : onTouchEvent:--1 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
複製代碼

2、掉鏈子測試

1.d16tdispatchTouchEvent :

口號:寧爲玉碎不爲瓦全,我得不到的,你也別想獲得!dom


1.1.Activity的d16t掉鏈子:寧爲玉碎不爲瓦全

Activity說:哥不爽了,事件不給大家玩!d16t 返回 false. 說完把event扔了
這鏈子一斷...就算點擊View單體,事件也傳不下去了。ide

掉鏈子.png

---->[EventActivity#dispatchTouchEvent]------------------------------
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.e(TAG, "dispatchTouchEvent:--" + ev.getAction() + "--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍");
    return false;
}


2019-02-21 11:01:19.109 29054-29054/com.toly1994.analyzer E/EventTest: dispatchTouchEvent:--0--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 11:01:19.159 29054-29054/com.toly1994.analyzer E/EventTest: dispatchTouchEvent:--1--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
複製代碼

1.2.ViewGroup的d16t掉鏈子

如今回到初始狀況,Activity把事件給了ViewGroup玩,ViewGroup說:爺也不爽了!而後一丟
測試中看來:在ViewGroup的d16t返回false以後會回調Activity的o10T佈局

ViewGroup掉鏈子.png

---->[FatherViewGroup#dispatchTouchEvent]--------------------
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    Log.e(TAG, "FatherViewGroup--dispatchTouchEvent:--" + ev.getAction() + " --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ ");
    return false;
}

2019-02-21 11:27:07.487: dispatchTouchEvent:--0--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 11:27:07.488: FatherViewGroup--dispatchTouchEvent:--0 --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 11:27:07.489: onTouchEvent:--0 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 11:27:07.538: dispatchTouchEvent:--1--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 11:27:07.538: onTouchEvent:--1 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
複製代碼

1.3.View單體的d16t掉鏈子

如今回到初始狀況post

View掉鏈子.png

---->[SonView#dispatchTouchEvent]--------------------
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    Log.e(TAG, "SonView--dispatchTouchEvent:" + event.getAction() + "-- ††††††††††††††††††††††††††††††††††††††††");
    return false;
}

2019-02-21 11:28:32.080: dispatchTouchEvent:--0--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 11:28:32.081: FatherViewGroup--dispatchTouchEvent:--0 --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 11:28:32.081: FatherViewGroup--onInterceptTouchEvent:--0 -- ☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯  
2019-02-21 11:28:32.081: SonView--dispatchTouchEvent:0-- ††††††††††††††††††††††††††††††††††††††††
2019-02-21 11:28:32.082: FatherViewGroup--onTouchEvent:--0  --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 11:28:32.083: onTouchEvent:--0 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 11:28:32.121: dispatchTouchEvent:--1--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 11:28:32.121: onTouchEvent:--1 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
複製代碼
1.4 小結一下d16t

從測試上看到的結論:測試

dispatchTouchEvent的做用在於分發事件,源頭出發不走,後面都白忙活 
只要發走了,後面哪裏斷掉,就會觸發上一級的 o10t 
複製代碼

2.o18t對事件的影響

做爲ViewGroup獨有的方法,onInterceptTouchEvent能夠決定事件是否打斷

打斷.png

---->[FatherViewGroup#onInterceptTouchEvent]--------------------
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.e(TAG, "FatherViewGroup--onInterceptTouchEvent:--" + ev.getAction() + " -- ☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ ");
    return true;
}

2019-02-21 11:54:46.648 : dispatchTouchEvent:--0--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 11:54:46.649 : FatherViewGroup--dispatchTouchEvent:--0 --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 11:54:46.649 : FatherViewGroup--onInterceptTouchEvent:--0 -- ☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯  
2019-02-21 11:54:46.650 : FatherViewGroup--onTouchEvent:--0  --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 11:54:46.652 : onTouchEvent:--0 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 11:54:46.697 : dispatchTouchEvent:--1--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 11:54:46.697 : onTouchEvent:--1 --卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
複製代碼

3.o10t掉鏈子
3.1:當ViewGroup消費事件

默認狀況下由最頂層消費事件,這裏只有讓當ViewGroup消費事件,事件就不會往下傳遞了

當ViewGroup消費事件.png

---->[FatherViewGroup#onTouchEvent]--------------------
 @Override
 public boolean onTouchEvent(MotionEvent event) {
     Log.e(TAG, "FatherViewGroup--onTouchEvent:--" + event.getAction() + " --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ ");
     return true;
 }

2019-02-21 12:09:06.605 11221-11221/com.toly1994.analyzer E/EventTest: dispatchTouchEvent:--0--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 12:09:06.606 11221-11221/com.toly1994.analyzer E/EventTest: FatherViewGroup--dispatchTouchEvent:--0 --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 12:09:06.606 11221-11221/com.toly1994.analyzer E/EventTest: FatherViewGroup--onInterceptTouchEvent:--0 -- ☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯  
2019-02-21 12:09:06.606 11221-11221/com.toly1994.analyzer E/EventTest: SonView--dispatchTouchEvent:0-- ††††††††††††††††††††††††††††††††††††††††
2019-02-21 12:09:06.607 11221-11221/com.toly1994.analyzer E/EventTest: SonView--onTouchEvent:--0--  ††††††††††††††††††††††††††††††††††††††††
2019-02-21 12:09:06.607 11221-11221/com.toly1994.analyzer E/EventTest: FatherViewGroup--onTouchEvent:--0  --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 12:09:06.621 11221-11221/com.toly1994.analyzer E/EventTest: dispatchTouchEvent:--1--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 12:09:06.621 11221-11221/com.toly1994.analyzer E/EventTest: FatherViewGroup--dispatchTouchEvent:--1 --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 12:09:06.622 11221-11221/com.toly1994.analyzer E/EventTest: FatherViewGroup--onTouchEvent:--1  --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
複製代碼

3.2:當子View消費事件

子View消費.png

---->[SonView#onTouchEvent]--------------------
@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.e(TAG, "SonView--onTouchEvent:--" + event.getAction() + "-- ††††††††††††††††††††††††††††††††††††††††");
    return true;
}

2019-02-21 12:10:05.682 : dispatchTouchEvent:--0--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 12:10:05.683 : FatherViewGroup--dispatchTouchEvent:--0 --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 12:10:05.683 : FatherViewGroup--onInterceptTouchEvent:--0 -- ☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯  
2019-02-21 12:10:05.683 : SonView--dispatchTouchEvent:0-- ††††††††††††††††††††††††††††††††††††††††
2019-02-21 12:10:05.683 : SonView--onTouchEvent:--0--  ††††††††††††††††††††††††††††††††††††††††
2019-02-21 12:10:05.724 : dispatchTouchEvent:--1--卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍卍
2019-02-21 12:10:05.724 : FatherViewGroup--dispatchTouchEvent:--1 --☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯ 
2019-02-21 12:10:05.724 : FatherViewGroup--onInterceptTouchEvent:--1 -- ☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯☯  
2019-02-21 12:10:05.724 : SonView--dispatchTouchEvent:1-- ††††††††††††††††††††††††††††††††††††††††
2019-02-21 12:10:05.724 : SonView--onTouchEvent:--1--  ††††††††††††††††††††††††††††††††††††††††
複製代碼

3.3 小結一下o10t

從測試上看到的結論:

onTouchEvent的做用在於消費事件,消費以後不會再往下傳遞
這裏仍是想強調一下[d16t]和[o10t]的區別,一者消費,一者分發
因爲消費在分發以前,消費是不會阻礙分發的,但分發會影響消費

老婆(Activity):給你100塊當作一月生活費,這叫分發dispatchTouchEvent,生活費至關MotionEvent
你(ViewGroup):拿到這100塊,能夠決定是否把這100塊給兒子(View)當生活費(dispatchTouchEvent)
當決定給了,可是中途還能夠經過onInterceptTouchEvent打斷給的念頭...

|--兒子拿到錢花了,錢就沒了
|--兒子拿到錢不花,就被老爸沒收了,老爸花了,錢就沒了
|--兒子拿到錢不花,就被老爸沒收了,老爸沒花,就被老婆沒收了,老婆花了,錢就沒了  
差很少就是這個理,默認下你和兒子都是不敢花的,但都在手裏過了一遍
後面人有沒有得花,首先要看老婆給不給,不給,後面就沒戲了...

複製代碼

上面若是理清楚,使用方面應該就沒問題了


2、源碼查看

1.Activity和ViewGroup中的dispatchTouchEvent

dispatchTouchEvent.png

---->[Activity#dispatchTouchEvent]----------------------------
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
--->if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
--->return onTouchEvent(ev);
}

|---這裏能夠很清楚的看清Activity的onTouchEvent回調的時機即
    getWindow().superDispatchTouchEvent(ev)  返回false時觸發
|---getWindow這裏就不廢話了,前面都說過,是PhoneWindow對象,直接進

---->[PhoneWindow#dispatchTouchEvent]----------------------------
|--- 調用的是mDecor的方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

---->[PhoneWindow$DecorView#superDispatchTouchEvent]----------------------------
|--- 這皮球踢得...mDecor是DecorView對象,繼承自FrameLayout,在上一輩即是ViewGroup
|--- 因此這樣看來,Activity的dispatchTouchEvent的本質也是ViewGroup觸發的
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

---->[ViewGroup#superDispatchTouchEvent]----------------------------
|---這個方法大概100多行
/**
 * {@inheritDoc}
 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
        ...
--->    final boolean intercepted;//是否打斷
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
--->            intercepted = onInterceptTouchEvent(ev);//觸發onInterceptTouchEvent,默認false
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }

        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

        // Check for cancelation.
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
--->    if (!canceled && !intercepted) {//未被取消而且未被打斷
            ...
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                ...
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    ...
                    //這裏接了一下mChildren,mChildren的初始化及添加操做稍後分析
                    final View[] children = mChildren;
        --->        for (int i = childrenCount - 1; i >= 0; i--) {//這裏開始遍歷全部的孩子
                        final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i) : i;
                        final View child = (preorderedList == null)
                                ? children[childIndex] : preorderedList.get(childIndex);
                        ...
                        newTouchTarget = getTouchTarget(child);
                        ...
                        resetCancelNextUpFlag(child);
                        //dispatchTransformedTouchEvent會觸發子View的d16t方法
                --->    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // Child wants to receive touch within its bounds.---孩子想要在他的範圍內接受觸摸
                            ...
               --->        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        ...
        // Dispatch to touch targets. ---- 分發到多個觸摸點?...
        if (mFirstTouchTarget == null) {//mFirstTouchTarget在addTouchTarget方法中被賦值
--->        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            //下面遍歷target的鏈表,取出鏈表中的View執行dispatchTransformedTouchEvent
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
        --->        if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }
        ....
         return handled;
}

---->[ViewGroup#dispatchTransformedTouchEvent]----------------------------
|--總的來講就是觸發super或是child的d16t方法,ViewGroup的super是誰?
|--答:View 。child 若是不爲空走本身的d16t,若是child仍是ViewGroup,就再走一圈上面的
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
...
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {//取消時
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
--->        handled = super.dispatchTouchEvent(event);//孩子爲空觸發super的d16t,
        } else {
            handled = child.dispatchTouchEvent(event);//孩子不爲空,觸發d16t
        }
        event.setAction(oldAction);
        return handled;
    }

    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
    --->        handled = super.dispatchTouchEvent(event);
            } else {
                ...
    --->        handled = child.dispatchTouchEvent(event);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }

    if (child == null) {
--->    handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        ...
--->    handled = child.dispatchTouchEvent(transformedEvent);
    }
    transformedEvent.recycle();
    return handled;
}


---->[ViewGroup#addTouchTarget]----------------------------
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
    TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

---->[TouchTarget]----------------------------
|---描述觸摸的View以及手指的id們
|---TouchTarget是ViewGroup的一個內部類,看樣子是一個單鏈表
|---有一個next的TouchTarget字段,鏈表承載數據類型是View
private static final class TouchTarget {
    ...
    public View child;
    public TouchTarget next;
    ...
}

---->[ViewGroup$TouchTarget#obtain]----------------------------
|-- 這裏很明顯是第一個元素出鏈表
public static TouchTarget obtain(View child, int pointerIdBits) {
    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;
}

---->[ViewGroup#onInterceptTouchEvent]---------------------------
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}
複製代碼

2.View的dispatchTouchEvent

看這個鬆了一口氣...

public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.isTargetAccessibilityFocus()) {
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        event.setTargetAccessibilityFocus(false);
    }
    boolean result = false;
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
         //注意,這四個條件知足,直接返回true,就不會觸發onTouchEvent了
         //而且會觸發mOnTouchListener的onTouch回調
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
--->            && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
--->    if (!result && onTouchEvent(event)) {//這裏觸發了View的onTouchEvent!!!,感動...
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }
    return result;
}
複製代碼

4.關於onInterceptTouchEvent

也許你沒注意,剛纔已經被消滅了...它只是用來控制的boolean而已

---->[ViewGroup#onInterceptTouchEvent]---------------------------
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}
複製代碼

5.關於:onTouchEvent
---->[Activity#onTouchEvent]-----------------------
|--少得可憐,基本上就是false了
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

---->[ViewGroup#onTouchEvent]-----------------------
|--咦,個人onTouchEvent呢?居然沒有!!!!
|--這讓我挺意外,也就是ViewGroup徹底使用View的onTouchEvent

---->[View#onTouchEvent]-----------------------
|--就這個有點說頭...這裏追蹤一下點擊事件
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    if ((viewFlags & ENABLED_MASK) == DISABLED) {//表示不可用
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        return (((viewFlags & CLICKABLE) == CLICKABLE //直接滾回
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
    }

    if (mTouchDelegate != null) {//有了觸摸代理
        if (mTouchDelegate.onTouchEvent(event)) {//執行代理人的onTouchEvent
            return true;////直接滾回
        }
    }
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {//知足上面一堆狀況
        switch (action) {//咱們最熟悉的 switch (action) 
            case MotionEvent.ACTION_UP://擡起時
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    boolean focusTaken = false;
                    ...
                    if (prepressed) {//被按下
                        setPressed(true, x, y);//標true
                   }
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // This is a tap, so remove the longpress check--這是一個短點擊,因此去掉長按檢查
                        removeLongPressCallback();
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();//執行點擊
                            }
                        }
                    }
                   ...
                break;
            case MotionEvent.ACTION_DOWN:
                ...
                break;
            case MotionEvent.ACTION_CANCEL:
                ...
                break;

            case MotionEvent.ACTION_MOVE:
                ...
                break;
        }

        return true;
    }

    return false;
}

---->[View#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;
}

複製代碼
番外:ViewGroup對子View的添加
---->[ViewGroup對子View的添加]---------------------------
private View[] mChildren;
mChildren = new View[ARRAY_INITIAL_CAPACITY];//默認12

---->[ViewGroup#addInArray]---------------------------
private void addInArray(View child, int index) {
    View[] children = mChildren;
    final int count = mChildrenCount;
    final int size = children.length;
    if (index == count) {
        if (size == count) {
            mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
            System.arraycopy(children, 0, mChildren, 0, size);
            children = mChildren;
        }
--->    children[mChildrenCount++] = child;
    } else if (index < count) {
        if (size == count) {
            mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
            System.arraycopy(children, 0, mChildren, 0, index);
            System.arraycopy(children, index, mChildren, index + 1, count - index);
            children = mChildren;
        } else {
            System.arraycopy(children, index, children, index + 1, count - index);
        }
--->    children[index] = child;
        mChildrenCount++;
        if (mLastTouchDownIndex >= index) {
            mLastTouchDownIndex++;
        }
    } else {
        throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
    }
}

|--- 關於ViewGroup添加View,追蹤了一下:
addView(一參)-->addView(兩參)-->addView(三參)-->addViewInner-->addInArray

複製代碼

小結:

總的來講源碼看下來,感受view事件分發機制也並不像我想像中的那麼難
在自定義View中至多也就是ViewGroup+子View的觸摸事件協調,Activity通常不參和
Activity的事件分發實質上是DecorView的事件分發,因此都是View家的,Activity打了波醬油
最後我想強調一下d10to18t返回false時的不一樣點,這也是我之前比較迷惑的:見下圖

打斷.png

ViewGroup掉鏈子.png


最後用一個小例子來表達一下經過o18t來控制事件響應行爲

若是父View左滑了,那麼就不截斷子View的滑動(孩子消費)
若是父View右滑了,那麼就截斷子View的滑動(本身消費)

/**
 * 做者:張風捷特烈<br/>
 * 時間:2019/2/21/021:9:06<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:事件測試ViewGroup
 * 左滑動  ViewGroup 響應
 * 右滑動  View單體 響應
 */
public class FatherViewGroup extends FrameLayout {
    private static final String TAG = "EventTest";
    boolean isLeft = false;
    
    public FatherViewGroup(Context context) {
        super(context);
    }
    public FatherViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        setBackgroundColor(0xff9869B7);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return isLeft;
    }
    
    float x;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                x = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                float curX = event.getX();
                float dx = curX - this.x;
                isLeft = dx < 0;
                x = curX;
                setBackgroundColor(ColUtils.randomColor());
                break;
        }
        return true;
    }
}

/**
 * 做者:張風捷特烈<br/>
 * 時間:2019/2/21/021:9:06<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:事件測試View單體
 */
public class SonView extends View {
    private static final String TAG = "EventTest";
    public SonView(Context context) {
        super(context);
    }
    public SonView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setBackgroundColor(0xffE58F46);
    }
    float x;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        setBackgroundColor(ColUtils.randomColor());
        return true;
    }
}
複製代碼

後記:捷文規範

1.本文成長記錄及勘誤表
項目源碼 日期 附錄
V0.1-- 2018-2-21

發佈名:Android事件分發機制[源碼級]
捷文連接:juejin.im/post/5c6e5f…

2.更多關於我
筆名 QQ 微信
張風捷特烈 1981462002 zdl1994328

個人github:github.com/toly1994328
個人簡書:www.jianshu.com/u/e4e52c116…
個人掘金:juejin.im/user/5b42c0…
我的網站:www.toly1994.com

3.聲明

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持

icon_wx_200.png
相關文章
相關標籤/搜索