主要coordinatorlayout的代码来自coordinatorlayout-1.0.0-sources.jar
本文从源码介绍 CoordinatorLayout 的 behavior 怎么工作的
1 behavior的使用方法
不管是自定义behavior还是使用Android自带的behavior, 放的都是全类名,且外面包含CoordinatorLayout ,behavior的所在xml节点必须是CoordinatorLayout 的直接子布局(不能隔层嵌套) Behavior 是layout_behavior 只能由 CoordinatorLayout的LayoutParams去解析
2 behavior如何实例化
2.1调用 parseBehavior 解析我们设置的 layout_behavior
2.2获取设置好的全类名 xxx.xx.xx 还可以是 .xx
2.3通过类名获取 class 然后获取两个参数的构造方法
2.4通过反射创建 Behavior 对象 newInatence() , 把所有的 behavior 放入了集合 以备之后使用
源码证明1-2.4
利用
红橙Darren视频笔记 CoordinatorLayout:实现自定义behavior
一文的demo 在TranslationBehavior的构造函数打下断点 观察调用
在CoordinatorLayout generateLayoutParams中会调用parseBehavior
CoordinatorLayout.java
//CoordinatorLayout的LayoutParams继承自MarginLayoutParams 这里是它的构造方法
//这里会解析CoordinatorLayout的直接子view 确认他们的属性 因此说只能用在直接子view上
LayoutParams(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CoordinatorLayout_Layout);
this.gravity = a.getInteger(
R.styleable.CoordinatorLayout_Layout_android_layout_gravity,
Gravity.NO_GRAVITY);
mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor,
View.NO_ID);
this.anchorGravity = a.getInteger(
R.styleable.CoordinatorLayout_Layout_layout_anchorGravity,
Gravity.NO_GRAVITY);
this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline,
-1);
insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0);
dodgeInsetEdges = a.getInt(
R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0);
mBehaviorResolved = a.hasValue(
R.styleable.CoordinatorLayout_Layout_layout_behavior);
//重点 判断子view是否有behavior属性
if (mBehaviorResolved) {
//这里得到了实例 需要跟进去 确认它如何实例化的
mBehavior = parseBehavior(context, attrs, a.getString(
R.styleable.CoordinatorLayout_Layout_layout_behavior));
}
a.recycle();
if (mBehavior != null) {
// If we have a Behavior, dispatch that it has been attached
mBehavior.onAttachedToLayoutParams(this);
}
}
//sConstructors起缓存作用
static final ThreadLocal<Map<String, Constructor<Behavior>>> sConstructors =
new ThreadLocal<>();
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;//如果在xml的写法类似app:layout_behavior=".TranslationBehavior"
//那么 加上包名补全类的全名
} 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);
}
//根据类的全名取出Behavior的构造函数
Constructor<Behavior> c = constructors.get(fullName);
if (c == null) {
//利用反射 根据Behavior的全类名 得到class
final Class<Behavior> clazz = (Class<Behavior>) context.getClassLoader()
.loadClass(fullName);
//获取类的两个参数的构造函数
c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
c.setAccessible(true);
//将构造函数存储起来 以备下次使用 key是全类名 value是构造函数
constructors.put(fullName, c);
}
//利用反射创建behavior实例
return c.newInstance(context, attrs);
} catch (Exception e) {
throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
}
}
//两参构造函数的参数
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
Context.class,
AttributeSet.class
};
3 CoordinatorLayout 如何与RecyclerView 联动 即behavior如何监听到滚动
同样 在上面的demo中的TranslationBehavior.onStartNestedScroll中断点 观察调用堆栈
这一段用源码说明容易造成混乱 我直接写下我的调查结果:
CoordinatorLayout.dispatchTouchEvent
->CoordinatorLayout.dispatchTransformedTouchEvent
->child.dispatchTouchEvent(event);(这里的child是recyclerView 断点可以看出类型)
->RecyclerView.onInterceptTouchEvent(ev);
->RecyclerView.startNestedScroll
->RecyclerView.getScrollingChildHelper().startNestedScroll(axes, type);
->NestedScrollingChildHelper.startNestedScroll()
->ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)
->ViewParentCompat.onStartNestedScroll
->((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
nestedScrollAxes, type);
->CoordinatorLayout.onStartNestedScroll
->viewBehavior.onStartNestedScroll(this, view, child,
target, axes, type);
总结下来顺序如下:
CoordinatorLayout处理事件分发 分发给子view RecyclerView,子view RecyclerView接受到手势调用startNestedScroll并利用NestedScrollingChildHelper告诉CoordinatorLayout子view RecyclerView开始scroll CoordinatorLayout则利用内部类告诉其他包含Behavior属性的布局RecyclerView开始Scroll了