CoordiantorLayout与Behavior

2018-10-24 14:40:42 浏览数 (1)

CoordinatorLayout

CoordinatorLayout继承自FrameLayout,并且实现了NestedScrollingParent2接口用于接收嵌套滑动的事件。并且内部定义了一套Behavior机制,以供所有的子View可以通过Behavior的方式来达到共同参与嵌套滑动的目的。

CoordinatorLayout的使用

通过使用layout_anchor以及layout_anchorGravity来定义子View之间的位置关系,并且通过Behavior自定义子View之间React的交互依赖关系。

NestedScroll流程

  1. 当子View在XML中使用app:layout_behavior指定了对应的Behavior后,会在XML解析的时候将其通过反射机制生成一个Behavior对象保存在LayoutParams中 PS:自定义Behavior类需要定义一个接收Context以及AttributeSet的构造函数,否则无法对象
代码语言:javascript复制
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<Behavior>> constructors = sConstructors.get();
            if (constructors == null) {
                constructors = new HashMap<>();
                sConstructors.set(constructors);
            }
            Constructor<Behavior> c = constructors.get(fullName);
            if (c == null) {
                final Class<Behavior> clazz = (Class<Behavior>) context.getClassLoader()
                        .loadClass(fullName);
                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);
        }
    }
  1. 根据之前文章分析,在触发嵌套滑时,会回调父View的onStartNestedScroll以及onNestedScrollAccepted方法,遍历所有的子View
    • 判断子View是否隐藏,如果隐藏则不回调
    • 如果子View存在Behavior的话,则通过子View的onStartNestedScroll询问是否支持嵌套滑动,如果支持的话,则会在LayoutParams中设置accepted标志位
代码语言:javascript复制
@Override
    public boolean onStartNestedScroll(View child, View target, int axes, int type) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i  ) {
            final View view = getChildAt(i);
            if (view.getVisibility() == View.GONE) {
                // If it's GONE, don't dispatch
                continue;
            }
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
                        target, axes, type);
                handled |= accepted;
                lp.setNestedScrollAccepted(type, accepted);
            } else {
                lp.setNestedScrollAccepted(type, false);
            }
        }
        return handled;
    }
  1. 当子View开始滑动的时候,会回调onNestedPreScroll函数,遍历子View,判断子View是否有需要消费距离
    • 判断子View是否隐藏,若隐藏则不参与嵌套滑动
    • 判断子View是否允许嵌套滑动,若不允许则不参与嵌套滑动
    • 判断子View是否定义了Behavior,若没有则不允许参与嵌套滑动
    • 调用子View的onNestedPreScroll获取子View所消费的距离
    • 将消费最多的距离告知嵌套滑动的View
    • 若允许嵌套滑动的话,则调用onChildViewsChanged告知所有的子View当前有触发嵌套滑动的事件发生
代码语言:javascript复制
@Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int  type) {
        int xConsumed = 0;
        int yConsumed = 0;
        boolean accepted = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i  ) {
            final View view = getChildAt(i);
            if (view.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted(type)) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                mTempIntPair[0] = mTempIntPair[1] = 0;
                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair, type);

                xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
                        : Math.min(xConsumed, mTempIntPair[0]);
                yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
                        : Math.min(yConsumed, mTempIntPair[1]);

                accepted = true;
            }
        }

        consumed[0] = xConsumed;
        consumed[1] = yConsumed;

        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }
  1. onChildViewsChanged中,会遍历所有存在依赖关系的子View,即mDependencySortedChildren
    • mDependencySortedChildren中获取有依赖的子View,而mDependencySortedChildren是在prepareChildren中初始化完毕的
    • 在最后会遍历参与Dependency的View,通过layoutDependsOn判断是否两者有依赖关系,如果有的话,则通过onDependentViewChanged通知子View进行变化
代码语言:javascript复制
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);
            ...
            // 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);
                    }
                }
            }
        }
       ...
    }
  1. 当CoordinatorLayout对子View进行Measure以及Layout的过程中,Behavior也可以充当interpolator进行布局的处理,一切都与Dependency有关
代码语言:javascript复制
@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int layoutDirection = ViewCompat.getLayoutDirection(this);
        final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i  ) {
            final View child = mDependencySortedChildren.get(i);
            if (child.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Behavior behavior = lp.getBehavior();

            if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
                onLayoutChild(child, layoutDirection);
            }
        }
    }

     public void onLayoutChild(View child, int layoutDirection) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (lp.checkAnchorChanged()) {
            throw new IllegalStateException("An anchor may not be changed after CoordinatorLayout"
                      " measurement begins before layout is complete.");
        }
        if (lp.mAnchorView != null) {
            layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection);
        } else if (lp.keyline >= 0) {
            layoutChildWithKeyline(child, lp.keyline, layoutDirection);
        } else {
            layoutChild(child, layoutDirection);
        }
    }
  1. 其余的NestedPreScrollNestedStartScrollNestedStopScroll与上篇文章所述相同

0 人点赞