对CoordinatorLayout.Behavior调用的一点理解

本文深入解析了 CoordinatorLayout 中 Behavior 的工作原理,包括实例化、依赖判断和响应视图变化的过程。Behavior 的关键在于 layout_dependson 属性,通过反射实例化并连接相关视图。CoordinatorLayout 通过遍历子视图,判断从属关系并调用 onDependentViewChanged 方法,实现在滑动事件中的联动效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于CoordinatorLayout的使用
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0717/3196.html
这篇文章是翻译的,CoordinatorLayout的各种用法基本都讲了。


以下是我个人的理解。
在使用CoordinatorLayout时,如果需要某一个view主导滑动,要添加这样一个属性

app:layout_behavior="@string/appbar_scrolling_view_behavior"

这个属性后面传入的字符串是类名,@string/appbar_scrolling_view_behavior对应的是
AppBarLayout.ScrollingViewBehavior这个内部类。
这个内部类继承了ViewOffsetBehavior,泛型是任何view,
ViewOffsetBehavior又继承了CoordinatorLayout.Behavior。

app:layout_behavior这个属性跟Behavior是怎么联系起来的呢?

1、Behavior的实例化

在CoordinatorLayout的内部类,LayoutParams里面有以下代码

LayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray a = context.obtainStyledAttributes(attrs, styleable.CoordinatorLayout_LayoutParams);
            ...
            //先判断一下layout_behavior有没有值
            this.mBehaviorResolved = a.hasValue(styleable.CoordinatorLayout_LayoutParams_layout_behavior);
            if(this.mBehaviorResolved) {
            //通过parseBehavior()返回了一个Behavior
                this.mBehavior = CoordinatorLayout.parseBehavior(context, attrs, a.getString(styleable.CoordinatorLayout_LayoutParams_layout_behavior));
            }
...
        }

在这里,parseBehavior()这个方法通过反射实例化了mBehavior 。

  1. AppBarLayout对Bahavior的实现
app:layout_behavior="@string/appbar_scrolling_view_behavior"

这个字符串对应的类——AppBarLayout.ScrollingViewBehavior。

ScrollingViewBehavior重写了behavior的layoutDependsOn()。

public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
            return dependency instanceof AppBarLayout;
        }

这是要注意的第一个方法,判断是否依赖,可以看到它要求dependency(从属)的view必须是AppBarLayout。layoutDependsOn的返回值决定了我们接下来的滑动处理等的方法是否能够执行。

接下来是onDependentViewChanged()方法,当从属的view发生了变化,同样是重写的。在这里对view进行相应的改变,这个方法里面具体是啥不是现在的重点,分析就省略了。

public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
            android.support.design.widget.CoordinatorLayout.Behavior behavior = ((android.support.design.widget.CoordinatorLayout.LayoutParams)dependency.getLayoutParams()).getBehavior();
            if(behavior instanceof AppBarLayout.Behavior) {
                int appBarOffset = ((AppBarLayout.Behavior)behavior).getTopBottomOffsetForScrollingSibling();
                int expandedMax = dependency.getHeight() - this.mOverlayTop;
                int collapsedMin = parent.getHeight() - child.getHeight();
                if(this.mOverlayTop != 0 && dependency instanceof AppBarLayout) {
                    int scrollRange = ((AppBarLayout)dependency).getTotalScrollRange();
                    this.setTopAndBottomOffset(AnimationUtils.lerp(expandedMax, collapsedMin, (float)Math.abs(appBarOffset) / (float)scrollRange));
                } else {
                    this.setTopAndBottomOffset(dependency.getHeight() - this.mOverlayTop + appBarOffset);
                }
            }

            return false;
        }

看到这里,我还是没明白Behaivor到底是怎么回事,又转到CoordinatorLayout里面去看,我发现自己走弯路了。我想搞明白父类是怎么下达指令的,现在却在看子类是怎么执行的。

**

3、CoorDinatorLayout对Behavior的调用

**

void dispatchOnDependentViewChanged(boolean fromNestedScroll) {
        int layoutDirection = ViewCompat.getLayoutDirection(this);
        int childCount = this.mDependencySortedChildren.size();
//这里在遍历CoorDinatorLayout这个布局里面所有的子view
        for(int i = 0; i < childCount; ++i) {
            View child = (View)this.mDependencySortedChildren.get(i);
            CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)child.getLayoutParams();

            for(int oldRect = 0; oldRect < i; ++oldRect) {
                View newRect = (View)this.mDependencySortedChildren.get(oldRect);
                if(lp.mAnchorDirectChild == newRect) {
                    this.offsetChildToAnchor(child, layoutDirection);
                }
            }

            Rect var14 = this.mTempRect1;
            Rect var15 = this.mTempRect2;
            this.getLastChildRect(child, var14);
            this.getChildRect(child, true, var15);
            //检查view是否有变化
            if(!var14.equals(var15)) {
                this.recordLastChildRect(child, var15);
//从下一个子view开始,再次遍历
                for(int j = i + 1; j < childCount; ++j) {
                    View checkChild = (View)this.mDependencySortedChildren.get(j);
 //获取layoutpParams,还记得在layoutParame()里面初始化了Behavior么
                    CoordinatorLayout.LayoutParams checkLp = (CoordinatorLayout.LayoutParams)checkChild.getLayoutParams();
//获取到Behavior
                    CoordinatorLayout.Behavior b = checkLp.getBehavior();
//调用layoutDependsOn来判断这两个view的从属关系是否正确
                    if(b != null && b.layoutDependsOn(this, checkChild, child)) {
                        if(!fromNestedScroll && checkLp.getChangedAfterNestedScroll()) {
                            checkLp.resetChangedAfterNestedScroll();
                        } else {
                        //此时,调用了onDependentViewChanged方法,进行相应的改变
                            boolean handled = b.onDependentViewChanged(this, checkChild, child);
                            if(fromNestedScroll) {
                                checkLp.setChangedAfterNestedScroll(handled);
                            }
                        }
                    }
                }
            }
        }

    }

看到这里,总算明白了,Behavior是如何让两个view联系起来的,还是CoordinatorLayout的功劳,只要是在CoordinatorLayout布局里面的view,都会被遍历,来判断与另一个view的相互关系。
接下来搞定dispatchOnDependentViewChanged()这个方法如何调用就可以了。

这个方法猜测一下,应该是当view发生变化的时候调用,不是某一个指定的view,也不是coordinatorLayout布局,而是整个视图树中全局事件改变,在CoordinatorLayout中注册了一个ViewTreeObserver,

public void onAttachedToWindow() {
        super.onAttachedToWindow();
        this.resetTouchBehaviors();
        if(this.mNeedsPreDrawListener) {
            if(this.mOnPreDrawListener == null) {
                this.mOnPreDrawListener = new CoordinatorLayout.OnPreDrawListener();
            }

            ViewTreeObserver vto = this.getViewTreeObserver();
//添加了一个listener            vto.addOnPreDrawListener(this.mOnPreDrawListener);
        }

        this.mIsAttachedToWindow = true;
    }

在这个listener里面调用了dispatchOnDependentViewChanged();

 class OnPreDrawListener implements android.view.ViewTreeObserver.OnPreDrawListener {
        OnPreDrawListener() {
        }

        public boolean onPreDraw() {
            CoordinatorLayout.this.dispatchOnDependentViewChanged(false);
            return true;
        }
    }

这里,参数传递的是false。参数名是fromNestedScroll。判断是不是来自可嵌套的ScrollView;所以在使用时,如果不是可嵌套的ScrollView,就无法实现联动。

最后,来捋一遍调用过程
1、在onAttachedToWindow()里注册了一个ViewTreeObserver,用来监听view的变化。当view发生变化时调用dispatchOnDependentViewChanged();
2、在dispatchOnDependentViewChanged()里遍历CoorDinatorLayout这个布局里面所有的子view,获取到Behavior并且调用layoutDependsOn来判断这两个view的从属关系是否正确。
3、当view的从属关系正确时,调用了onDependentViewChanged方法,进行相应的改变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值