参考文章: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的关系,理清对应滑动的流程。