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

来源:https://www.cnblogs.com/codingblock/archive/2018/01/14/8227647.html
-Advertisement-
Play Games

事件分發機制是Android中非常重要的一個知識點,同時也是難點,相信到目前為止很多Android開發者對事件分發機制並沒有一個非常系統的認識,當然也包括博主個人在內。可能在平時的開發工作中我們並沒有意識到事件分發機制起到的作用,其實它是時刻存在的只是我們不知道而已,就像一些滑動衝突、點擊事件之間的 ...


事件分發機制是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開發藝術探索》

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本章目錄 1.1 Shell,Bash介紹 1.2 內部命令與外部命令 1.3 命令別名 1.4 命令歷史 1.5 Bash快捷鍵 1.1 Shell,Bash介紹 ◆Shell:Linux系統的用戶界面,提供了用戶與內核進行交互操作的一種介面。它接收用戶輸入的命令並把它送入內核去執行。 ◆Bash ...
  • 前言: Linux一般將文件可存取的身份分為三個類別,分別是 owner/group/others,根據許可權劃分,每個目錄都可以擁有相對身份的-rwx[可讀可寫可執行] 使用者與群組: Owner :文件擁有者,如jane擁有此文件所屬權,可對該文件進行相應許可權操作。 Group :文件擁有組,如一 ...
  • 概述 由於目前生產環境的mysql資料庫分佈在兩台伺服器,若從單一主從來看,配置很簡單,但是需要將兩台伺服器的資料庫同步到一臺從庫上面,需要進行更多配置和註意事項。多源複製有兩種方案,Binlog+Position和GTID,我們採用的是第一種方案Binlog+Position進行的多源複製。 安裝 ...
  • select * from scott.dept;select * from scott.emp; --dual 是 Oracle提供的一個虛表select length('hello') from dual; --常用函數--lower把大寫轉小寫 upper把小寫轉大寫select * from ...
  • --註意:rownum和rowid只有Oracle有,其它資料庫是不支持的 select * from scott.dept;--查詢的結果稱為'結果集' --rownum 偽列 '結果集'中產生的序列--在下麵的結果集中deptno為20的rownum為2select rownum,deptno, ...
  • 資料查找https://www.cnblogs.com/tdws/p/5836122.html https://www.cnblogs.com/lori/p/5794454.html private static readonly ConnectionMultiplexer _sentinel; s ...
  • 在看 carthage 時,其中需要配置一個 copy-frameworks 腳本,此腳本特殊的地方,需要手動填寫 Input Files 和 Output Files.一個一個填寫,很糟心~ ...
  • xUtils是基於Afinal開發的目前功能比較完善的一個Android開源框架,最近又發佈了xUtil3.0,在增加新功能的同時又提高了框架的性能。它的功能很強大,但是有時候我們只需要其中的一些功能,如果把整個xUtils引進去沒什麼必要。 下麵我們就講講如何自定義小型的xUtils,只有兩個功能 ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...