关于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 。
- 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方法,进行相应的改变。