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截取了嘛)

以上

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章