View的事件分發(fā)機(jī)制總結(jié)

1.view的重要性

view的雖稱(chēng)不上Android四大組件,但它的重要性可以說(shuō)是跟四大組件平級(jí),根據(jù)使用頻率,甚至比廣播跟內(nèi)容提供器重要。view主要包含兩類(lèi):ViewGroup和具體的View,有Android開(kāi)發(fā)經(jīng)驗(yàn)的都知道這兩類(lèi)的區(qū)別了。我們時(shí)時(shí)刻刻都有使用到view,例如TextView、ImageView等,正因?yàn)槲覀儠r(shí)時(shí)刻刻都在用,所以就顯得特別重要了。

2.view的知識(shí)體系

view的事件分發(fā)機(jī)制、view的繪制流程、滑動(dòng)沖突解決、熟悉sdk提供的常用view、自定義view相關(guān)知識(shí)與技巧、view的性能優(yōu)化。

3.view事件分發(fā)的重要性

事件分發(fā)作為view的基礎(chǔ)知識(shí),view的滑動(dòng)沖突以及自定義view都需要了解view的事件分發(fā)流程,所以足以體現(xiàn)view事件分發(fā)機(jī)制的重要性。

4、事件分發(fā)流程梳理

事件分發(fā)流程說(shuō)簡(jiǎn)單不簡(jiǎn)單,說(shuō)難不難,曾今試過(guò)拼命記住這個(gè)流程,但發(fā)現(xiàn)不久之后都會(huì)忘記,因?yàn)橛浀亩际莿e人寫(xiě)出來(lái)的總結(jié),所以這次試著自己跟著源碼走一遍流程,再用流程圖表達(dá)出來(lái),就清晰很多了。先上流程圖,再跟著流程圖走一遍。


View事件分發(fā)流程圖

順著箭頭走,可以看到事件是被
Activity 的dispatchTouchEvent(MotionEvent ev)方法先拿到的,從方法名可以看出來(lái)這個(gè)方法就是分發(fā)觸摸事件的;接著會(huì)調(diào)用
PhoneWindow 的superDispatchTouchEvent(MotionEvent event)方法,其實(shí)PhoneWindow是Window的唯一實(shí)現(xiàn)類(lèi);再調(diào)用
DecorView 的superDispatchTouchEvent(MotionEvent event)方法,DecorView其實(shí)就是最頂級(jí)的View,繼承自FrameLayout,平時(shí)我們熟悉的setContentView(R.layout.activity_layout)就是將布局設(shè)置到這個(gè)DecorView;接著來(lái)到
ViewGroup ,這個(gè)ViewGroup就是我們平時(shí)自己布局里面的最外層的父布局,例如LinearLayout,事件來(lái)到這里就會(huì)稍微復(fù)雜起來(lái)了,會(huì)根據(jù)情況處理事件,首先還是調(diào)用ViewGroup的dispatchTouchEvent(MotionEvent ev)方法,在該方法會(huì)優(yōu)先判斷disallowIntercept這個(gè)布爾值,這個(gè)disallowIntercept后面會(huì)講到,如果為true,則說(shuō)明ViewGroup不消費(fèi)事件,事件就來(lái)到了子
View 的dispatchTouchEvent(MotionEvent event),這里假設(shè)我們的布局是很簡(jiǎn)單的,一個(gè)LinearLayout包含一個(gè)Button,這樣方面分析,所以這個(gè)子View就是Button,如果有設(shè)置mOnTouchListener,這事件會(huì)來(lái)到mOnTouchListener.onTouch(this, event),如果onTouch方法返回true則該事件就被消費(fèi)了,返回false的話事件會(huì)來(lái)到onTouchEvent(event)方法,在該方法會(huì)判斷mOnClickListener是否被設(shè)置,這個(gè)監(jiān)聽(tīng)器是我們平時(shí)用的最多的setOnClickListener(@Nullable OnClickListener l)方法,這個(gè)方法的優(yōu)先級(jí)從這里可以看出優(yōu)先級(jí)是最低的,因?yàn)槭录阶詈蟛艜?huì)來(lái)到這里,如果onTouchEvent(event)返回false則會(huì)調(diào)用ViewGroup的onTouchEvent(ev),如果ViewGroup的onTouchEvent(ev)還是返回false,則事件最終丟回給Activity的onTouchEvent(ev)方法了,相當(dāng)于這個(gè)事件走了地球一圈都沒(méi)人要還是回到了原點(diǎn)。

剛剛分析到ViewGroup的時(shí)候,當(dāng)disallowIntercept為false時(shí),事件會(huì)來(lái)到它的onInterceptTouchEvent(ev),這個(gè)方法的從名字可以看出是否攔截事件的意思,返回true表示攔截,則會(huì)交給自己的onTouchEvent(ev)方法,返回false表示不攔截事件,將事件丟給了View的dispatchTouchEvent(MotionEvent event),也就相當(dāng)于disallowIntercept等于true。

前面ViewGroup 說(shuō)到的disallowIntercept變量是通過(guò)parent.requestDisallowInterceptTouchEvent(boolean)設(shè)置的,設(shè)置是否禁止攔截事件的意思,一般子view不希望父類(lèi)攔截事件的話可以調(diào)用該方法,在處理滑動(dòng)沖突的時(shí)候會(huì)經(jīng)常用到這個(gè)方法,他的優(yōu)先級(jí)也是最高的。

5.記憶技巧

要想對(duì)這個(gè)事件流程做到熟練也不易忘記,跟著源碼走一遍的必須的,只有自己分析過(guò)一遍了才能有更深的體會(huì),過(guò)完之后需要再跟著源碼將流程圖畫(huà)出來(lái),不要參考別人的流程圖這點(diǎn)很重要,熟練的表達(dá)才是掌握的最好證明。整個(gè)流程就是事件從某個(gè)地方出發(fā),一層一層的傳遞下去,如果在某一層被消費(fèi)掉的話,則該事件就結(jié)束掉了,如果傳到最外一層的子view還沒(méi)被消費(fèi)的話,該事件又會(huì)往回走,往回走如果還是沒(méi)人消費(fèi)的話就會(huì)回到出發(fā)點(diǎn)。

6.解決滑動(dòng)沖突

滑動(dòng)沖突是很長(zhǎng)見(jiàn)的一種場(chǎng)景,下面是其中一種比較常見(jiàn)的滑動(dòng)沖突


父View與子View都可以左右滑動(dòng).png

例如ViewPager裝載多個(gè)可以縮放的ZoomImageView,默認(rèn)情況下ViewPager或攔截滑動(dòng)事件,所以當(dāng)ZoomImageView的寬或高大于ViewPager的寬或者高時(shí),這個(gè)時(shí)候滑動(dòng)的話應(yīng)該是滑動(dòng)ZoomImageView而不是ViewPager。如果沒(méi)有解決滑動(dòng)沖突就會(huì)出現(xiàn)下面gif的情況


未解決.gif

這種用戶體驗(yàn)是為0的,這種情況一般可以使用內(nèi)部攔截法或外部攔截法處理,下面是內(nèi)部攔截法的核心代碼:

 @Override
    public boolean onTouch(View v, MotionEvent event) {
        ...
         switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if ((rectF.width() > width + 0.01 || rectF.height() > height + 0.01)) {
                    if (getParent() instanceof ViewPager) {
                        //禁止父類(lèi)攔截事件
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                }
                break;
      ...
    return true;
}

只需要在ZoomImageView的onTouch方法的MotionEvent.ACTION_DOWN事件調(diào)用getParent().requestDisallowInterceptTouchEvent(true);就可以了,當(dāng)ZoomImageView的寬或高大于ViewPager的寬或高時(shí)禁止ViewPager攔截事件,最后一行return true則表示該事件自己消費(fèi)掉了。


解決沖突.gif

滑動(dòng)沖突還有其他幾種場(chǎng)景,但核心都是根據(jù)業(yè)務(wù)需求使用外部攔截或內(nèi)部攔截基本都可以解決。
view的事件分發(fā)機(jī)制對(duì)于自定義view的作用更是重中之中。

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

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