CoordinateLayout的实现原理

引言

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进行处理。

CoordinateLayout的实现原理

上一篇:3dsmax造型设计之书法印刷效果


下一篇:datagrid中给字段添加链接