Android开发之CoordinatorLayout使用详解一

  发布日期:   2017-06-01
  最新修改:   2020-03-28
  阅读次数:   44 次

官网描述为: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. Always lay out this child after the dependent child is laid out, regardless * of child order.
  1. Call {@link #onDependentViewChanged} when the dependency view's layout or * position changes.

         *
         * @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的协作调用过程有一些些的帮助。


   转载规则

《Android开发之CoordinatorLayout使用详解一字》GajAngels 采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。