此篇文章需要用到之前的知識點,即我之前寫的這兩篇博客
事件分發機制原理分析
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,和child1、child2在布局上可以是并列的,后面我們把dependency統一稱為DepandedView
2.CoordinatorLayout內部嵌套滑動原理
CoordinatorLayout實作了NestedScrollingParent2介面,所以當事件(scroll或fling)產生后,內部實作了NestedScrollingChild介面的子控制元件會將事件傳遞給CoordinatorLayout,CoordinatorLayout又會將事件傳遞給所有的Behavior,然后在Behavior中實作子控制元件的嵌套滑動,

具體流程圖

相對于NestedScrolling機制(參與角色只有子控制元件和父控制元件),CoordainatorLayout中的互動角色玩出了新高度,在CoordainatorLayout下的子控制元件可以與多個兄弟控制元件進行互動,即從1:1變成了1:N
3.CoordinatorLayout子控制元件的測量與布局
在特殊的情況下,如子控制元件需要處理寬高和布局的時候,那么交由Behavior內部的onMeasureChild與onLayoutChild方法來進行處理

4.CoordinatorLayout子控制元件的事件攔截與回應
對于事件的攔截與處理,如果子控制元件需要攔截并消耗事件,那么交由給Behavior內部的onInterceptTouchEvent與onTouchEvent方法進行處理

三.原始碼分析CoordinatorLayout子控制元件依賴互動功能原理
由于View的生命周期的開始是在onAttachedToWindow方法中,所以我們進入此方法尋找
1.我們在CoordinatorLayout類中找到onAttachedToWindow方法
發現它呼叫getViewTreeObserver,獲得ViewTreeObserver,然后呼叫了addOnPreDrawListener

- 關于
ViewTreeObserver:
ViewTreeObserver注冊一個觀察者來監聽視圖樹,當視圖樹的布局、視圖樹的焦點、視圖樹將要繪制、視圖樹滾動等發生改變時,ViewTreeObserver都會收到通知,ViewTreeObserver不能被實體化,可以呼叫View.getViewTreeObserver()來獲得 - 關于
dispatchOnPreDraw:通知觀察者繪制即將開始,如果其中的某個觀察者回傳true,那么繪制將會取消,并且重新安排繪制,如果想在View Layout或Viewhierarchy還未依附到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,然后就會回呼Behavior的onDependentViewRemoved,也就是上面原始碼分析的那部分

四.原始碼總結
總結一波:
當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還是有問題
