CoordinatorLayout三部曲学习之二:CoordinateLayout源码学习

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

相关文章
相关标签/搜索