上一篇自定义CoordinatorLayout.Behavior,总结了CoordinatorLayout.Behavior的使用和自定义。
本文主要分析
-
Behavior
的构造函数 - Property Behavior的关键方法
layoutDependsOn
onDependentViewChanged
1 Behavior的构造函数
以上一篇自定义CoordinatorLayout.Behavior的Property Behavior TextViewPropertyBehavior为例,两个参数的构造函数的调用栈如下:
LayoutInflater.inflate (LayoutInflater.java:396)
LayoutInflater.inflate (LayoutInflater.java:489)
LayoutInflater.rInflate (LayoutInflater.java:748)
CoordinatorLayout.generateLayoutParams (CoordinatorLayout.java:92)
CoordinatorLayout.generateLayoutParams (CoordinatorLayout.java:1435)
CoordinatorLayout$LayoutParams.<init> (CoordinatorLayout.java:2288)
CoordinatorLayout.parseBehavior (CoordinatorLayout.java:585)
Constructor.newInstance (Constructor.java:417)
Constructor.constructNative (Constructor.java:-2)
TextViewPropertyBehavior.<init> (TextViewPropertyBehavior.java:25)
可以看出加载布局的时候,CoordinatorLayout.parseBehavior
解析XML中的app:layout_behavior
的值(即TextViewPropertyBehavior
)的全名,通过反射调用其构造方法。
1.1 获取app:layout_behavior
等参数
- CoordinatorLayout继承自ViewGroup,is a super-powered FrameLayout
- CoordinatorLayout调用
generateLayoutParams
,获取Layout所需要的参数 -
generateLayoutParams
调用CoordinatorLayout.LayoutParams
(design-23.2.1 CoordinatorLayout.java:2267)的构造函数,获取子布局中的参数。这些参数决定子布局在CoordinatorLayout
中的布局。
LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
//获取XML布局属性
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CoordinatorLayout_LayoutParams);
this.gravity = a.getInteger(
R.styleable.CoordinatorLayout_LayoutParams_android_layout_gravity,
Gravity.NO_GRAVITY);
mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_LayoutParams_layout_anchor,
View.NO_ID);
this.anchorGravity = a.getInteger(
R.styleable.CoordinatorLayout_LayoutParams_layout_anchorGravity,
Gravity.NO_GRAVITY);
this.keyline = a.getInteger(R.styleable.CoordinatorLayout_LayoutParams_layout_keyline,
-1);
mBehaviorResolved = a.hasValue(
R.styleable.CoordinatorLayout_LayoutParams_layout_behavior);
//获取Behavior
if (mBehaviorResolved) {
mBehavior = parseBehavior(context, attrs, a.getString(
R.styleable.CoordinatorLayout_LayoutParams_layout_behavior));
}
a.recycle();
}
1.2 实例化Behavior
实例化Behavior的逻辑在parseBehavior
函数。
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 {
//只有类名的,在CoordinatorLayout的包中获取预备的Behavior类
// Assume stock behavior in this package.
fullName = WIDGET_PACKAGE_NAME + '.' + name;
}
//通过反射获取Behavior实现类的构造方法
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>) 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);
}
}
2. Property Behavior的关键方法
2.1 layoutDependsOn 依赖条件的判断
开启一个CoordinatorLayout
之后layoutDependsOn
被多次调用。根据Logger的日志,可知,CoordinatorLayout.onMeasure
调用的。也就是说,布局改变会触发layoutDependsOn
方法。
View.measure (View.java:17547)
CoordinatorLayout.onMeasure (CoordinatorLayout.java:671)
CoordinatorLayout.ensurePreDrawListener (CoordinatorLayout.java:1288)
CoordinatorLayout.hasDependencies (CoordinatorLayout.java:1318)
CoordinatorLayout$LayoutParams.dependsOn (CoordinatorLayout.java:2461)
TextViewPropertyBehavior.layoutDependsOn (TextViewPropertyBehavior.java:16)
TextViewPropertyBehavior.layoutDependsOn (TextViewPropertyBehavior.java:37)
- 上文讲到构造函数获取XML中的
app:layout_behavior
属性值保存在CoordinatorLayout.LayoutParams
。 -
CoordinatorLayout.hasDependencies有
根据LayoutParams
存储的属性值,判断是否存在带有合法依赖关系的子布局。-
条件一:
app:layout_anchor
(设置子View的锚点,即以哪个控件为参照点设置位置。)已经设置。 -
条件二:至少一个子View符合
dependsOn
(CoordinatorLayout:2460)函数的条件
-
条件一:
/**
* Check if an associated child view depends on another child view of the CoordinatorLayout.
*
* @param parent the parent CoordinatorLayout
* @param child the child to check
* @param dependency the proposed dependency to check
* @return true if child depends on dependency
*/
boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency == mAnchorDirectChild
|| (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
}
可以看到这里调用了Behavior.layoutDependsOn
方法。
2.2 onDependentViewChanged 依赖的逻辑关系
-
ensurePreDrawListener
函数判断,如果符合依赖条件,给布局添加一个OnPreDrawListner
void addPreDrawListener() {
if (mIsAttachedToWindow) {
// Add the listener
if (mOnPreDrawListener == null) {
mOnPreDrawListener = new OnPreDrawListener();
}
final ViewTreeObserver vto = getViewTreeObserver();
vto.addOnPreDrawListener(mOnPreDrawListener);
}
// Record that we need the listener regardless of whether or not we're attached.
// We'll add the real listener when we become attached.
mNeedsPreDrawListener = true;
}
ViewTreeObserver.OnPreDrawListener
在CoordinatorLayout
重绘前调用。
- 而
CoordinatorLayout
实现了这个接口,如下:
class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
@Override
public boolean onPreDraw() {
dispatchOnDependentViewChanged(false);
return true;
}
}
-
dispatchOnDependentViewChanged
这个函数的命名来看,肯定是调用了onDependentViewChanged
了。看(CoordinatorLayout.java 1173)
if (b != null && b.layoutDependsOn(this, checkChild, child)) {
if (!fromNestedScroll && checkLp.getChangedAfterNestedScroll()) {
// If this is not from a nested scroll and we have already been changed
// from a nested scroll, skip the dispatch and reset the flag
checkLp.resetChangedAfterNestedScroll();
continue;
}
final boolean handled = b.onDependentViewChanged(this, checkChild, child);
//...后面还有处理NestedScroll的响应逻辑
}
因此,每次重绘dispatchOnDependentViewChanged
再找出存在依赖关系的子View,上次是为了添加监听器,这次是执行依赖逻辑Behavior.onDependentViewChanged
。
2.3 mDependencySortedChildren
CoordinatorLayout.dispatchOnDependentViewChanged
在获取dependent view(被依赖)和auto view时,都从mDependencySortedChildren
获取。CoordinatorLayout
到底用什么结构存储这些View和她们的关系呢?
改变mDependencySortedChildren
在布局初始化的时候初始化,(L:613)prepareChilden
具体实现。
private void prepareChildren() {
mDependencySortedChildren.clear();
//首先无差别添加所有的child view(所以,dispatchOnDependentViewChanged之前还要调用layoutDependsOn判断是否符合依赖关系)
for (int i = 0, count = getChildCount(); i < count; i++) {
final View child = getChildAt(i);
final LayoutParams lp = getResolvedLayoutParams(child);
lp.findAnchorView(this, child);
mDependencySortedChildren.add(child);
}
// We need to use a selection sort here to make sure that every item is compared
// against each other
//排序
selectionSort(mDependencySortedChildren, mLayoutDependencyComparator);
}
- 首先无差别添加所有的child view(所以,dispatchOnDependentViewChanged之前还要调用layoutDependsOn判断是否符合依赖关系
- 排序,排序规则看
mLayoutDependencyComparator
与selectionSort
的实现
mLayoutDependencyComparator的实现
final Comparator<View> mLayoutDependencyComparator = new Comparator<View>() {
@Override
public int compare(View lhs, View rhs) {
if (lhs == rhs) {
return 0;
} else if (((LayoutParams) lhs.getLayoutParams()).dependsOn(
CoordinatorLayout.this, lhs, rhs)) {
return 1;
} else if (((LayoutParams) rhs.getLayoutParams()).dependsOn(
CoordinatorLayout.this, rhs, lhs)) {
return -1;
} else {
return 0;
}
}
};
dependsOn
上文已经提到。
咋看,如果用的是Collection.sort,那么dependent view
排在后,auto view
排在前。
可是,实际上,selectionSort
的逻辑是,通过一维数组存储一对多的数据结构,存储了CoordinatorLayout
所有子View的依赖关系。
|0...|i | i+1 |...| ... |||n-1|
|--|-----------:|:-------------:| -----:|
|...|dependency|auto|auto|...|dependency|auto|...|