CoordinatorLayout布局,是“com.android.support:design”包中很重要的一个控件,通过为CoordinatorLayout(以下简称Col)的子view直接设置一个Behavior,可以定制出很棒的交互体验。本文从使用到源码解析来学习Col,源码基于23.3.0版本。
一、CoordinatorLayout和Behavior的源码分析
结合上面的类图,我们把Behavior理解成一个代理,他来代理子view的显示触摸滑动事件。
1. Behavior的实例化
1.1 通过构造方法实例,并在代码中设置到LayoutParams里
MyBehavior myBehavior = new MyBehavior();
CoordinatorLayout.LayoutParams params =(CoordinatorLayout.LayoutParams) yourView.getLayoutParams();
params.setBehavior(myBehavior)
很简单就不多说了~
1.2 XML方式实例化Behavior
在xml文件中给属性layout_behavior指定对应的behavior:
app:layout_behavior="com.aswifter.material.behavior.ScrollBehavior"
类ScrollBehavior中:
public class ScrollBehavior extends CoordinatorLayout.Behavior<View> {
public ScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
...
}
看下CoordinatorLayout.Behavior
public static abstract class Behavior<V extends View> {
public Behavior() {
}
public Behavior(Context context, AttributeSet attrs) {
}
...
}
以上可以看到,调用了这个两个参数的构造方法,这里为空,那么怎么实例化ScrollBehavior类的呢?
Behavoir是CoordinatorLayout.LayoutParams的属性值:
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
Behavior mBehavior;
boolean mBehaviorResolved = false;
...
LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
mBehaviorResolved = a.hasValue(
R.styleable.CoordinatorLayout_LayoutParams_layout_behavior);
if (mBehaviorResolved) {
mBehavior = parseBehavior(context, attrs, a.getString(
R.styleable.CoordinatorLayout_LayoutParams_layout_behavior));
}
...
}
}
在这里,先检查是否有layout_behavior,接下来通过调用parseBehavior方法得到Behavior的实例
parseBehavior方法:
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
...
//fullname指定behavior的完整路径,这里就不贴代码了,很简单~
final String fullName;
....
try {
Map<String, Constructor<Behavior>> constructors = sConstructors.get();
if (constructors == null) {
constructors = new HashMap<>();
sConstructors.set(constructors);
}
Constructor<Behavior> c = constructors.get(fullName);
//利用反射机制实例化指定的Behavior,例如:ScollBehavior
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);
}
}
到这里就能够明白xml方式是如何实例化Behavior了~
1.3 通过DefaultBehavior注解
以AppBarLayout.Behavior为例:
@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {
...
}
进入DefaultBehavior
@Retention(RetentionPolicy.RUNTIME)
public @interface DefaultBehavior {
Class<? extends Behavior> value();
}
发现这是一个接口,我们在看看哪里具体实现了这个接口:
LayoutParams getResolvedLayoutParams(View child) {
final LayoutParams result = (LayoutParams) child.getLayoutParams();
if (!result.mBehaviorResolved) {
Class<?> childClass = child.getClass();
DefaultBehavior defaultBehavior = null;
while (childClass != null &&
(defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) {
childClass = childClass.getSuperclass();
}
if (defaultBehavior != null) {
try {
//实例化Behavior
result.setBehavior(defaultBehavior.value().newInstance());
} catch (Exception e) {
Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() +
" could not be instantiated. Did you forget a default constructor?", e);
}
}
result.mBehaviorResolved = true;
}
return result;
}
那这个getResolvedLayoutParams方法在哪里被调用的,接着看,发现在prepareChildren方法中调用,而prepareChildren被onMeasure调用。
到这里就明白,注解方式实例化Behavoir是怎么实现的了~