1. 程式人生 > >Android查缺補漏(View篇)--事件分發機制

Android查缺補漏(View篇)--事件分發機制

touch事件 滑動沖突 今天 version schema ttr 步驟 isp win

事件分發機制是Android中非常重要的一個知識點,同時也是難點,相信到目前為止很多Android開發者對事件分發機制並沒有一個非常系統的認識,當然也包括博主個人在內。可能在平時的開發工作中我們並沒有意識到事件分發機制起到的作用,其實它是時刻存在的只是我們不知道而已,就像一些滑動沖突、點擊事件之間的沖突等等大多是因為事件分發處理不當導致的。想起了博主大學時做過一個小項目,裏面就出現了滑動沖突的問題,雖然最後在網上一步步看著別人的教程也糊裏糊塗的解決了,但終究不知其所以然,那麽今天就讓我們一起來深入的探索一下事件分發機制吧。

什麽是事件分發機制?

說了半天的事件分發機制那到底是個啥東西呢?我們不要把它想象的那麽高深莫測,不要在心理上給自己設上障礙,其實很容易理解,博主的理解是:簡單來說,事件分發機制就是Android系統對事件傳遞過程規定的一種事件傳遞規則,事件都會按照這個規則進行分發傳遞。

在研究事件分發機制之前,我們先來確定一下分析的步驟,化整為零,各個擊破:

  • 弄清楚分析目標:MotionEvent。
  • 了解三個方法:dispatchTouchEvent(MotionEvent event)、onInterceptTouchEvent(Motion event)、onTouchEvent(MotionEvent event)。
  • MotionEvent事件的傳遞過程
  • 小結

MotionEvent

其實點擊事件的分發過程就是對MotionEvent事件的分發過程,當用戶點擊操作按下後,MotionEvent事件隨即產生並通過一定的規則傳遞到指定的View上,這個傳遞的過程和規則就是事件分發機制。

而點擊操作觸發MotionEvent事件是一個事件流或者說是一個事件序列,其典型的事件類型有如下三種:

  • MotionEvent.ACTION_DOWN:手指剛點下屏幕時觸發此類型。
  • MotionEvent.ACTION_MOVE:手指在屏幕上移動時會多次觸發此類型。
  • MotionEvent.ACTION_UP:手指在屏幕上擡起時觸發此類型。

要特別註意的是,通常情況下一個MotionEvent事件序列包含一個 ACTION_DOWN 若幹個 ACTION_MOVE 和 ACTION_UP 是一個完整的事件序列。(點下去立馬擡起手指時,會只有 ACTION_DOWN 和 ACTION_UP,這也是一個完整的事件序列)

dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent

  • boolean dispatchTouchEvent (MotionEvent event):

分發事件,只要事件能傳遞到當前View就一定會調用此方法,其返回值是一個布爾類型表示是否消耗事件。返回true代表消耗事件,事件流的後續部分還會接著傳遞過來;返回false代表不消耗事件,事件流的後續部分就不再傳遞於此。

  • boolean onInterceptTouchEvent (MotionEvent ev):

此方法表示是否攔截MotionEvent事件,只有ViewGroup類型的控件才有此方法。如果此方法返回true表示攔截事件,事件將傳遞給當前View的onTouchEvent()方法,而不再向其下級的View傳遞。如果此方法返回false表示不攔截事件,事件將傳遞給下級View的dispatchTouchEvent()。

  • boolean onTouchEvent (MotionEvent event):

此方法用來處理MotionEvent,返回值表示是否消耗事件。返回true表示消耗事件,那麽事件流的後續部分還會傳遞過來;返回false表示不消耗事件,事件將交給上級View的onTouchEvent()處理,如果上級View的onTouchEvent()仍然返回false,那麽事件將再交給上級的上級處理,以此類推,如果各級View的onTouchEvent()都不消耗事件,那麽事件最終將交給Activity的onTouchEvent()處理。

上文說了這麽多還是不夠具體,先用流程圖大體說明一個以上三個方法的關系,及調用流程,下文還會結合具體示例詳細說明在事件分發傳遞中各個方法的調用規則。

三者關系大體如下圖:

技術分享圖片

MotionEvent事件傳遞過程

當手指點擊屏幕產生一個Touch事件後,事件按照Activity->Window->View的順序依次傳遞。

首先會傳遞給Activity的dispatchTouchEvent(),在此方法內部會將由Window處理,接著事件會傳遞給根View,根View接收到事件後就會按照事件分發機制去處理事件。

根View在這裏就是一個ViewGroup,它在接受到事件後會調用dispatchTouchEvent(),在此方法內部會通過onInterceptTouchEvent()方法判斷是否攔截事件,如果onInterceptTouchEvent()返回true就表示它要攔截事件,事件將傳遞給當前ViewGroup的onTouchEvent()。如果onInterceptTouchEvent()放回false就表示它不攔截事件,事件將傳給其下級的View,調用下級View的dispatchTouchEvent()。

根View的下級View可能又是一個ViewGroup,如果這樣的話其傳遞流程同根View一樣。無論根View的下級View是不是ViewGroup,如果不攔截事件,最終事件會傳遞到一個純View的控件上。

當一個View(純View控件)接收到事件後,也會調用其dispatchTouchEvent(),然後在此方法內部會調用當前View的onTouchEvent(),如果onTouchEvent()返回true則表示要處理此事件。如果返回false表示不消耗事件,其上級View的onTouchEvent()將被調用,則事件流的後續部分不再傳遞到當前View,在一個事件流中也不會再調用當前View的dispatchTouchEvent()。

接下來通過具體示例來查看事件傳遞的流程:

示例一,默認情況下的事件傳遞流程

創建3個類,一個Activity、一個繼承自LinearLayout的View,一個繼承自Button的View,並重寫他們的dispatchTouchEvent()、onIntercepteTouchEvent()、onTouchEvent(),三個類及布局文件的代碼如下:

  • EventDispatchActivity
/**
 * 事件分發機制測試Activity
 * Created by liuwei on 18/1/5.
 */
public class EventDispatchActivity extends AppCompatActivity {

    private final static String TAG = "Activity";//EventDispatchActivity.class.getSimpleName();

    private EventDispatchTestView edtv_test;
    private EventDispatchLinearLayout edll_test;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_dispatch);
        edtv_test = ViewUtils.find(this, R.id.edtv_test);
        edll_test = ViewUtils.find(this, R.id.edll_test);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        // 被調用時輸出log,event.getAction表示事件的類型,0:ACTION_DOWN,1:ACTION_UP,2:ACTION_MOVE。

        Log.i(TAG, "dispatchTouchEvent: " + event.getAction() + " | 分發事件");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent: " + event.getAction() + " | 是否消耗事件:" + true);
        return super.onTouchEvent(event);
    }
}
  • EventDispatchLinearLayout
/**
 * 事件分發機制測試 ViewGroup
 * Created by liuwei on 18/1/5.
 */
public class EventDispatchLinearLayout extends LinearLayout {

    private final static String TAG = "——Layout";//EventDispatchLinearLayout.class.getSimpleName();


    public EventDispatchLinearLayout(Context context) {
        super(context);
    }

    public EventDispatchLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i(TAG, "dispatchTouchEvent: " + event.getAction() + " | 分發事件");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        Log.i(TAG, "onInterceptTouchEvent: " + event.getAction() + " | 是否攔截:" + false);
        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent: " + event.getAction() + " | 是否消耗事件:" + false);
        return super.onTouchEvent(event);
    }
}
  • EventDispatchTestView
/**
 * 事件分發機制測試 View
 * Created by liuwei on 18/1/5.
 */
public class EventDispatchTestView extends Button {

    private final static String TAG = "————View";//EventDistpatchTestView.class.getSimpleName();

    public EventDispatchTestView(Context context) {
        super(context);
    }

    public EventDispatchTestView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i(TAG, "dispatchTouchEvent: " + event.getAction() + " | 分發事件");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent: " + event.getAction() + " | 是否消耗事件:" + true);
        return super.onTouchEvent(event);
    }
}
  • 布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="cn.codingblock.view.event_dispatch.EventDispatchActivity">

    <cn.codingblock.view.event_dispatch.EventDispatchLinearLayout
        android:id="@+id/edll_test"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#cccccc">

        <cn.codingblock.view.event_dispatch.EventDispatchTestView
            android:id="@+id/edtv_test"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:layout_margin="10dp"
            android:background="#000000"/>

    </cn.codingblock.view.event_dispatch.EventDispatchLinearLayout>

</LinearLayout>

運行代碼,點擊EventDispatchTestView(黑色區域),log輸出如下(log中的數字表示事件的類型,0:ACTION_DOWN,1:ACTION_UP,2:ACTION_MOVE):

01-05 16:58:05.574 23295-23295/cn.codingblock.view I/Activity: dispatchTouchEvent: 0 | 分發事件
01-05 16:58:05.574 23295-23295/cn.codingblock.view I/——Layout: dispatchTouchEvent: 0 | 分發事件
01-05 16:58:05.574 23295-23295/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 0 | 是否攔截:false
01-05 16:58:05.574 23295-23295/cn.codingblock.view I/————View: dispatchTouchEvent: 0 | 分發事件
01-05 16:58:05.574 23295-23295/cn.codingblock.view I/————View: onTouchEvent: 0 | 是否消耗事件:true

01-05 16:58:05.611 23295-23295/cn.codingblock.view I/Activity: dispatchTouchEvent: 2 | 分發事件
01-05 16:58:05.611 23295-23295/cn.codingblock.view I/——Layout: dispatchTouchEvent: 2 | 分發事件
01-05 16:58:05.611 23295-23295/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 2 | 是否攔截:false
01-05 16:58:05.611 23295-23295/cn.codingblock.view I/————View: dispatchTouchEvent: 2 | 分發事件
01-05 16:58:05.611 23295-23295/cn.codingblock.view I/————View: onTouchEvent: 2 | 是否消耗事件:true

01-05 16:58:05.619 23295-23295/cn.codingblock.view I/Activity: dispatchTouchEvent: 1 | 分發事件
01-05 16:58:05.619 23295-23295/cn.codingblock.view I/——Layout: dispatchTouchEvent: 1 | 分發事件
01-05 16:58:05.619 23295-23295/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 1 | 是否攔截:false
01-05 16:58:05.620 23295-23295/cn.codingblock.view I/————View: dispatchTouchEvent: 1 | 分發事件
01-05 16:58:05.620 23295-23295/cn.codingblock.view I/————View: onTouchEvent: 1 | 是否消耗事件:true

由log可以看出ViewGroup的onInterceptTouchEvent方法默認是不攔截事件的,View的onTouchEvent方法默認消耗事件。事件流的ACTION_DOWN類型Motion
Event率先到達View的onTouchEvent方法中,此時onTouchEvent方法返回true,表示要處理事件,所以事件流的後續部分依然經過log中的流程到達了View的onTouchEvent方法中。

示例二、在示例一的基礎上,讓View的onTouchEvent不消耗事件時的傳遞流程

接下來讓上面的EventDispatchTestView的onTouchEvent返回false:

@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.i(TAG, "onTouchEvent: " + event.getAction() + " | 是否消耗事件:" + false);
    return false;//super.onTouchEvent(event);
}

測試log如下:

01-05 18:18:52.545 10771-10771/cn.codingblock.view I/Activity: dispatchTouchEvent: 0 | 分發事件
01-05 18:18:52.545 10771-10771/cn.codingblock.view I/——Layout: dispatchTouchEvent: 0 | 分發事件
01-05 18:18:52.546 10771-10771/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 0 | 是否攔截:false
01-05 18:18:52.546 10771-10771/cn.codingblock.view I/————View: dispatchTouchEvent: 0 | 分發事件
01-05 18:18:52.546 10771-10771/cn.codingblock.view I/————View: onTouchEvent: 0 | 是否消耗事件:false
01-05 18:18:52.546 10771-10771/cn.codingblock.view I/——Layout: onTouchEvent: 0 | 是否消耗事件:false
01-05 18:18:52.547 10771-10771/cn.codingblock.view I/Activity: onTouchEvent: 0 | 是否消耗事件:true

01-05 18:18:52.629 10771-10771/cn.codingblock.view I/Activity: dispatchTouchEvent: 2 | 分發事件
01-05 18:18:52.629 10771-10771/cn.codingblock.view I/Activity: onTouchEvent: 2 | 是否消耗事件:true

01-05 18:18:52.630 10771-10771/cn.codingblock.view I/Activity: dispatchTouchEvent: 1 | 分發事件
01-05 18:18:52.630 10771-10771/cn.codingblock.view I/Activity: onTouchEvent: 1 | 是否消耗事件:true

當View的onTouchEvent不消耗事件時,事件會交給ViewGroup的onTouchEvent方法處理,而從log可以看出ViewGroup的onTouchEvent默認也不消耗事件,所以事件由交給Activity的onTouchEvent方法處理,最終事件流的後續部分不再傳遞給ViewGroup和View,而是直接傳遞給Activity的onTouchEvent處理。

示例三、在示例二的基礎上讓ViewGroup消耗事件

修改EventDispatchLinearLayout的onTouchEvent(),讓其返回true。

@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.i(TAG, "onTouchEvent: " + event.getAction() + " | 是否消耗事件:" + true);
    return true;//super.onTouchEvent(event);
}

測試log如下:

01-05 18:34:53.409 21169-21169/cn.codingblock.view I/Activity: dispatchTouchEvent: 0 | 分發事件
01-05 18:34:53.409 21169-21169/cn.codingblock.view I/——Layout: dispatchTouchEvent: 0 | 分發事件
01-05 18:34:53.409 21169-21169/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 0 | 是否攔截:false
01-05 18:34:53.409 21169-21169/cn.codingblock.view I/————View: dispatchTouchEvent: 0 | 分發事件
01-05 18:34:53.410 21169-21169/cn.codingblock.view I/————View: onTouchEvent: 0 | 是否消耗事件:false
01-05 18:34:53.410 21169-21169/cn.codingblock.view I/——Layout: onTouchEvent: 0 | 是否消耗事件:true

01-05 18:34:53.420 21169-21169/cn.codingblock.view I/Activity: dispatchTouchEvent: 2 | 分發事件
01-05 18:34:53.420 21169-21169/cn.codingblock.view I/——Layout: dispatchTouchEvent: 2 | 分發事件
01-05 18:34:53.420 21169-21169/cn.codingblock.view I/——Layout: onTouchEvent: 2 | 是否消耗事件:true

01-05 18:34:53.470 21169-21169/cn.codingblock.view I/Activity: dispatchTouchEvent: 1 | 分發事件
01-05 18:34:53.470 21169-21169/cn.codingblock.view I/——Layout: dispatchTouchEvent: 1 | 分發事件
01-05 18:34:53.470 21169-21169/cn.codingblock.view I/——Layout: onTouchEvent: 1 | 是否消耗事件:true

此種情況下,事件流的ACTION_DOWN先到達View的onTouchEvent,發現它不消耗事件,繼而返回上級的ViewGroup的onTouchEvent中,發現它要消耗事件,事件流的後續部分就不在傳遞給View,也不在調用ViewGroup的onInterceptTouchEvent方法,因為已經知道View不處理事件,所以沒必要再通過onInterceptTouchEvent方法來判斷了。

示例四、如果在ViewGroup的onInterceptTouchEvent中返回了true攔截了事件,整個事件將不再傳遞給View而是直接交由ViewGroup的onTouchEvent處理。

修改EventDispatchLinearLayout的onInterceptTouchEvent(),讓其返回true。

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    Log.i(TAG, "onInterceptTouchEvent: " + event.getAction() + " | 是否攔截:" + true);
    return true;//super.onInterceptTouchEvent(event);
}

測試log如下:

01-05 19:03:21.788 9733-9733/cn.codingblock.view I/Activity: dispatchTouchEvent: 0 | 分發事件
01-05 19:03:21.789 9733-9733/cn.codingblock.view I/——Layout: dispatchTouchEvent: 0 | 分發事件
01-05 19:03:21.789 9733-9733/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 0 | 是否攔截:true
01-05 19:03:21.789 9733-9733/cn.codingblock.view I/——Layout: onTouchEvent: 0 | 是否消耗事件:true
01-05 19:03:21.819 9733-9733/cn.codingblock.view I/Activity: dispatchTouchEvent: 2 | 分發事件
01-05 19:03:21.819 9733-9733/cn.codingblock.view I/——Layout: dispatchTouchEvent: 2 | 分發事件
01-05 19:03:21.819 9733-9733/cn.codingblock.view I/——Layout: onTouchEvent: 2 | 是否消耗事件:true
01-05 19:03:21.877 9733-9733/cn.codingblock.view I/Activity: dispatchTouchEvent: 1 | 分發事件
01-05 19:03:21.877 9733-9733/cn.codingblock.view I/——Layout: dispatchTouchEvent: 1 | 分發事件
01-05 19:03:21.877 9733-9733/cn.codingblock.view I/——Layout: onTouchEvent: 1 | 是否消耗事件:true

示例五、給View綁定OnTouchListener和OnClickListener監聽器。

在EventDispatchActivity的onCreate()方法裏面添加如下代碼,並將EventDispatchLinearLayout和EventDispatchTestView的各方法的返回值都還原成示例一中的狀態。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_event_dispatch);
    edtv_test = ViewUtils.find(this, R.id.edtv_test);
    edll_test = ViewUtils.find(this, R.id.edll_test);
    
    edtv_test.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // 為了log顯示的層次更加清晰,這裏的TAG使用View的TAG
            Log.i("————View", "onTouch: 返回 " + false);
            return false;
        }
    });

    edtv_test.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 為了log顯示的層次更加清晰,這裏的TAG使用View的TAG
            Log.i("————View", "onClick: ");
        }
    });
}

測試log如下:

01-06 19:35:07.563 6737-6737/cn.codingblock.view I/Activity: dispatchTouchEvent: 0 | 分發事件
01-06 19:35:07.563 6737-6737/cn.codingblock.view I/——Layout: dispatchTouchEvent: 0 | 分發事件
01-06 19:35:07.563 6737-6737/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 0 | 是否攔截:false
01-06 19:35:07.563 6737-6737/cn.codingblock.view I/————View: dispatchTouchEvent: 0 | 分發事件
01-06 19:35:07.563 6737-6737/cn.codingblock.view I/————View: onTouch: 返回 false
01-06 19:35:07.563 6737-6737/cn.codingblock.view I/————View: onTouchEvent: 0 | 是否消耗事件:true

01-06 19:35:07.573 6737-6737/cn.codingblock.view I/Activity: dispatchTouchEvent: 2 | 分發事件
01-06 19:35:07.573 6737-6737/cn.codingblock.view I/——Layout: dispatchTouchEvent: 2 | 分發事件
01-06 19:35:07.573 6737-6737/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 2 | 是否攔截:false
01-06 19:35:07.573 6737-6737/cn.codingblock.view I/————View: dispatchTouchEvent: 2 | 分發事件
01-06 19:35:07.574 6737-6737/cn.codingblock.view I/————View: onTouch: 返回 false
01-06 19:35:07.574 6737-6737/cn.codingblock.view I/————View: onTouchEvent: 2 | 是否消耗事件:true

01-06 19:35:07.673 6737-6737/cn.codingblock.view I/Activity: dispatchTouchEvent: 1 | 分發事件
01-06 19:35:07.674 6737-6737/cn.codingblock.view I/——Layout: dispatchTouchEvent: 1 | 分發事件
01-06 19:35:07.674 6737-6737/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 1 | 是否攔截:false
01-06 19:35:07.674 6737-6737/cn.codingblock.view I/————View: dispatchTouchEvent: 1 | 分發事件
01-06 19:35:07.674 6737-6737/cn.codingblock.view I/————View: onTouch: 返回 false
01-06 19:35:07.674 6737-6737/cn.codingblock.view I/————View: onTouchEvent: 1 | 是否消耗事件:true
01-06 19:35:07.704 6737-6737/cn.codingblock.view I/————View: onClick: 

然後再上面修改代碼,讓onTouch()方法消耗事件,也就是返回true,再觀察log:

edtv_test.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 為了log顯示的層次更加清晰,這裏的TAG使用View的TAG
        Log.i("————View", "onTouch: 返回 " + false);
        return false;
    }
});

log如下:

01-07 11:03:55.411 2757-2757/cn.codingblock.view I/Activity: dispatchTouchEvent: 0 | 分發事件
01-07 11:03:55.412 2757-2757/cn.codingblock.view I/——Layout: dispatchTouchEvent: 0 | 分發事件
01-07 11:03:55.412 2757-2757/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 0 | 是否攔截:false
01-07 11:03:55.412 2757-2757/cn.codingblock.view I/————View: dispatchTouchEvent: 0 | 分發事件
01-07 11:03:55.412 2757-2757/cn.codingblock.view I/————View: onTouch: 返回 true

01-07 11:03:55.542 2757-2757/cn.codingblock.view I/Activity: dispatchTouchEvent: 2 | 分發事件
01-07 11:03:55.542 2757-2757/cn.codingblock.view I/——Layout: dispatchTouchEvent: 2 | 分發事件
01-07 11:03:55.542 2757-2757/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 2 | 是否攔截:false
01-07 11:03:55.542 2757-2757/cn.codingblock.view I/————View: dispatchTouchEvent: 2 | 分發事件
01-07 11:03:55.542 2757-2757/cn.codingblock.view I/————View: onTouch: 返回 true

01-07 11:03:55.560 2757-2757/cn.codingblock.view I/Activity: dispatchTouchEvent: 1 | 分發事件
01-07 11:03:55.560 2757-2757/cn.codingblock.view I/——Layout: dispatchTouchEvent: 1 | 分發事件
01-07 11:03:55.560 2757-2757/cn.codingblock.view I/——Layout: onInterceptTouchEvent: 1 | 是否攔截:false
01-07 11:03:55.560 2757-2757/cn.codingblock.view I/————View: dispatchTouchEvent: 1 | 分發事件
01-07 11:03:55.560 2757-2757/cn.codingblock.view I/————View: onTouch: 返回 true

從log中我們可以看出:

  • 為View綁定的OnTouchListener中的onTouch()方法是優先於View的onTouchEvent()方法執行的。如果在onTouch()消耗了事件(返回true),那麽事件將不在傳遞給onTouchEvent()方法,最終也不會調用onClick()方法。
  • 為View綁定的OnClickListener中的onClick()方法優先級最低,是在整個事件流結束後才會被調用,也就是需要經過手指的按下--擡起這個過程才會觸發onClick()方法。

小結

為了更好的理解,可以把事件流看成是一隊人,把ACTION_DOWN類型看做探路人,探路人按規定的線路先走一遍,直到走到View的onTouchEvent這裏,如果onTouchEvent返回true,可理解成此路通,後續部隊可以過來。如果返回false,可以理解成此路不通,然後探路人再到Layout(ViewGroup)的onTouchEvent中問路通不通,如果通的話後續部隊就不用再去View那裏了,直接到ViewGroup這來就可以了。而如果ViewGroup這裏路也不通,那麽探路人就只能去Activity的onTouchEvent那裏了,後續部隊也直接去Activity的onTouchEvent這裏就可以了。


最後想說的是,本系列文章為博主對Android知識進行再次梳理,查缺補漏的學習過程,一方面是對自己遺忘的東西加以復習重新掌握,另一方面相信在重新學習的過程中定會有巨大的新收獲,如果你也有跟我同樣的想法,不妨關註我一起學習,互相探討,共同進步!

參考文獻:

  • 《Android開發藝術探索》

Android查缺補漏(View篇)--事件分發機制