引言
CoordinateLayout的主要用法:
1、作为顶层应用的装饰或者chrome布局
2、作为一个能响应特定的一个或多个子视图交互的容器
也就是说,CoordinateLayout本身不具备布局的能力,它只是作为一个将Behavior和子View绑定的容器,将收到的事件几乎原封不动的分发给子View对应的Behavior。
例如,BottomSheetDialog的布局:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<View
android:id="@+id/touch_outside"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:soundEffectsEnabled="false"
tools:ignore="UnusedAttribute"/>
<FrameLayout
android:id="@+id/design_bottom_sheet"
style="?attr/bottomSheetStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top"
app:layout_behavior="@string/bottom_sheet_behavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>
我们将子View add到design_bottom_sheet的FrameLayout中之后,就能实现从底部弹窗的效果。
我们找到这个Behavior发现对应了一个java类,发现了里面包含了大量事件处理的函数。
<string name="bottom_sheet_behavior" translatable="false">com.google.android.material.bottomsheet.BottomSheetBehavior</string>
引发我们思考几个问题:
1、这个layout_behavior属性为什么加在子View上面?
2、上面的Behavior又是如何跟对应的View关联的,为何Behavoir能处理子View对应的行为?
实现原理
针对上述原理,我们进一步探究CoordinateLayout的实现原理。
如引言所说,coordinateLayout只是一个代理,只是仅仅作为事件转发给对应的Behavior,那么是如何转发的呢?以及Behavior又是如何跟对应的子View绑定的呢?
针对第2个问题:
我们回到bottomSheet那个例子,发现在对应的子View上指定了layout_behavior这个属性。
事实上coordianteLayout将子View上的属性解析出来,然后获取layout_behavior上的值,也就是对应的class类名。
之后就可以通过反射class对应的类,构造Behavior对象。
CoordinateLayout继承于ViewGroup,拥有响应事件的能力,因此,它将事件进一步转发给子类的所有Behavior。
其中还有一个关键的问题,就是CoordinateBehavior何时能够获取到子View的Attribute。
事实上,子View在被addView到Parent中的时候,会先看下子View有没有提供LayoutParams,如果没有提供,就会调用父ViewGroup的generatorLayoutParams()生成对应的LayoutParams,在此时接收了子View的Attribute参数。
伪代码:
public class CoordinateLayout extends ViewGroup {
// 拿到AttributeSet,生成自己的LayoutParams。
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
private static class LayoutParams {
Behavor behavior;
public LayoutParams(Context context, AttributeSet attrs) {
// get layout_behavior attribute from attrs
TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.BehaviorCoordinateLayout);
String behaviorClassName = typedArray.getString(R.styleable.BehaviorCoordinateLayout_layout_behavior);
// Generate the Behavior object use reflection
behavior = parseBehavior(behaviorClassName, c, attrs);
typedArray.recycle();
}
}
public void onTouchEvent(MotionEvent event) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.behavior != null) {
lp.behavior.onTouchEvent(this, child, event);
}
}
return false;
}
// .. 其他代码省略
}
我们通过在generatorLayoutParams中生成自己的LayoutParams返回,并在LayoutParams中绑定自己对应的Behavior,之后在CoordinateLayout收到某个事件的时候,直接传递给所有子View的Behavior。
需要消费该事件的子View只需要实现Behavior对应的方法,即可完成事件的处理。
Behavior对应的伪代码很简单:
public class Behavior {
public Behavior(Context context, AttributeSet attrs) {}
public void onTouchEvent(View parent, View child, MotionEvent ev) { }
}
例如,在BottomSheetDialog中,子View就绑定对应的BottomSheetBehavior,进而在该Behavior中进一步通过viewDragHelper来完成手势等操作,置于viewDragHelper如何使用请自行参考其他文章。
总结
本文主要介绍了CoordinateLayout的原理,描述了其如何绑定子View对应的Behavior,以及如何将对应的Event转发给对应的子View进行处理。