Android事件傳遞機制

上善若水發表於2016-07-18

感覺這裡是Android中最麻煩,最繞的一個知識點了。博主心裡現在心裡有千萬只草泥馬在奔騰,著實不想整理啊,然而也就讓它們稍微奔騰一會吧…… 
   
我們先通過一個例子,來了解一下什麼是事件的傳遞:

總統對省長說:我要吃紅燒魚 
省長對市長說:你做個紅燒魚 
市長對縣長說:你做個紅燒魚 
縣長對農民說:你做個紅燒魚 
……(農民做呀做,沒做出來) 
農民說:我盡力了,但真心不會做呀,饒了我吧 
縣長說:你個笨蛋,下次不找你了,看我來做 
……(縣長做呀做,沒做出來) 
縣長對市長說:我盡力了,非常抱歉,我不會做 
市長說:你個廢物,要你何用,只能我自己來做了 
……(市長做呀做,做成功了) 
市長對省長說:紅燒魚做好了 
省長說:不錯,下次有事還找你 
省長對總統說:紅燒魚做好了 
總統說:不錯,下次有事還找你


總統對省長說:我要吃水煮魚 
省長對市長說:你做個水煮魚 
市長說:縣長連紅燒魚都搞不定,這次就不找他了,我自己親自來做 
……(市長做呀做,又成功了) 
市長對省長說:水煮魚做好了 
省長說:不錯,下次有事還找你 
省長對總統說:水煮魚做好了 
總統說:不錯,下次有事還找你 
             ——本例引用於 @morgan_xww的《Android TouchEvent事件傳遞機制 》

  我們可以把上級分發下來的任務作為事件,當有事件發生時,事件會一級一級的向下傳遞。通過上面的例子我們可以瞭解到上級可以允許事件向下傳遞也可以自己攔截事件進行處理。在Android中,當螢幕接收到點選事件之後,螢幕會通過底層硬體層層的處理,將事件傳遞到我們當前正在執行的應用程式中,應用程式首先將事件交給當前正在執行的Activity,然後Activity可以決定將事件傳遞給當前的佈局中的ViewGroup,ViewGroup可以決定將事件攔截或者是繼續傳遞給其內部的View控制元件。就這樣事件通過層層的處理和傳遞形成了事件的傳遞機制。

事件傳遞相關API

這裡寫圖片描述

涉及事件傳遞的三個方法: 
public boolean dispatchTouchEvent(MotionEvent ev): 
  分發事件的方法。當接收到事件時,通過呼叫此方法來決定是否分發,當該方法返回true時,事件不再進行分發且已經在自身事件的分發中被消費了;當返回false時,也不對事件進行分發。也就是說當該方法不論是返回true還是返回false都不會對事件進行分發,只有當返回系統預設的 super.dispatchTouchEvent(ev)時,才會將事件分發給本層的攔截事件onInterceptTouchEvent(MotionEvent ev)去處理,如果沒有onInterceptTouchEvent(MotionEvent ev)方法,則直接分發給子View中的onInterceptTouchEvent方法處理。
public boolean onInterceptTouchEvent(MotionEvent ev): 
  攔截事件的方法,從上圖中我們可以看出,該方法只有在佈局或者ViewGroup中才會有。通過呼叫該方法來決定是否對事件進行攔截。當方法返回true時,進行攔截,將事件進行分發onTouchEvent方法處理;當返回false時,不對事件進行攔截,傳遞給下一級處理。 
public boolean onTouchEvent(MotionEvent ev): 
  處理事件。當對事件進行了攔截時,將會呼叫onTouchEvent方法來對事件進行處理。該方法返回true則表示該View能處理該事件,事件將終止向上傳遞(傳遞給其父View);返回false表示不能處理,則把事件傳遞給其父View的onTouchEvent()方法來處理。

事件的傳遞機制

我們通過一個例項來驗證學習一下事件的傳遞機制: 
這裡寫圖片描述 
  如上圖所示,我們來模擬Activity——>ViewGroup——>View的事件傳遞。首先自定義一個ViewGroup,這裡我們自定義一個MyLinearLayout繼承LinearLayout;然後自定義一個MyButton繼承Button

MyLinearLayout.java

package com.csii.eventdelivery;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;

/**
 * Created by bitaotao on 2016/7/18.
 */
public class MyLinearLayout extends LinearLayout {

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

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

    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 事件的分發
     * 分發事件的方法。當接收到事件時,通過呼叫此方法來決定是否分發。
     * @param ev
     * @return
     *  true: 事件不再進行分發且已經在自身事件的分發中被消費
     *  false: 也不對事件進行分發。
     *  也就是說該方法不論是返回true還是返回false都不會對事件進行分發,
     *
     *  只有當返回系統預設super.dispatchTouchEvent(ev)時,才會將事件分發給本層
     *  的攔截事件onInterceptTouchEvent(MotionEvent ev)去處理,如果沒有onInterceptTouchEvent(MotionEvent ev)方法
     *  則直接分發給子View中的onInterceptTouchEvent(MotionEvent ev)方法處理
     *
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(ev.getAction() == MotionEvent.ACTION_DOWN){
            Log.d("data","[LinearLayout]--dispatchTouchEvent:"+ev.getAction()+"     "+super.dispatchTouchEvent(ev));
        }
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 事件的攔截
     * 攔截事件方法,該方法只有在佈局或者ViewGroup中才會有。通過呼叫該方法來決定是否對事件
     * 進行攔截。
     * @param ev
     * @return
     *  true:進行攔截,將事件進行分發onTouchEvent方法處理
     *  false:不對事件進行攔截,傳遞給下一級處理
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(ev.getAction() ==MotionEvent.ACTION_DOWN){
            Log.d("data","[LinearLayout]--onInterceptTouchEvent:"+ev.getAction()+"     "+super.onInterceptTouchEvent(ev));
        }
        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 事件的處理
     * 處理事件。當對事件進行了攔截時,將會呼叫onTouchEvent方法對事件進行處理。
     *
     * @param ev
     * @return
     *  true:表示該View能處理該事件,事件將終止向上傳遞(傳遞給其父View)
     *  false:不能處理,則把事件傳遞給其父View的onTouchEvent方法來處理
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(ev.getAction() ==MotionEvent.ACTION_DOWN){
            Log.d("data","[LinearLayout]--onTouchEvent:"+ev.getAction()+"     "+super.onTouchEvent(ev));
        }
        return super.onTouchEvent(ev);
    }
}
MyButton.java

package com.csii.eventdelivery;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;

/**
 * 注意View中不包含攔截事件的功能。
 * 攔截事件的方法只有在佈局或者ViewGroup中才會用
 * Created by bitaotao on 2016/7/18.
 */
public class MyButton extends Button {
    public MyButton(Context context) {
        super(context);
    }

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

    public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(ev.getAction() == MotionEvent.ACTION_DOWN){
            Log.d("data","[Button]--dispatchTouchEvent:"+ev.getAction()+"     "+super.dispatchTouchEvent(ev));
        }
        return super.dispatchTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(ev.getAction() ==MotionEvent.ACTION_DOWN){
            Log.d("data","[Button]--onTouchEvent:"+ev.getAction()+"     "+super.onTouchEvent(ev));
        }
        return super.onTouchEvent(ev);
    }
}
MainActivity.java

package com.csii.eventdelivery;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(ev.getAction() == MotionEvent.ACTION_DOWN){
            Log.d("data","[Activity]--dispatchTouchEvent:"+ev.getAction()+"     "+super.dispatchTouchEvent(ev));
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(ev.getAction() == MotionEvent.ACTION_DOWN){
            Log.d("data","[Activity]--onTouchEvent:"+ev.getAction()+"     "+super.onTouchEvent(ev));
        }
        return super.onTouchEvent(ev);
    }
}


注意為了更好的看日誌列印,我們只對ACTION_DOWN事件處理。

點選LinearLayout

首先我們點選一下LinearLayout佈局的空白處(非Button處),形成如下日誌:

這裡寫圖片描述

  Activity接收到點選事件,然後將其傳遞給LinearLayout,LinearLayout的攔截返回的是false,也就是不對事件進行攔截,此時需要分發,但是LinearLayout發現自己下面沒有可以進行處理的View,只能交給自己的onTouchEvent處理,然後發現自己也處理不了,所以又提交給Activity的onTouchEvent處理,但是Activity的onTouchEvent返回的也是false,所以又交給更上一層進行處理。

點選Button

然後我們點選一下我們的Button按鈕,形成如下日誌:

這裡寫圖片描述

  Activity接收到點選事件,然後將其傳遞給LinearLayout,LinearLayout的攔截返回的是false,也就是不對事件進行攔截,此時需要分發,LinearLayout將事件分發給Button,Button首先判斷自己能否進行處理和分發,返回的都是true,但是因為Button下沒有子View所以不進行分發,自己onTouchEvent處理。

  Android中的事件在表現形式上有很多,如onTouch、onClick和onLongClick等,在具體微觀上的表現形式有action_down、action_move和action_up等。但是不管是哪種表現形式,都是基於事件的傳遞。

相關文章