Android觸摸事件傳遞機(jī)制

[聲明] 本文來(lái)自《顧浩鑫-Android高級(jí)進(jìn)階》第一章 的學(xué)習(xí)筆記氢哮,發(fā)文目的在于傳遞分享知識(shí)。如涉及文章內(nèi)容型檀、版權(quán)和其它問(wèn)題冗尤,請(qǐng)與本人聯(lián)系,我將在第一時(shí)間刪除內(nèi)容胀溺!

前言:在Android開(kāi)發(fā)中生闲,經(jīng)常會(huì)遇到觸摸事件沖突,比如ViewPager的輪播圖跟Fragment的劃動(dòng)事件沖突月幌,或者輪播圖跟下拉事件沖突碍讯,自定義view的事件處理等,本文章將會(huì)詳細(xì)介紹Activity扯躺、View捉兴、ViewGroup三者的觸摸事件傳遞機(jī)制,傳遞包括三個(gè)階段:分發(fā)录语、攔截倍啥、消費(fèi)。

一.觸摸事件的類(lèi)型

觸摸事件對(duì)應(yīng)的是 MotionEvent 類(lèi)澎埠,事件類(lèi)型主要有三種:

  1. ACTION_DOWN:用戶(hù)按下操作虽缕,表示一次觸摸事件的開(kāi)始。
  2. ACTION_MOVE:在按下的情況下蒲稳,進(jìn)行移動(dòng)氮趋。輕微的移動(dòng)都會(huì)傳遞到該事件伍派。
  3. ACTION_UP:用戶(hù)手指離開(kāi)屏幕,表示一次觸摸事件的

注 :如果用戶(hù)僅僅的是點(diǎn)擊而已剩胁,則只會(huì)執(zhí)行到 ACTION_DOWN 和 ACTION_UP 兩個(gè)事件诉植,不會(huì)執(zhí)行到 ACTION_MOVE 事件。所以 ACTION_DOWN 和 ACTION_UP 是事件是必須的昵观。

二.觸摸事件的傳遞階段

1.分發(fā)(Dispatch)

在Android系統(tǒng)中所有的觸摸事件都是由 dispatchTouchEvent 方法進(jìn)行分發(fā)的晾腔。該方法中判斷事件是被消費(fèi)(return true),還是繼續(xù)分發(fā)給子視圖處理(return super.dispatchTouchEvent)啊犬,如果當(dāng)前視圖是ViewGroup或者其子類(lèi)灼擂,則會(huì)調(diào)用onInterceptTouchEvent 判斷是否截?cái)r。

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

2.截?cái)r(Intercept)

事件的截?cái)r InterceptTouchEvent 只存在于ViewGroup及其子類(lèi)觉至,activity和View是不存在該方法剔应。該方法判斷事件是被截?cái)r (return true)并交給自身的 OnToucEvent 方法進(jìn)行消費(fèi),還是繼續(xù)傳遞給子視圖(return super.InterceptTouchEvent 或者 return false)康谆。

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }

3.消費(fèi)(Consume)

事件的消費(fèi)通過(guò) OnTouchEvent 方法判斷领斥,是被消費(fèi)(return true),還是不處理(return false)并將事件傳遞給父視圖的 OnTouchEvent 方法進(jìn)行處理嫉到。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

所有擁有事件傳遞能力的類(lèi):
Activity: 擁有dispatchTouchEvent 沃暗、OnTouchEvent
ViewGroup: 擁有dispatchTouchEvent 、OnInterceptTouchEvent 何恶、OnTouchEvent
View:擁有dispatchTouchEvent 孽锥、OnTouchEvent

三、View的事件傳遞機(jī)制

3.1 dome

雖然說(shuō)ViewGroup是View的子類(lèi)细层,但是這是說(shuō)的View指的是除ViewGroup之外的View控件子類(lèi)惜辑,首先定義一個(gè)MyTextView繼承TextView,打印每次事件的觸發(fā)以變了解事件傳遞的流程疫赎。

MyTextView 類(lèi)

public class MyTextView extends TextView {

    private String tag = "MyTextView";

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

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "dispatchTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "dispatchTouchEvent ACTION_DOWN");
                break;
        }
        return super.dispatchTouchEvent(event);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "onTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onTouchEvent ACTION_DOWN");
                break;
        }
        return super.onTouchEvent(event);
    }
}

定義一個(gè)MainActivity來(lái)展現(xiàn)這個(gè)MyTextView盛撑,同時(shí)設(shè)置點(diǎn)擊(onClick)和觸摸(onTouch)監(jiān)聽(tīng)。
MainActivity 類(lèi)

public class MainActivity extends AppCompatActivity implements View.OnClickListener,View.OnTouchListener{
    private MyTextView mMyTextView;
    private String tag = "MainActiviy";

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

        mMyTextView = findViewById(R.id.text_view);
        //  點(diǎn)擊監(jiān)聽(tīng)
        mMyTextView.setOnClickListener(this);
        //  觸碰監(jiān)聽(tīng)
        mMyTextView.setOnTouchListener(this);
    }


    // MyTextView 點(diǎn)擊事件
    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.text_view:
                Log.i(tag, "MyTextView onClick");
                break;
        }
    }

    // MyTextView 觸碰事件
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "MyTextView onTouch ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "MyTextView onTouch ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "MyTextView onTouch ACTION_DOWN");
                break;
        }
        return false;
    }

    // Activity 的事件分發(fā)
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "dispatchTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "dispatchTouchEvent ACTION_DOWN");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    // Activity 的事件消費(fèi)
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "onTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onTouchEvent ACTION_DOWN");
                break;
        }
        return super.onTouchEvent(event);
    }
}

3.2 打印日志

運(yùn)行后捧搞,點(diǎn)擊Text View反饋的打印日志

03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_DOWN
03-28 08:05:14.824 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_DOWN
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_UP
03-28 08:05:15.034 1219-1219/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_UP
03-28 08:05:15.044 1219-1219/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onClick

dispatchTouchEvent 抵卫、 OnTouchEvent 這兩個(gè)方法的返回值存在三種情況:

  1. 直接返回true。
  2. 直接返回false胎撇。
  3. 返回父類(lèi)同名方法介粘,super.dispatchTouchEvent 或者 super.OnTouchEvent。

由于擁有不同的返回值晚树,所以事件傳遞流程也有不同姻采,經(jīng)過(guò)不斷修改返回值測(cè)試,最終得到了點(diǎn)擊事件的流程圖爵憎,ACTION_DOWN 和 ACTION_UP 事件的傳遞流程是相同的慨亲。

3.3 事件傳遞流程圖

image

從上面的流程圖可以得出結(jié)論:

  1. 觸摸事件是從 dispatchTouchEvent 開(kāi)始的婚瓜,默認(rèn)返回父類(lèi)同名方法 super ,事件將會(huì)依照嵌套層次從外向內(nèi)傳遞( MainActivityMyTextView )巡雨,到達(dá)最內(nèi)層的 View 時(shí)闰渔,將由 ViewOnTouchEvent 方法處理,該方法返回 true 時(shí)進(jìn)行消費(fèi)不再傳遞铐望,返回 false 時(shí)再由內(nèi)向外傳遞冈涧,由外層的 OnTouchEvent 處理。
  2. 如果外層向內(nèi)層傳遞過(guò)程中正蛙,人為干擾返回 true 消費(fèi)督弓,則不會(huì)繼續(xù)繼續(xù)像內(nèi)部傳遞。
  3. View 的事件控制順序先執(zhí)行 onTouch 再執(zhí)行 onClick 乒验,如果 onTouch 返回 true 消費(fèi)愚隧,則不會(huì)繼續(xù)傳遞,也不會(huì)執(zhí)行 onClick 方法锻全。

四狂塘、ViewGroup的事件傳遞機(jī)制

4.1 dome

ViewGroupView 的控件容器存在,擁有 dispatchTouchEvent 鳄厌、 onInterceptTouchEventonTouchEvent 三個(gè)方法荞胡,比 View 多了一個(gè) onInterceptTouchEvent 方法。為了更好的觀察了嚎,我們需要自定義 MyRelativeLayout 繼承 RelativeLayout 泪漂。

MyRelativeLayout類(lèi)

public class MyRelativeLayout extends RelativeLayout {

    private final static String tag = "MyRelativeLayout";

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

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "dispatchTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "dispatchTouchEvent ACTION_DOWN");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "onInterceptTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onInterceptTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onInterceptTouchEvent ACTION_DOWN");
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "onTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onTouchEvent ACTION_DOWN");
                break;
        }
        return super.onTouchEvent(event);
    }
}

main_activity.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<com.mvp.chenzhesheng.androidadvance.MyRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <com.mvp.chenzhesheng.androidadvance.MyTextView
        android:id="@+id/text_view"
        android:clickable="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"

</com.mvp.chenzhesheng.androidadvance.MyRelativeLayout>

4.2 打印日志

04-02 08:47:57.980 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: onInterceptTouchEvent ACTION_DOWN
04-02 08:47:58.000 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_DOWN
04-02 08:47:58.010 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_DOWN
04-02 08:47:58.010 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_DOWN
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: dispatchTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: dispatchTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyRelativeLayout: onInterceptTouchEvent ACTION_UP
04-02 08:47:58.200 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: dispatchTouchEvent ACTION_UP
04-02 08:47:58.210 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onTouch ACTION_UP
04-02 08:47:58.210 1030-1030/com.mvp.chenzhesheng.androidadvance I/MyTextView: onTouchEvent ACTION_UP
04-02 08:47:58.260 1030-1030/com.mvp.chenzhesheng.androidadvance I/MainActiviy: MyTextView onClick

可以看到 MainActivityMyTextView 的事件傳遞處理中添加了一層 MyRelativeLayout 。通過(guò)不同返回值測(cè)試歪泳,得到一套流程圖萝勤。

4.3 流程圖

image

從上面的流程圖可以得出結(jié)論:

  1. 觸摸事件傳遞是從 Activity 傳遞到 ViewGroup ,再傳遞到 View 。如果中間沒(méi)有 ViewGroup 則直接從 Activity 傳遞到 View 呐伞。
  2. ViewGroup 通過(guò) onInterceptTouchEvent 方法對(duì)事件進(jìn)行截?cái)r敌卓,如果返回 false 或者 super.onInterceptTouchEvent ,則事件會(huì)繼續(xù)傳遞給子 View 伶氢。
  3. View 中對(duì)事件進(jìn)行消費(fèi)后趟径,ViewGroup 將不會(huì)接收到任何事件。

五.總結(jié)

  1. 事件分發(fā)是由外到內(nèi)鞍历,從 Activity 到具體的子 View 舵抹;
  2. 事件處理消費(fèi)是由內(nèi)到外,從子 View 到最外層 Activity 劣砍;
  3. 事件攔截只存在于 ViewGroup 中惧蛹;
  4. 掌握事件傳遞機(jī)制可以更好的進(jìn)行事件處理,無(wú)論是自定義 View 還是閱讀 Framework 層源碼都需要對(duì)事件傳遞進(jìn)行學(xué)習(xí),才能更精致的開(kāi)發(fā)應(yīng)用香嗓。
微信圖片_20180530164801.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末迅腔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子靠娱,更是在濱河造成了極大的恐慌沧烈,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件像云,死亡現(xiàn)場(chǎng)離奇詭異锌雀,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)迅诬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)腋逆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人侈贷,你說(shuō)我怎么就攤上這事惩歉。” “怎么了俏蛮?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵撑蚌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我搏屑,道長(zhǎng)争涌,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任睬棚,我火速辦了婚禮第煮,結(jié)果婚禮上解幼,老公的妹妹穿的比我還像新娘抑党。我一直安慰自己,他們只是感情好撵摆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布底靠。 她就那樣靜靜地躺著,像睡著了一般特铝。 火紅的嫁衣襯著肌膚如雪暑中。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天鲫剿,我揣著相機(jī)與錄音鳄逾,去河邊找鬼。 笑死灵莲,一個(gè)胖子當(dāng)著我的面吹牛雕凹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼枚抵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼线欲!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起汽摹,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤李丰,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后逼泣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體趴泌,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年拉庶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了踱讨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡砍的,死狀恐怖痹筛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情廓鞠,我是刑警寧澤帚稠,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站床佳,受9級(jí)特大地震影響滋早,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜砌们,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一杆麸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浪感,春花似錦昔头、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至峻堰,卻和暖如春讹开,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捐名。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工旦万, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镶蹋。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓成艘,卻偏偏與公主長(zhǎng)得像拇砰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狰腌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容