CoordinateLayout onMeasure流程分析

先来看CoordinateLayout:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        prepareChildren();
        ensurePreDrawListener();
        ...

开头就是两个关键方法 prepareChildren();和 ensurePreDrawListener();

 private void prepareChildren() {
        mDependencySortedChildren.clear();
        for (int i = 0, count = getChildCount(); i < count; i++) {
            final View child = getChildAt(i);

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

            mDependencySortedChildren.add(child);
        }
        // We need to use a selection sort here to make sure that every item is compared
        // against each other
        selectionSort(mDependencySortedChildren, mLayoutDependencyComparator);
    }

先来看这个方法里的

    LayoutParams getResolvedLayoutParams(View child) {
        final LayoutParams result = (LayoutParams) child.getLayoutParams();
        if (!result.mBehaviorResolved) {
            Class<?> childClass = child.getClass();
            DefaultBehavior defaultBehavior = null;
            while (childClass != null &&
                    (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) {
                childClass = childClass.getSuperclass();
            }
            if (defaultBehavior != null) {
                try {
                    result.setBehavior(defaultBehavior.value().newInstance());
                } catch (Exception e) {
                    Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() +
                            " could not be instantiated. Did you forget a default constructor?", e);
                }
            }
            result.mBehaviorResolved = true;
        }
        return result;
    }

用反射的方法来解析注解生成behavior的实例,然后设置到LayoutParams里头并返回,这个处理的就是在类的头部用注解定义behavior的那种

@DefaultBehavior(MyBehavior.class)
Class xxxxx{
xxxx
}

再来看
findAnchorView

        View findAnchorView(CoordinatorLayout parent, View forChild) {
            if (mAnchorId == View.NO_ID) {
                mAnchorView = mAnchorDirectChild = null;
                return null;
            }

            if (mAnchorView == null || !verifyAnchorView(forChild, parent)) {
                resolveAnchorView(forChild, parent);
            }
            return mAnchorView;
        }

顾名思义,就是得到anchor标记的那个view了。比如在xml里头这样定义anchorapp:layout_anchor="@id/main.appbar",那么在LayoutParams构造方法里头就有相应的

     mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_LayoutParams_layout_anchor,View.NO_ID);

来获取相应的属性

来详细分析一下findAnchorView里头的函数


        /**
         * Determine the anchor view for the child view this LayoutParams is assigned to.
         * Assumes mAnchorId is valid.
         */
        private void resolveAnchorView(final View forChild, final CoordinatorLayout parent) {
            mAnchorView = parent.findViewById(mAnchorId);//得到依赖的那个anchor view。。一句话就结束了。接下来就是对这个anchorview的合理性做检查
            ....
                View directChild = mAnchorView;
                //肯定不能锚定coordinateLayout嘛
                if (mAnchorView == parent) {
                     throw new IllegalStateException(
                            "View can not be anchored to the the parent CoordinatorLayout");
                }

                //沿着Anchorview的树向上检查
                for (ViewParent p = mAnchorView.getParent();
                        p != parent && p != null;
                        p = p.getParent()) {
                        //Anchor view也不能是依赖的view的子节点,要不然到底是谁决定谁呢?循环依赖了就
                        if (p == forChild) {
                        throw new IllegalStateException(
                                "Anchor must not be a descendant of the anchored view");
                        }
                    }
                    if (p instanceof View) {
                        directChild = (View) p;
                    }
                }
                mAnchorDirectChild = directChild;
                //记录描定的那个孩子???
            } 
        }

回到prepareChildren这个函数,我们找完了anchor的view并且记录在layoutparams里头之后,把view添加到mDependencySortedChildren这个ArrayList里头,并且紧接着进行排序
selectionSort(mDependencySortedChildren, mLayoutDependencyComparator);
顾名思义,就是一个选择排序,传入待排序数组和一个比较器。
先看一眼比较器:


    final Comparator<View> mLayoutDependencyComparator = new Comparator<View>() {
        @Override
        public int compare(View lhs, View rhs) {
            if (lhs == rhs) {
                return 0;
            } else if (((LayoutParams) lhs.getLayoutParams()).dependsOn(
                    CoordinatorLayout.this, lhs, rhs)) 
{
                return 1;
            } else if (((LayoutParams) rhs.getLayoutParams()).dependsOn(
                    CoordinatorLayout.this, rhs, lhs)) {
                return -1;
            } else {
                return 0;
            }
        }
    };

如果左边view依赖右边就返回1,否则返回-1,就是按照依赖的顺序进行选择排序嘛。

看看那个依赖函数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
                    || (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
        }

你看,之前保存的 mAnchorDirectChild就派上用场了,直接检查自己所保存的依赖的那个Anchor 的 view和当前传入的dependency是否一致,或者是在Behavior里头显式地重写layoutDependsOn来定义自己所依赖的view。

再看一样选择排序

    private static void selectionSort(final List<View> list, final Comparator<View> comparator) {
        if (list == null || list.size() < 2) {
            return;
        }

        final View[] array = new View[list.size()];
        list.toArray(array);
        final int count = array.length;

        for (int i = 0; i < count; i++) {
            int min = i;

            for (int j = i + 1; j < count; j++) {
                if (comparator.compare(array[j], array[min]) < 0) {
//关键部分:比较器代码相应代码如下
// if (((LayoutParams) rhs.getLayoutParams()).dependsOn(
//                    CoordinatorLayout.this, rhs, lhs)) {
//               return -1;
//           }

                    min = j;
                }
            }

            if (i != min) {
                // We have a different min so swap the items
                final View minItem = array[min];
                array[min] = array[i];
                array[i] = minItem;
            }
        }

        // Finally add the array back into the collection
        list.clear();
        for (int i = 0; i < count; i++) {
            list.add(array[i]);
        }
    }
}

当比较器的结果小于0的时候,就是右侧的参数依赖于左侧的那个参数,就返回-1,记录左侧的参数为min。综上,所以被依赖的view排在数组的前面,依赖他人的view排在数组的后面

总结起来,做完选择排序之后的mDependencySortedChildren会保证把被依赖的view排在最前面,而把依赖别人的view排在后面,第二次序才是view的添加次序(就是一开始被add加入mDependencySortedChildren的数组的次序)。

注意,这个mDependencySortedChildren是相当关键的一个数组,它成了后面几乎所有的遍历子view操作的那个顺序。其实也很好理解,对他人进行依赖的view必然是随着被依赖的那个view的变化而变化,那么我们自然要优先处理那个自变量,然后再处理因变量嘛。

接下来看
ensurePreDrawListener

    /**
     * Add or remove the pre-draw listener as necessary.
     */
    void ensurePreDrawListener() {
        boolean hasDependencies = false;
        final int childCount = getChildCount();
        //检查是否有依赖的存在
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            if (hasDependencies(child)) {
                hasDependencies = true;
                break;
            }
        }

        if (hasDependencies != mNeedsPreDrawListener) {
            if (hasDependencies) {
                addPreDrawListener();
            } else {
                removePreDrawListener();
            }
        }
    }

这段比较容易:检查是否有依赖的存在,有的话就调用addPreDrawListener(); 预绘制的监听器

    /**
     * Add the pre-draw listener if we're attached to a window and mark that we currently
     * need it when attached.
     */
    void addPreDrawListener() {
        if (mIsAttachedToWindow) {
            // Add the listener
            if (mOnPreDrawListener == null) {
                mOnPreDrawListener = new OnPreDrawListener();
            }
            final ViewTreeObserver vto = getViewTreeObserver();
            vto.addOnPreDrawListener(mOnPreDrawListener);
        }

        // Record that we need the listener regardless of whether or not we're attached.
        // We'll add the real listener when we become attached.
        mNeedsPreDrawListener = true;
    }

明显的,ViewTreeObserver加入一个监听器,在draw之前都会进行调用。那么自然是看看监听器到底写了什么鬼了

    class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
        @Override
        public boolean onPreDraw() {
            dispatchOnDependentViewChanged(false);
            return true;
        }
    }

跟踪关键函数,我们又到了一个重要的地方

    void dispatchOnDependentViewChanged(final boolean fromNestedScroll) {
        final int layoutDirection = ViewCompat.getLayoutDirection(this);
        final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);//按照依赖的先后顺序取出子view!!
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();//取出子view的lp

            // Check child views before for anchor
            for (int j = 0; j < i; j++) {
                final View checkChild = mDependencySortedChildren.get(j);//内循环,取出当前位置之前的所有的view(就是有可能对他们产生依赖的view,因为排在他前面嘛)

                if (lp.mAnchorDirectChild == checkChild) {//如果是Anchor依赖
                    offsetChildToAnchor(child, layoutDirection);//对当前view进行调整(我们知道anchor这种属性是随着被依赖的view变化的)
                }
            }

            // Did it change? if not continue
            //看看当前的view的Rect和上一次记录的Rect是否一致(就是上下左右四个值是不是一样啦)
            final Rect oldRect = mTempRect1;
            final Rect newRect = mTempRect2;
            getLastChildRect(child, oldRect);//从这个view的LP里头得到上一次记录的这个view的Rect值,就是上下左右的值啦
            getChildRect(child, true, newRect);
            if (oldRect.equals(newRect)) {//比较是否有位置变化等
                continue; //没变化就跳过后面步骤,直接检查下一个view的依赖
            }
            recordLastChildRect(child, newRect);//把View当前的Rect记录在这个view自己的LayoutParams里头(供下一次取出和比对)

            // Update any behavior-dependent views for the change
            //如果进行anchor调整之后,当前view发生了变化,那么就开始向后进行循环调整(处在后面的view都有可能对当前view产生依赖嘛)
            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)) {
                //如果后面的view对当前view有依赖,那么要随着当前view的变化而变化
                    if (!fromNestedScroll && checkLp.getChangedAfterNestedScroll()) {
                        // If this is not from a nested scroll and we have already been changed
                        // from a nested scroll, skip the dispatch and reset the flag
                        checkLp.resetChangedAfterNestedScroll();
                        continue;
                    }

                    final boolean handled = b.onDependentViewChanged(this, checkChild, child);
//回调后面的view的onDependentViewChanged方法(自己依赖的view发生变化啦!)
                    if (fromNestedScroll) {
                        // If this is from a nested scroll, set the flag so that we may skip
                        // any resulting onPreDraw dispatch (if needed)
                        checkLp.setChangedAfterNestedScroll(handled);
                    }
                }
            }
        }
    }

前面有段注释如是说:
* Usually run as part of the pre-draw step when at least one child view has a reported
* dependency on another view. This allows CoordinatorLayout to account for layout
* changes and animations that occur outside of the normal layout pass.
*
* It can also be ran as part of the nested scrolling dispatch to ensure that any offsetting
* is completed within the correct coordinate window.‘

这个函数除了用在这个绘制前监听之外,如果看看nestedScroll相关代码,里头也大量用到了这个函数。
总结一下上面这个函数,是为了处理anchor这个属性而存在的。从前到后以依赖的顺序取出子view,然后对每个view检查对前面的view是否存在anchor依赖,如果是就调整来配合anchor的view(offsetChildToAnchor函数)

在根据Anchor调整完毕之后,View位置大小就基本确定下来了,这个时候就接着检查这个view的lp所记录的Rect和当前的位置是否发生变化,如果是,那么就要遍历自己之后的元素看有没有元素依赖自己,有的话要回调他们相应的behavior.onDependentViewChanged 方法。

以上方法会在每次绘制前调用,保证了每个View变化的时候,依赖他的view能跟着一起变化

可以看到这个依赖的实现相当地粗暴简单,没有用到什么图论之类的知识,直接暴力循环了。。。

现在回到最最开始的onMeasure方法,我们上面搞了半天分析了两个函数
prepareChildren();
ensurePreDrawListener();

接下来继续向下分析onMeasure的相关部分

...
        for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
...
            final Behavior b = lp.getBehavior();
            if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
                    childHeightMeasureSpec, 0)) {
                onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
                        childHeightMeasureSpec, 0);
            }
       }

可以看到以依赖的先后顺序取出子view,并开始调用子view的lp的behavior的相应onMeasureChild,如果这里返回true,那么正常的onMeasureChild流程就不会进行调用了(被Behavior截取了嘛)

以上

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值