在上一篇博客CoordinatorLayout高级用法-自定义Behavior中,我们介绍了如何去自定义一个CoordinatorLayout的Behavior,通过文章也可以看出Behavior在CoordinatorLayout中地位是相当高的,那么今天我们就来接着上篇博客来从源码分析一下Behavior的实现思路,如果你对CoordinatorLayout和Behavior还不熟悉的话,建议先去看看上篇博客《CoordinatorLayout高级用法-自定义Behavior》。
这篇文章我们要分析的内容有:
- Behavior的实例化
- layoutDependsOn和onDependentViewChanged调用过程
- onStartNestedScroll和onNestedPreScroll实现原理
- Behavior的事件分发过程
Behavior的实例化
大家都知道,我们在view中可以通过app:layout_behavior
然后指定一个字符串来表示使用哪个behavior,稍微去想一下,在CoordinatorLayout中肯定是利用反射机制来完成的behavior的实例化,现在就让我们从CoordinatorLayout的源码中找寻答案,来验证我们的猜想。首先,我们来看看CoordinatorLayout的一个内部类,也是大家熟悉的LayoutParams
,
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
/**
* A {@link Behavior} that the child view should obey.
*/
Behavior mBehavior;
...
}
在这里我们确实看到了behavior的影子,那它是在什么时候被初始化的呢?继续看代码,
LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CoordinatorLayout_LayoutParams);
...
mBehaviorResolved = a.hasValue(
R.styleable.CoordinatorLayout_LayoutParams_layout_behavior);
if (mBehaviorResolved) {
mBehavior = parseBehavior(context, attrs, a.getString(
R.styleable.CoordinatorLayout_LayoutParams_layout_behavior));
}
a.recycle();
}
在LayoutParams的构造方法中,首先是去检查了是不是有layout_behavior
,这里很容易理解,接下来调用了parseBehavior
方法,返回了Behavior的实例,我们非常有理由去看看parseBehavior
到底干了嘛,或许我们要的答案就在里面!
// 这里是指定的Behavior的参数类型
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
Context.class,
AttributeSet.class
};
...
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
// 代表了我们指定的那个behavior的完整路径
final String fullName;
// 如果是".MyBehavior"
// 则在前面加上程序的包名
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);
// 这里利用反射去实例化了指定的Behavior
// 并且值得注意到是,这里指定了构造的参数类型
// 也就是说我们在自定义Behavior的时候,必须要有这种类型的构造方法
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);
}
}
上面的代码很容易理解,就是利用反射机制去实例化了Behavior,调用的是两个参数的那个构造方法,这也就是我们在自定义Behavior的时候为什么一定要去重写,
public Behavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
这个构造的原因。看来获取一个Behavior的实例还是很简单的,那么,下面就让我们开始分析Behavior中常用方法调用的机制吧。
layoutDependsOn和onDependentViewChanged调用过程
在上一篇博客中我们学会了自定义两种形式的Behavior,其中第一种就是去观察一个view的状态变化,也就是涉及到layoutDependsOn
和onDependentViewChanged
两个方法的调用,现在我们从源码的角度来分析一下这两个方法调用的时机和调用的过程,在前一篇博客中我们提到过onDependentViewChanged
这个方法会在view的状态发生变化后去调用,那在状态发生变化时必定会执行什么操作呢?重绘,是的,状态变化了,那肯定重绘是避免不了的,在CoordinatorLayout
中注册了一个ViewTreeObserver
,我们可以从这里入手,因为它可以监听到view的各种状态变化,
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();