Android-CoordinatorLayout.……

CoordinatorLayout原理研究

2018-09-05  本文已影响4人  以帅服人的珂哥

一、CoordinatorLayout介绍

就像其名字一样,CoordinatorLayout作用在于协调子 View 之间的联动,在其出现之前,如要实现多 View 之间的联动,需持有所有 View 的引用,难免各种事件处理、计算且高度耦合,写法难度大。
CoordinatorLayout提供了一种清爽的方式,解决了 View 之间联动问题。
CoordinatorLayout的基本用法网上遍地都是,不再赘述。主要好奇于它的强大与丝滑,想弄明白原理,参考了部分博客,然后观摩一下源码,在此记录一下。

二、重中之重Behavior

抽象的来讲,就是协调者布局中子 view 应当遵守的行为(个人理解)。
源码中的注释:

A {@link Behavior} that the child view should obey.

乍一听可能懵逼,但明白其作用于原理之后,就能逐渐领悟这句话的意义。

1、首先Behavior作用

CoordinatorLayout(以下简写Co)中定义了两个概念 Child 与Dependency,child 就是Co 中的 子 View,Dependency就是被 child 依赖的 其他 子View,当Dependency发生某些行为(如拖动),就会通知 child 以便执行相应的改变。
而Behavior就提供了:

        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
            // We depend on any AppBarLayouts
            return dependency instanceof AppBarLayout;
        }

        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
                View dependency) {
            offsetChildAsNeeded(parent, child, dependency);
            return false;
        }

layoutDependsOn方法为依赖关系true 表示依赖,意思是声明此 behavior 的 view依赖 Co 的自view 中所有instanceof AppBarLayout的 view。
onDependentViewChanged方法就是dependency发生改变的回调,意思就是dependency也就是AppBarLayout发生改变时候执行offsetChildAsNeeded方法(此方法无外乎是一些 View 的移动啥的)。
至此Co通过 behavior 完成了一次协调作用。

(behavior的作用远不止此,且其在 Co 中权限很高,有机会在做进一步探讨)

2、Behavior原理

1.Behavior是个啥,怎么初始化的?
点开 Co 源码可发现Behavior是Co的一个抽象内部类

 public static abstract class Behavior<V extends View> {
  ...
 }

进一步搜索,发现 Behavior为 Co 的LayoutParams的成员变量

 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        /**
         * A {@link Behavior} that the child view should obey.
         */
        Behavior mBehavior;
        ...
        LayoutParams(Context context, AttributeSet attrs) {
           ...
            if (mBehaviorResolved) {
                mBehavior = parseBehavior(context, attrs, a.getString(
                        R.styleable.CoordinatorLayout_Layout_layout_behavior));
            }
           ...
}

并在LayoutParams中调用parseBehavior初始化。
(还有一种通过注解初始化,有兴趣可自行了解一下:getResolvedLayoutParams)

2.分析如何建立child 与dependency依赖关系
打开 Co 源码 command+F 搜索layoutDependsOn,顺藤摸瓜看都有何处调用:
发现在onChildViewsChanged与LayoutParams 中的方法dependsOn中调用

        /**
         * Check if an associated child view depends on another child view of the CoordinatorLayout.
         *
         * @param parent the parent CoordinatorLayout
         * @param child the child to check
         * @param dependency the proposed dependency to check
         * @return true if child depends on dependency
         */
        boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
            return dependency == mAnchorDirectChild
                    || shouldDodge(dependency, ViewCompat.getLayoutDirection(parent))
                    || (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
        }

onChildViewsChanged先忽略,再搜dependsOn何时调用:

 private void prepareChildren() {
        mDependencySortedChildren.clear();
        mChildDag.clear();

        for (int i = 0, count = getChildCount(); i < count; i++) {
            final View view = getChildAt(i);

            final LayoutParams lp = getResolvedLayoutParams(view);
            lp.findAnchorView(this, view);

            mChildDag.addNode(view);

            // Now iterate again over the other children, adding any dependencies to the graph
            for (int j = 0; j < count; j++) {
                if (j == i) {
                    continue;
                }
                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);
                    }
                    // Now add the dependency to the graph
                    mChildDag.addEdge(other, view);
                }
            }
        }

        // Finally add the sorted graph list to our list
        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);
    }

prepareChildren中可以看出通过遍历Co 中所有的 View,把所有的依赖关系维护到mChildDag中。
mChildDag为DirectedAcyclicGraph类型可简单理解为可以保存对应关系的容器。

而prepareChildren在onMeasure第一行调用

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        prepareChildren();
        ...
    }

这下就了然了,概括的说 就是 Co 在 onMeasure的时候首先会通过 behavior 把所有的 child 与Dependency的依赖关系保存起来(保存到mChildDag中)。

3.如何回调onDependentViewChanged
老方法 command+F,发现最终在两处调用
onChildViewsChanged与dispatchDependentViewsChanged
后者为 public 提供给外部调用的方法,所以不用 care。

    final void onChildViewsChanged(@DispatchChangeEvent final int type) {
         ......
        for (int i = 0; i < childCount; i++) {
             ......
            // 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();

                if (b != null && b.layoutDependsOn(this, checkChild, child)) {
                    ......
                    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;
                    }

                    ......
                    }
                }
            }
        }

    ......      
    }

代码过长,部分省略,果然是用脚指头都能想到的循环遍历调用。
继续看onChildViewsChanged的调用:

    @Override
    public void onNestedScroll(...){
         ......  
        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }

    @Override
    public void onNestedPreScroll(...){
         ......  
        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }

   @Override
    public boolean onNestedFling(.....){
    ......
    }
 class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
        @Override
        public boolean onPreDraw() {
            onChildViewsChanged(EVENT_PRE_DRAW);
            return true;
        }
    }
       @Override
        public void onChildViewRemoved(View parent, View child) {
            onChildViewsChanged(EVENT_VIEW_REMOVED);
            if (mOnHierarchyChangeListener != null) {
                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
            }
        }

无非是当页面 滑动,view绘制之前,与 view removed的时候,回回调onDependentViewChanged。这也正好解释了 AppBarLayout 与ScrollingViewBehavior滑动联动的原理。

三、总结

粗略的探讨了CoordinatorLayout 通过 behavior 实现协调子 View 的基本原理。
通过自定义 behavior 可爽快的实现一些联动效果。
但 通过源码behavior在CoordinatorLayout中作用远不止此,且behavior权限很高,有机会再分析其他作用。

参考连接:
CoordinatorLayout的使用如此简单
一步一步深入理解CoordinatorLayout

上一篇 下一篇

猜你喜欢

热点阅读