美文网首页
CoordiantorLayout与Behavior

CoordiantorLayout与Behavior

作者: None_Ling | 来源:发表于2018-07-03 16:27 被阅读51次

    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的构造函数,否则无法对象
    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标志位
    @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当前有触发嵌套滑动的事件发生
    @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进行变化
    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有关
    @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与上篇文章所述相同

    相关文章

      网友评论

          本文标题:CoordiantorLayout与Behavior

          本文链接:https://www.haomeiwen.com/subject/dsfjuftx.html