Android事件分發(fā)、事件攔截機(jī)制全解

觸摸反饋

把一系列的觸摸事件解讀為對應(yīng)的操作,然后根據(jù)解讀出來的操作給出相應(yīng)的反饋,這就是觸摸反饋的本質(zhì)。其中,觸摸事件不是獨(dú)立的,是成序列的,成組的。每一組事件以按下事件為開頭,以抬起事件或取消事件為結(jié)束。其中ACTION_CANCEL是特殊的,對應(yīng)的是事件序列的非人為的提前結(jié)束。每一個(gè)觸摸事件都會(huì)交給View.onTouchEvent(MotionEvent event)去處理。然后通過參數(shù)event,代表事件類型(按下、抬起或其他)、坐標(biāo)、其他各種信息反饋給我們開發(fā)者。

事件分發(fā)機(jī)制:(從上到下)

為了解決觸摸沖突而設(shè)置的機(jī)制。

例子:

矩形是父view,有2個(gè)子view:圓形按鈕(可點(diǎn)擊)和"Lorem Ipsum"文字(不可點(diǎn)擊)。這時(shí)點(diǎn)擊按鈕是可以子view觸發(fā)事件,但是點(diǎn)擊文字是觸發(fā)的父view的點(diǎn)擊事件。

Android是如何做到的?(觸摸事件分發(fā))

如果一個(gè)view對這個(gè)down的onTouchEvent()沒有響應(yīng),那么它就會(huì)繼續(xù)向下,直到遇到第一個(gè)做出響應(yīng)的view,這個(gè)向下的過程才會(huì)結(jié)束。這個(gè)時(shí)候這個(gè)view就成了這組事件的接收者。這個(gè)事件的后續(xù)事件都會(huì)直接發(fā)送給這個(gè)view,不會(huì)給它上面的view和下面的view.直到這組事件的結(jié)束,即ACTION_UP/ACTION_CANCEL事件。

這是一個(gè)很易懂的邏輯,離用戶最近的可觸摸的控件是這組事件的響應(yīng)者。

onTouchEvent返回return true;表示消費(fèi)了該事件。更直觀的理解為,告訴android,我希望處理以這個(gè)down事件為起始點(diǎn)的事件流,你把這之后的后續(xù)事件都交給我。

事件的攔截機(jī)制:(從下往上)

為了解決滑動(dòng)沖突而設(shè)置的機(jī)制。

案例:

可點(diǎn)擊的控件是在列表里面。點(diǎn)擊某個(gè)控件會(huì)觸發(fā)點(diǎn)擊事件。而把手機(jī)放到屏幕上滑一下,列表也是會(huì)滑動(dòng)的。為了符合直覺,安卓的觸摸是從上往下傳遞的被某個(gè)控件消費(fèi)后就不會(huì)再往下傳了。那么隔著一個(gè)按鈕實(shí)現(xiàn)的滑動(dòng)怎么做到的?

答案:觸摸事件的攔截機(jī)制。

其實(shí)在觸摸事件的分發(fā)(從屏幕的頂部向下分發(fā))之前還有個(gè)過程:觸摸屏幕的時(shí)候,每個(gè)觸摸事件到達(dá)onTouchEvent()之前,android會(huì)從整個(gè)activity的最底部的那個(gè)view(根view)去向上一級一級的詢問:你要不要攔截這組事件。
攔截的意思就是說事件我就不交給子view了,我就自己來處理了。

具體在實(shí)現(xiàn)上,它是通過調(diào)用viewgroup的onInterceptTouchEvent()實(shí)現(xiàn)。也就是當(dāng)一個(gè)事件發(fā)生的時(shí)候,首先會(huì)從底部的view向上遞歸的調(diào)用每一級的子view的onInterceptTouchEvent()去詢問該子view是否要攔截這組事件,默認(rèn)是返回false不攔截。如果他要返回false,那么就會(huì)繼續(xù)向上去問它的子view詢問是否攔截。如果整個(gè)流程都走完,全部都返回false,那么就會(huì)走第2個(gè)流程:onTouchEvent()從上往下。

另外,對于onInterceptTouchEvent()返回true的時(shí)候,除了完成事件接管,這個(gè)view還會(huì)對它的子view發(fā)送一個(gè)ACTION_CANCEL取消事件。

onInterceptTouchEvent()則是你在整個(gè)過程中,都可以對事件流中的每個(gè)事件進(jìn)行監(jiān)聽,你可以先行觀望,給子view一個(gè)處理事件的機(jī)會(huì),而一旦事件流的發(fā)展達(dá)到了你的觸發(fā)條件,比如用戶現(xiàn)在在滑動(dòng),你可以立刻返回true,立刻實(shí)現(xiàn)事件流的接管,這樣就2不耽誤,既讓子view有機(jī)會(huì)去處理事件,又可以在需要的時(shí)候把處理事件的工作給接管過來。

事件攔截的主要方法:

Android事件分發(fā)主要牽涉到dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev)這三個(gè)方法。

public boolean dispatchTouchEvent(MotionEvent ev) 事件分發(fā)
  • return super.dispatchTouchEvent(ev),默認(rèn)事件會(huì)自動(dòng)的分發(fā)給當(dāng)前 View 的 onInterceptTouchEvent 方法;
  • return true,事件會(huì)分發(fā)給當(dāng)前 View 并由 dispatchTouchEvent 方法進(jìn)行消費(fèi),同時(shí)事件會(huì)停止向下傳遞;
  • return false,會(huì)將事件返回給父的 onTouchEvent 進(jìn)行消費(fèi);
public boolean onInterceptTouchEvent(MotionEvent ev) 事件攔截
  • return super.onInterceptTouchEvent(ev),默認(rèn)情況下會(huì)將事件進(jìn)行攔截,并將攔截到的事件交由當(dāng)前 View 的 onTouchEvent 進(jìn)行處理;
  • return true,同return super.onInterceptTouchEvent(ev);
  • return false,表示不攔截當(dāng)前事件,當(dāng)前 View 上的事件會(huì)被傳遞到子 View 上,再由子 View 的 dispatchTouchEvent 來開始這個(gè)事件的分發(fā);
public boolean onTouchEvent(MotionEvent event) 事件消費(fèi)
  • return super.onTouchEvent(ev),默認(rèn)將事件傳遞到上層View進(jìn)行處理,如果上層也return false,則該事件消失;
  • return true,會(huì)消費(fèi)當(dāng)前事件;
  • return false,同return super.onTouchEvent(ev);

總結(jié):

事件總線圖

Activity沒有onInterceptTouchEvent的方法,只能將事件傳遞給下一層Viewgroup1進(jìn)行分發(fā),如果Viewgroup1攔截事件,就交給onTouchEvent處理,如果onTouchEvent返回true,就消費(fèi)事件,事件消失,如果onTouchEvent返回false,事件傳遞給父View;如果Viewgroup1不攔截事件,再交給一下層Viewgroup2進(jìn)行分發(fā),如果攔截事件,交給onTouchEvent處理,而簡單View沒有onInterceptTouchEvent攔截事件的方法。如此層層下去,子又生孫,孫又生子。如果沒有任何一個(gè)消費(fèi)事件,那么事件會(huì)依次層層往上傳遞。

打完收工,弄清楚事件分發(fā)機(jī)制能幫助我們在開發(fā)復(fù)雜Viewgroup,或多層View嵌套時(shí),能順利處理事件的攔截和沖突。好了,下期節(jié)目再見。

問題總結(jié):

  • OnTouchListener、OnLongClickListener和onTouchEvent有什么區(qū)別

onTouch是View的OnTouchListener接口中定義的方法,所以需要先調(diào)用setOnTouchListener設(shè)置監(jiān)聽。onTouchEvent()是每個(gè)事件處理對象都自帶的方法,比如Activity,view,viewGroup等都有onTouchEvent()事件監(jiān)聽。

onTouchListener的onTouch方法優(yōu)先級比onTouchEvent高,會(huì)先觸發(fā)。

假如onTouch方法返回false會(huì)接著觸發(fā)onTouchEvent,反之onTouchEvent方法不會(huì)被調(diào)用。
內(nèi)置諸如click事件的實(shí)現(xiàn)等等都基于onTouchEvent,假如onTouch返回true,這些事件將不會(huì)被觸發(fā)。

順序?yàn)?
OnTouchListener—–>onTouchEvent—>onclick

        view.setOnClickListener {
            Log.i("minfo", "click")
        }

        view.setOnLongClickListener(object: View.OnLongClickListener {
            override fun onLongClick(v: View?): Boolean {
                Log.i("minfo", "longClick")
                return false
            }
        })

        view.setOnTouchListener(object: View.OnTouchListener {
            override fun onTouch(v: View, event: MotionEvent): Boolean {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    Log.i("minfo", "action_down")
                    return true
                }
                else if (event.getAction() == MotionEvent.ACTION_UP) {
                    Log.i("minfo", "action_up")
                    return true
                }
                else if (event.getAction() == MotionEvent.ACTION_MOVE){
                    Log.i("minfo", "action_move")
                    return true
                }
                return false
            }
        })

參考:
https://rengwuxian.com/ui-3-1/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容