Android開發之CoordinatorLayout使用詳解一

主頁:http://cherylgood.cn/c/Android開發之CoordinatorLayout使用詳解一.php

官網描述爲:CoordinatorLayout是一個增強版的FrameLayout(繼承自ViewGroup)

用途:

1、作爲應用的頂層視圖。

2、作爲一個可以指定子View之間相互作用的容器,通過給CoordinatorLayout的子View指定CoordinatorLayout.Behavior 來定義子view之間的相互作用。(你可以想象成:CoordinatorLayout相當於在兩個View之間充當中介,這樣子的好處就是兩個view之間的耦合度降低了,只需要跟coordinatorLayout打交到即可,而CoordinatorLayout.Behavior 相當於兩個view之間的協議,即通過怎樣的規則來約束雙方的行爲。)

設計概念:

  1. CoordinatorLayout:CoordinatorLayout 作爲最頂層視圖,將負責管理所有的子view,使其內部的子View彼此間產生一種聯繫。這個聯繫通過Behavior來實現(包括了滑動狀態的處理以及View狀態的處理)。
  2. AppBarLayout:AppBarLayout 繼承自限性佈局,作爲增強版的線性佈局,他增加了對滑動手勢的處理。
  3. Behavior:Behavior 是google新提出的,能夠讓你以非侵入式的方式去處理目標View和其他View的交互行爲。Behavior需要設置在觸發事件(比如滾動)的view上,且這個View必須是CoordinatorLayout的第一層級下的子view,否則沒有效果,因爲Behavior的初始化是在CoordinatorLayout的LayoutParams中通過反射完成的。
    Behavior實例化方式:1、通過app:layout_behavior聲明 ;2、在你的自定義View類上添加@DefaultBehavior(MyBehavior.class);
  4. Behavior只是個接口,其調用是由NestedScrollingParent與NestedScrollingChild接口負責調用。

接下來我們通過閱讀部分源碼進行學習:

首先,我們從兩個view是如何通過coordinatorlayout產生關聯來入手;看代碼

LayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);

            final TypedArray a = context.obtainStyledAttributes(attrs,
                    R.styleable.CoordinatorLayout_Layout);

            this.gravity = a.getInteger(
                    R.styleable.CoordinatorLayout_Layout_android_layout_gravity,
                    Gravity.NO_GRAVITY);
            mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor,
                    View.NO_ID);
            this.anchorGravity = a.getInteger(
                    R.styleable.CoordinatorLayout_Layout_layout_anchorGravity,
                    Gravity.NO_GRAVITY);

            this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline,
                    -1);

            insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0);
            dodgeInsetEdges = a.getInt(
                    R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0);
            mBehaviorResolved = a.hasValue(
                    R.styleable.CoordinatorLayout_Layout_layout_behavior);
            if (mBehaviorResolved) {
                mBehavior = parseBehavior(context, attrs, a.getString(
                        R.styleable.CoordinatorLayout_Layout_layout_behavior));
            }
            a.recycle();

            if (mBehavior != null) {
                // If we have a Behavior, dispatch that it has been attached
                mBehavior.onAttachedToLayoutParams(this);
            }
        }

mBehaviorResolved = a.hasValue(
        R.styleable.CoordinatorLayout_Layout_layout_behavior);
if (mBehaviorResolved) {
    mBehavior = parseBehavior(context, attrs, a.getString(
            R.styleable.CoordinatorLayout_Layout_layout_behavior));
}
這幾句我們可以看到。
mBehaviorResolved 是個boolean 變量,如果
R.styleable.CoordinatorLayout_Layout_layout_behavior CoordinatorLayout的
layout_behavior這個字段設置有值,
1、mBehaviorResolved = true -》調用parseBehavior方法,將所需參數傳入通過java的反射技術返回一個Behavior實例。
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
        if (TextUtils.isEmpty(name)) {
            return null;
        }

        final String fullName;
        if (name.startsWith(".")) {
            // Relative to the app package. Prepend the app package name.
            fullName = context.getPackageName() + name;
        } else if (name.indexOf('.') >= 0) {
            // Fully qualified package name.
            fullName = name;
        } else {
            // Assume stock behavior in this package (if we have one)
            fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
                    ? (WIDGET_PACKAGE_NAME + '.' + name)
                    : name;
        }

        try {
            Map<String, Constructor> constructors = sConstructors.get();
            if (constructors == null) {
                constructors = new HashMap<>();
                sConstructors.set(constructors);
            }
            Constructor c = constructors.get(fullName);
            if (c == null) {
                final Class clazz = (Class) Class.forName(fullName, true,
                        context.getClassLoader());
                c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
                c.setAccessible(true);
                constructors.put(fullName, c);
            }
            return c.newInstance(context, attrs);
        } catch (Exception e) {
            throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
        }
    }
通過這一段我們可以知道,最後是通過調用Behavior的參數爲(context,attrs)的構造函數進行實例化。
實例化出Behavior之後我們會調用behavior的onAttachedToLayoutParams方法 將LayoutParams的實例對象傳進去mBehavior.onAttachedToLayoutParams(this);
mBehavior.onAttachedToLayoutParams是一個當LayoutParams被實例化後的回調方法。

通過這裏,我們的
CoordinatorLayout就能夠跟用layout_behavior標識的子View產生聯繫。

當子View發生變化時,CoordinatorLayout又是如何處理的的,請看下面代碼:
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);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
                // Do not try to update GONE child views in pre draw updates.
                continue;
            }

            // Check child views before for anchor
            for (int j = 0; j < i; j++) {
                final View checkChild = mDependencySortedChildren.get(j);

                if (lp.mAnchorDirectChild == checkChild) {
                    offsetChildToAnchor(child, layoutDirection);
                }
            }

            // Get the current draw rect of the view
            getChildRect(child, true, drawRect);

            // Accumulate inset sizes
            if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) {
                final int absInsetEdge = GravityCompat.getAbsoluteGravity(
                        lp.insetEdge, layoutDirection);
                switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) {
                    case Gravity.TOP:
                        inset.top = Math.max(inset.top, drawRect.bottom);
                        break;
                    case Gravity.BOTTOM:
                        inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top);
                        break;
                }
                switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.LEFT:
                        inset.left = Math.max(inset.left, drawRect.right);
                        break;
                    case Gravity.RIGHT:
                        inset.right = Math.max(inset.right, getWidth() - drawRect.left);
                        break;
                }
            }

            // Dodge inset edges if necessary
            if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) {
                offsetChildByInset(child, inset, layoutDirection);
            }

            if (type == EVENT_PRE_DRAW) {
                // 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();

                if (b != null && b.layoutDependsOn(this, checkChild, child)) {
                    if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
                        // If this is from a pre-draw and we have already been changed
                        // from a nested scroll, skip the dispatch and reset the flag
                        checkLp.resetChangedAfterNestedScroll();
                        continue;
                    }

                    final boolean handled;
                    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);
    }
我們可以看到文檔說明,大概意思是當子view發生變化會調用該方法。該方法會遍歷所有的子view,
然後調用如下代碼,layoutDependsOn()這個方法是做什麼的呢?我們接下來看下該方法。
if (b != null && b.layoutDependsOn(this, checkChild, child)) {
                    if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
                        // If this is from a pre-draw and we have already been changed
                        // from a nested scroll, skip the dispatch and reset the flag
                        checkLp.resetChangedAfterNestedScroll();
                        continue;
                    }

                    final boolean handled;
                    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);
                    }
                }
/**
         * Determine whether the supplied child view has another specific sibling view as a
         * layout dependency.
         *
         *

This method will be called at least once in response to a layout request. If it * returns true for a given child and dependency view pair, the parent CoordinatorLayout * will:

*
  1. *
  2. Always lay out this child after the dependent child is laid out, regardless * of child order.
  3. *
  4. Call {@link #onDependentViewChanged} when the dependency view's layout or * position changes.
  5. *
*
         * @param parent the parent view of the given child
         * @param child the child view to test
         * @param dependency the proposed dependency of child
         * @return true if child's layout depends on the proposed dependency's layout,
         *         false otherwise
         *
         * @see #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)
         */
        public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
            return false;
        }
這個方法,大概意思是如果我們返回true,說明當前發生變化的子view發生變化時。也就是該方法決定我們用
layout_behavior標識的view是否應該做出相應的變化。默認返回false,該方法需要我們在創建自己的Behavior時重寫。
當返回true的話,可以看到會調用
b.onDependentViewRemoved(this, checkChild, child);
handled = b.onDependentViewChanged(this, checkChild, child);

爲了更好理解這兩句代碼,我們舉個例子,假設有ViewA和ViewB ,當ViewB發生移動時,ViewA要向反方向移動。
1、當ViewB被移除時會調用
b.onDependentViewRemoved(this, checkChild, child); 
2、當ViewB發生變化時,會調用
handled = b.onDependentViewChanged(this, checkChild, child);
那麼我們就可以在
b.onDependentViewChanged裏面寫我們的功能代碼了。

通過以上的分析,希望能幫到大家對CoordinatorLayout的協作調用過程有一些些的幫助。


http://cherylgood.cn/c/Android開發之CoordinatorLayout使用詳解二.php


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