參考文章:php
http://androidwing.net/index.php/70java
https://www.jianshu.com/p/f7989a2a3ec2 (能夠看這篇文章)node
今天記錄下CoordinateLayout源碼學習的過程,下週又要在CoordinateLayout上面作文章了,實現暫時也還沒啥思路,不看源碼也不行了。android
關於CoordinateLayout的使用,這裏就不詳細說明了,不懂得能夠查閱這篇文章。app
這裏研究的是排除anchor做用的源碼ide
這裏先放上個一個簡單的例子吧:函數
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/coordinator" tools:context=".photo.TestActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:id="@+id/appbar" android:layout_height="220dp" android:background="#ffffff"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" app:layout_scrollFlags="scroll" android:orientation="vertical"> <View android:layout_width="match_parent" android:id="@+id/edit" android:background="#e29de3" android:layout_height="50dp"> </View> </LinearLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="500dp" android:background="#1d9d29" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <View android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="20dp" android:background="#d9ee33"> </View> <View android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="20dp" android:background="#2277dd"> </View> <View android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="20dp" android:background="#2277dd"> </View> <View android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="20dp" android:background="#2277dd"> </View> <View android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="20dp" android:background="#2277dd"> </View> <View android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="20dp" android:background="#2277dd"> </View> <View android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="20dp" android:background="#2277dd"> </View> <View android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="20dp" android:background="#dd2288"> </View> </LinearLayout> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout>
實現的效果以下:學習
代碼很簡單,經過一個appbar_scrolling_view_behavior
就可以直接實現聯動的效果,那麼首先就介紹一下解耦父View和子View的關鍵類:Behavior吧。ui
Behavior定義在CoordinateLayout內部:this
public static abstract class Behavior<V extends View> { ... //在子View獲取觸摸事件以前調用 public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) { return false; } //在Behavior開始時候調用 public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) { return false; } //依賴選擇 public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { return false; } //依賴選擇後的變化回調 public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) { return false; } ... //底下的方法跟NestedScrollingParent中的解釋一致 public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type) { if (type == ViewCompat.TYPE_TOUCH) { return onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes); } return false; } @Deprecated public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, @ScrollAxis int axes) { // Do nothing } public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type) { if (type == ViewCompat.TYPE_TOUCH) { onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, axes); } } @Deprecated public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target) { // Do nothing } public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, @NestedScrollType int type) { if (type == ViewCompat.TYPE_TOUCH) { onStopNestedScroll(coordinatorLayout, child, target); } } @Deprecated public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { // Do nothing } public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type) { if (type == ViewCompat.TYPE_TOUCH) { onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); } } @Deprecated public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) { // Do nothing } public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, @NestedScrollType int type) { if (type == ViewCompat.TYPE_TOUCH) { onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); } } public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, float velocityX, float velocityY, boolean consumed) { return false; } public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, float velocityX, float velocityY) { return false; } .... }
上述的方法中主要重要的爲兩個,layoutDependsOn()和onDependentViewChanged(),這裏詳細拿出來講一下吧。借用wing神寫的文章的話:
其實Behavior就是一個應用於View的觀察者模式,一個View跟隨者另外一個View的變化而變化,或者說一個View監聽另外一個View。在Behavior中,被觀察View 也就是事件源被稱爲denpendcy,而觀察View,則被稱爲child。
layoutDependsOn() 表明尋找被觀察View.
onDependentViewChanged() 被觀察View變化的時候回調用的方法.
這裏舉個例子,分別定義一個TextView,Button,EditText和ImageButton,讓ImageButton跟着EditText移動,EditText跟着Button移動,Button跟着TextView,因爲全部Behavior都基本同樣,惟一區別就是layoutDependsOn()
中的判斷,爲了節省篇幅,貼一個Button跟着TextView移動的Behavior:
public class TextBehavior extends CoordinatorLayout.Behavior<View> { ... @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { return dependency.getId==R.id.textView; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { child.setY(dependency.getY()); return true; } }
xml文件以下,省略無關代碼:
<android.support.design.widget.CoordinatorLayout ...> <TextView android:id="@+id/textView" android:layout_width="60dp" android:layout_height="60dp" android:background="#22ee22" android:gravity="center" android:text="TextView" /> <Button app:layout_behavior=".TextBehavior"/> <EditText app:layout_behavior=".ButtonBehavior"/> <ImageButton app:layout_behavior=".EditBehavior" /> </android.support.design.widget.CoordinatorLayout>
layoutDependsOn中dependency爲TextView的時候爲true,即咱們移動了TextView時候就會觸發onDependentViewChanged()方法,而後再onDependentViewChanged()操做Button的座標Y,而後依次觸發後續的View的移動。效果以下:
在CoordinateLayout中設置Behavior有兩種方式,一種是經過LayoutParam調用setBehavior()直接設置,一種是經過反射的方式,即經過在子View的xml中的layout_behavior來獲取對應的Behavior,這裏也都不敘述了。
OK,Behavior的介紹就到這,下面咱們就研究一下CoordinateLayout源碼吧, 構造函數中沒什麼關鍵信息,直接看onAttachTowindow(),在onAttachToWindow()中初始化了OnPreDrawListener類,在其回調onPreDraw()
實現了onChildViewsChanged(EVENT_PRE_DRAW)
,這裏因爲onPreDraw()
回調當視圖樹將要被繪製時,因此咱們先放着,接着繼續往下看onMeasure()
方法:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { prepareChildren(); ensurePreDrawListener(); ...//肯定寬高 final Behavior b = lp.getBehavior(); //是否由child自主測量寬高 if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed, childHeightMeasureSpec, 0)) { onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed, childHeightMeasureSpec, 0); } ... }
在測量方法中主要看prepareChildren()
,這裏面會實例化一個的實例變量:mDependencySortedChildren
private void prepareChildren() { mDependencySortedChildren.clear(); mChildDag.clear(); //a for (int i = 0, count = getChildCount(); i < count; i++) { final View view = getChildAt(i); final LayoutParams lp = getResolvedLayoutParams(view); lp.findAnchorView(this, view); //1 mChildDag.addNode(view); //b for (int j = 0; j < count; j++) { if (j == i) { continue; } final View other = getChildAt(j); //2 if (lp.dependsOn(this, view, other)) { if (!mChildDag.contains(other)) { // Make sure that the other node is added mChildDag.addNode(other); } // Now add the dependency to the graph mChildDag.addEdge(other, view); } } } //3 mDependencySortedChildren.addAll(mChildDag.getSortedList()); // We also need to reverse the result since we want the start of the list to contain // Views which have no dependencies, then dependent views after that Collections.reverse(mDependencySortedChildren); }
首先整理一下Demo中的對應關係:
分析該段代碼,咱們使用上面的自定義聯動的四個View的Demo進行分析,在a循環代碼段執行完畢後,在mChildDrag中的Map會依次記錄下各個View的依賴關係:
能夠看下mChildDrag的結構,以及對應方法:
public final class DirectedAcyclicGraph<T> { private final Pools.Pool<ArrayList<T>> mListPool = new Pools.SimplePool<>(10); private final SimpleArrayMap<T, ArrayList<T>> mGraph = new SimpleArrayMap<>(); ... public void addNode(@NonNull T node) { if (!mGraph.containsKey(node)) { mGraph.put(node, null); } } ... public void addEdge(@NonNull T node, @NonNull T incomingEdge) { ... ArrayList<T> edges = mGraph.get(node); if (edges == null) { // If edges is null, we should try and get one from the pool and add it to the graph edges = getEmptyList(); mGraph.put(node, edges); } // Finally add the edge to the list edges.add(incomingEdge); } ... }
在prepareChildren()
執行完畢後,mDependencySortedChildren按依賴關係排序,被依賴者排在前面,在上述Demo中即TextView,Button,EditText,ImageButton這樣排序。
看完onMeasure(....)後再看下onLayout(...)方法:
protected void onLayout(boolean changed, int l, int t, int r, int b) { final int layoutDirection = ViewCompat.getLayoutDirection(this); final int childCount = mDependencySortedChildren.size(); for (int i = 0; i < childCount; i++) { final View child = mDependencySortedChildren.get(i); if (child.getVisibility() == GONE) { // If the child is GONE, skip... continue; } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Behavior behavior = lp.getBehavior(); //是否代理給Behavior進行layout if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) { //由CoordinatorLayout進行layout onLayoutChild(child, layoutDirection); } } }
layout方法比較簡單,經過咱們在onMeasure()方法中獲取到的mDependencySortedChildren來進行layout。
接下來再說一下NestScrolling的問題吧,CoordinateLayout中實現了NestedScrollingParent2接口,意味着能夠進行聯動實現,在CoordinatorLayout源碼中有關於NestedScrollingParent2的方法皆由Behavior進行實現,Behavior也實現了NestedScrollingParent2接口,至關於CoordinatorLayout是Behavior的代理,這裏就不貼源碼了,因爲上一篇文章中分析過NestedScrollingParent和NestedScrollingChild的源碼。
再回來從新看下OnPreDrawListener類,在其回調onPreDraw()
實現了onChildViewsChanged(EVENT_PRE_DRAW)
的方法:
final void onChildViewsChanged(@DispatchChangeEvent final int type) { final int layoutDirection = ViewCompat.getLayoutDirection(this); final int childCount = mDependencySortedChildren.size(); final Rect inset = acquireTempRect(); final Rect drawRect = acquireTempRect(); final Rect lastDrawRect = acquireTempRect(); for (int i = 0; i < childCount; i++) { final View child = mDependencySortedChildren.get(i);//獲取對應的View final LayoutParams lp = (LayoutParams) child.getLayoutParams(); ... if (type != EVENT_VIEW_REMOVED) {//若是當前View所在視圖沒有變化,則進入下次循環 // Did it change? if not continue getLastChildRect(child, lastDrawRect); if (lastDrawRect.equals(drawRect)) { continue; } recordLastChildRect(child, drawRect); } ... // Update any behavior-dependent views for the change for (int j = i + 1; j < childCount; j++) { final View checkChild = mDependencySortedChildren.get(j); final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams(); final Behavior b = checkLp.getBehavior(); //通知依賴於的View進行對應的回調 if (b != null && b.layoutDependsOn(this, checkChild, child)) { ... final boolean handled; //回調Behavior對應的方法 switch (type) { case EVENT_VIEW_REMOVED: // EVENT_VIEW_REMOVED means that we need to dispatch // onDependentViewRemoved() instead b.onDependentViewRemoved(this, checkChild, child); handled = true; break; default: // Otherwise we dispatch onDependentViewChanged() handled = b.onDependentViewChanged(this, checkChild, child); break; } if (type == EVENT_NESTED_SCROLL) { // If this is from a nested scroll, set the flag so that we may skip // any resulting onPreDraw dispatch (if needed) checkLp.setChangedAfterNestedScroll(handled); } } } } releaseTempRect(inset); releaseTempRect(drawRect); releaseTempRect(lastDrawRect); }
上面就能夠看見咱們在Behavior中可能會重寫的方法layoutDependsOn()
以及onDependentViewChanged()
了。
總結一下Behavior的做用,對於Behavior而言,總共有兩個做用:
1.監聽CoordinatorLayout內部子View的滑動,從而進行滑動處理,即NestScrollingParent2以及NestScrollingChild聯動,主要由Behavior中的NestScrollingParent2接口實現。
2.一個子View監聽CoordinatorLayout內部另外一個子View的位置,大小等變換,主要由
layoutDependsOn()
以及onDependentViewChanged()
實現。
關於觸摸事件,在CoordinatorLayout中的onInterceptEvent(..)
以及onTouchEvent(..)
中也都是交由被Behavior處理,CoordinatorLayout中自己並無進行額外的處理,這裏也就不詳細分析了,有興趣能夠本身看下源碼。
有個問題,從上面的分析來看CoordinatorLayout至關於一個FrameLayout,並無進行什麼滑動的操做,那麼爲何AppBarLayout能夠滑動呢?這個問題一開始看完源碼後就直接出如今腦子裏面了,後來一想能夠經過Behavior委託給AppbarLayout進行滑動的處理啊,後面看AppbarLayout源碼的時候也證明這點。
那麼CoordinatorLayout源碼整體的思路大概清楚了,經過委託給Behavior的方式,CoordinatorLayout自己不作什麼處理,全部操做都由子View的自定義Behavior進行實現,充分解耦子View和CoordinatorLayout的邏輯,不得不佩服google大牛的擼代碼手法與思路。下篇文章須要分析一下AppBarLayout跟CoordinatorLayout的關係,理清對應滑動的流程。