主頁 > 移動端開發 > Material Design之CoordinatorLayout原理剖析

Material Design之CoordinatorLayout原理剖析

2021-04-14 11:07:59 移動端開發

此篇文章需要用到之前的知識點,即我之前寫的這兩篇博客
事件分發機制原理分析
NestedScrolling機制原理分析

一.CoordinatorLayout基本介紹

1.使用場景

一般作為應用的頂層布局,同時也作為一個管理容器,管理與子view 或者 view之間的互動,那么都具體管理啥呢?可以分成四個部分

2.作用

  • 處理子控制元件之間依賴下的互動
  • ②處理子控制元件之間的嵌套滑動
  • ③處理子控制元件的測量與布局
  • ④處理子控制元件的事件攔截與回應

以上四個功能,都建立于CoordainatorLayout中提供的一個叫做Behavior的**“ 插件”**之上,Behavior內部也提供了相應方法來對應這四個不同的功能,那么都有哪些方法呢?

3.Behavior中的常用方法

在這里插入圖片描述

4.為什么把這些方法都放到Behavior中呢?

答案:解耦,當用的時候就集成進去,不用的時候就remove,即可插拔

  • 在這里可以把CoordinatorLayout比喻成Android Studio,把子View比喻成我們的專案,我們知道,AS可以使用Plugins為專案引入各種插件,從而實作不同的功能,同樣的道理,CoordinatorLayout可以使用Behavior為子View引入各種行為,Behavior也是**“可插拔”**

二.CoordinatorLayout四個功能的原理

1.CoordinatorLayout下依賴互動功能原理(觀察者模式)

CoordainatorLayout中子控制元件depandency的位置、大小等發生改變的時候,那么在CoordainatorLayout內部會通知所有依賴depandency的控制元件,并呼叫對應宣告的Behavior,告知其依賴的depandency發生改變,

  • 那么如何判斷依賴是哪個View呢?
    layoutDependsOn方法
  • 接受到通知后如何處理呢?
    onDependentViewChanged/onDependentViewRemoved方法

原理圖
在這里插入圖片描述
dependency也是一個child,和child1child2在布局上可以是并列的,后面我們把dependency統一稱為DepandedView

2.CoordinatorLayout內部嵌套滑動原理

CoordinatorLayout實作了NestedScrollingParent2介面,所以當事件(scrollfling)產生后,內部實作了NestedScrollingChild介面的子控制元件會將事件傳遞給CoordinatorLayoutCoordinatorLayout又會將事件傳遞給所有的Behavior,然后在Behavior中實作子控制元件的嵌套滑動,
在這里插入圖片描述
具體流程圖
在這里插入圖片描述

相對于NestedScrolling機制(參與角色只有子控制元件和父控制元件),CoordainatorLayout中的互動角色玩出了新高度,在CoordainatorLayout下的子控制元件可以與多個兄弟控制元件進行互動,即從1:1變成了1:N

3.CoordinatorLayout子控制元件的測量與布局

在特殊的情況下,如子控制元件需要處理寬高和布局的時候,那么交由Behavior內部的onMeasureChildonLayoutChild方法來進行處理
在這里插入圖片描述

4.CoordinatorLayout子控制元件的事件攔截與回應

對于事件的攔截與處理,如果子控制元件需要攔截并消耗事件,那么交由給Behavior內部的onInterceptTouchEventonTouchEvent方法進行處理
在這里插入圖片描述

三.原始碼分析CoordinatorLayout子控制元件依賴互動功能原理

由于View的生命周期的開始是在onAttachedToWindow方法中,所以我們進入此方法尋找

1.我們在CoordinatorLayout類中找到onAttachedToWindow方法

發現它呼叫getViewTreeObserver,獲得ViewTreeObserver,然后呼叫了addOnPreDrawListener
在這里插入圖片描述

  • 關于ViewTreeObserver
    ViewTreeObserver 注冊一個觀察者來監聽視圖樹,當視圖樹的布局、視圖樹的焦點、視圖樹將要繪制、視圖樹滾動等發生改變時,ViewTreeObserver都會收到通知,ViewTreeObserver不能被實體化,可以呼叫View.getViewTreeObserver()來獲得
  • 關于dispatchOnPreDraw:通知觀察者繪制即將開始,如果其中的某個觀察者回傳true,那么繪制將會取消,并且重新安排繪制,如果想在View LayoutViewhierarchy 還未依附到Window時,或者在View處于GONE狀態時強制繪制,可以手動呼叫這個方法
2.接下來我們看一下它添加的監聽者是個啥,追蹤addOnPreDrawListener,找到OnPreDrawListener

在這里插入圖片描述
View發生變化的時候會呼叫onChildViewsChanged方法,

3.我們追蹤onChildViewsChanged

它有一個型別,即type
在這里插入圖片描述
也就是DispatchChangeEvent注解,我們點進去看一下
在這里插入圖片描述
一個代表繪制之前,一個代表嵌套滑動,一個代表View移除,意思是說這三種型別的事件發生的時候會呼叫onChildViewsChanged方法,

我們深究一下這個方法

final void onChildViewsChanged(@DispatchChangeEvent final int type) {
        ,,,
        //--------------------------------------
        //--------------------------------------
        //得到子View的數目
        //--------------------------------------
        //--------------------------------------
        final int childCount = mDependencySortedChildren.size();
        ,,,
		//--------------------------------------
        //--------------------------------------
        //取出每一個子View,即child 
        //--------------------------------------
        //--------------------------------------
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
                // Do not try to update GONE child views in pre draw updates.
                continue;
            }
			,,,
            //--------------------------------------
	        //--------------------------------------
	        //然后再嵌套一個for回圈,再依次重新取出子View
	        //--------------------------------------
	        //--------------------------------------
            for (int j = i + 1; j < childCount; j++) {
            	//--------------------------------------
		        //--------------------------------------
		        //首先得到子View即checkChild ,然后得到子View的LayoutParams,再根據它得到Behavior 
		        //--------------------------------------
		        //--------------------------------------
                final View checkChild = mDependencySortedChildren.get(j);
                final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
                final Behavior b = checkLp.getBehavior();
				//--------------------------------------
		        //--------------------------------------
		        //判斷一下此時得到的checkChild是不是要依賴child
		        //換句話說,看一下這個child是不是被依賴的
		        //--------------------------------------
		        //--------------------------------------
                if (b != null && b.layoutDependsOn(this, checkChild, child)) {
                    if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
                        //--------------------------------------
				        //--------------------------------------
				        //如果type是EVENT_PRE_DRAW(這個type上面說過),則呼叫resetChangedAfterNestedScroll
				        //--------------------------------------
				        //--------------------------------------
                        checkLp.resetChangedAfterNestedScroll();
                        continue;
                    }

                    final boolean handled;
                    switch (type) {
                        case EVENT_VIEW_REMOVED:
                            //--------------------------------------
					        //--------------------------------------
					        //如果type是EVENT_VIEW_REMOVED,則呼叫onDependentViewRemoved
					        //--------------------------------------
					        //--------------------------------------
                            b.onDependentViewRemoved(this, checkChild, child);
                            handled = true;
                            break;
                        default:
                            //--------------------------------------
					        //--------------------------------------
					        //如果type是其他型別,則呼叫onDependentViewChanged
					        //--------------------------------------
					        //--------------------------------------
                            handled = b.onDependentViewChanged(this, checkChild, child);
                            break;
                    }

                    if (type == EVENT_NESTED_SCROLL) {
                        //--------------------------------------
				        //--------------------------------------
				        //如果type是EVENT_NESTED_SCROLL,則呼叫setChangedAfterNestedScroll
				        //--------------------------------------
				        //--------------------------------------                        		
				        checkLp.setChangedAfterNestedScroll(handled);
                    }
                }
            }
        }
		...
    }

通過上面的原始碼,我們了解了在需要依賴其他控制元件的控制元件中設定一個behavior,那么被依賴的控制元件也就是DepandedView發生變化的時候就能通知對方的原因,主要是onChildViewsChanged方法,

  • 那么為什么可以呼叫remove相應的方法(即onDependentViewRemoved)呢?
    ①我們在CoordinatorLayout的構造方法中發現它呼叫了setOnHierarchyChangeListener
    在這里插入圖片描述
    我們看它傳入的引數是
    在這里插入圖片描述
    ②追蹤進入,發現HierarchyChangeListener類實作了OnHierarchyChangeListener介面,
    在這里插入圖片描述
    ③我們再追蹤OnHierarchyChangeListener
    在這里插入圖片描述
    發現它是一個介面,定義了兩個方法,
    EVENT_VIEW_REMOVED型別事件發生的時候,就會呼叫onChildViewsChanged方法,然后引數傳入EVENT_VIEW_REMOVED,然后就會回呼BehavioronDependentViewRemoved,也就是上面原始碼分析的那部分
    在這里插入圖片描述

四.原始碼總結

總結一波:
CoordinatorLayout的某一DepandedView發生變化的時候,必然會導致重繪,然后就會呼叫
onChildViewsChanged方法,如圖
在這里插入圖片描述在這個方法里面根據type,再去呼叫Behavior的相應方法,

  • 這也是把OnPreDrawListener作為監聽者的原因,因為有些時候onMeasure或者onLayout可能不會呼叫,但是關于draw的方法是肯定會呼叫的,這樣把onChildViewsChanged方法放在監聽者的onPreDraw方法中,就可以在DepandedView發生變化的時候,及時呼叫onChildViewsChanged,通知其他依賴DepandedView的子View們發生相應的變化,當然這些變化的方法是由Behavior呼叫的

一個問題:mDependencySortedChildren是啥

ok,讓我們思考一個問題,不知道大家發現沒有,在onChildViewsChanged原始碼中,CoordinatorLayout得到子View的時候,不是呼叫的我們之前學過的getChildAt方法,而是呼叫的mDependencySortedChildren的相應方法,
在這里插入圖片描述那么mDependencySortedChildren是個啥?我們點進去看看

在這里插入圖片描述
我們看到了它下面有一個DirectedAcyclicGraph<View>型別的資料,

  • 我先解釋下原因吧,在這里,因為CoordinatorLayout管理的不僅僅是子View,還有子View之間的關系(依賴關系),也就是說不能用一個簡單的集合,而是用這個資料結構,而DirectedAcyclicGraph就是一個圖(具體來說是有向無環圖,用鄰接表來實作的),表示1對多的關系,當我們找到一個View,以及其與其他View之間的依賴關系時,就把這個View和相應依賴關系(圖中叫做)存入這個DirectedAcyclicGraph中,然后再將其某種形式存入mDependencySortedChildren中,這就是mDependencySortedChildren

第二個問題,mDependencySortedChildren如何賦值的

mDependencySortedChildren的賦值和DirectedAcyclicGraph的賦值有很大關系,我們先看DirectedAcyclicGraph

onMeasure方法中

在這里插入圖片描述

我們發現了prepareChildren,追蹤進入

這里面就是具體賦值的流程,都寫在注釋中了

    private void prepareChildren() {
    	//--------------------------------------
        //--------------------------------------
        //首先對兩者進行清空操作
        //--------------------------------------
        //--------------------------------------          
        mDependencySortedChildren.clear();
        mChildDag.clear();

        for (int i = 0, count = getChildCount(); i < count; i++) {
	        //--------------------------------------
	        //--------------------------------------
	        //然后得到子View,即view 
	        //--------------------------------------
	        //--------------------------------------   
            final View view = getChildAt(i);

            final LayoutParams lp = getResolvedLayoutParams(view);
            lp.findAnchorView(this, view);
			//--------------------------------------
	        //--------------------------------------
	        //把這個子view加入圖中,作為圖的節點
	        //--------------------------------------
	        //------------------------------------
            mChildDag.addNode(view);

            for (int j = 0; j < count; j++) {
                if (j == i) {
                    continue;
                }
                //--------------------------------------
		        //--------------------------------------
		        //然后再嵌套for回圈,得到子View,即other 
		        //--------------------------------------
		        //------------------------------------
                final View other = getChildAt(j);
                if (lp.dependsOn(this, view, other)) {
                    if (!mChildDag.contains(other)) {
                        // Make sure that the other node is added
                        mChildDag.addNode(other);
                    }
                    //--------------------------------------
			        //--------------------------------------
			        //如果view依賴other,或者說other是DepandedView時
			        //就畫一條從other到view的邊,并加入圖中,表明view和other存在依賴關系
			        //--------------------------------------
			        //------------------------------------
                    mChildDag.addEdge(other, view);
                }
            }
        }

        //--------------------------------------
        //--------------------------------------
        //呼叫addAll方法,將圖的getSortedList加入到mDependencySortedChildren中
        //--------------------------------------
        //------------------------------------
        mDependencySortedChildren.addAll(mChildDag.getSortedList());
        //--------------------------------------
        //--------------------------------------
        //這里做了一個反轉,無需理解,知道就行
        //--------------------------------------
        //------------------------------------
        Collections.reverse(mDependencySortedChildren);
    }

到這我們知道了CoordinatorLayout是通過圖里面的邊來區分誰依賴于誰的了

五.圖示CoordinatorLayout下的事件傳遞機制

在這里插入圖片描述

在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/275826.html

標籤:其他

上一篇:ViewPager系列:解決ViewPager內部,Fragment設定高度不起作用(二)

下一篇:關于Axios中寫了axios.defaults.withCredentials = true,攜帶Session還是有問題

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more