CoordinateLayout onMeasure流程分析

来源:互联网 发布:南通集数数据 编辑:程序博客网 时间:2024/06/01 10:02

先来看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 0
原创粉丝点击