欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深入理解Android开发中的CoordinatorLayout Behavior

程序员文章站 2022-05-20 22:14:09
...

在使用Android设计支持库(Android Design Support Library)时,很难避开CoordinatorLayout:设计库中有很多视图都需要CoordinatorLayout的支持。为什么呢?实际上CoordinatorLayout本身所做的事情并不多,要是在标准框架视图中使用它,结果也就跟普通的FrameLayout差不多。那么奇迹来自何处呢?完全是由于CoordinatorLayout.Behaviors的存在。只要将Behavior绑定到CoordinatorLayout的直接子元素上,就能对触摸事件(touch events)、window insets、measurement、layout以及嵌套滚动(nested scrolling)等动作进行拦截。Design Library的大多功能都是借助Behavior的大量运用来实现的。

深入理解Android开发中的CoordinatorLayout Behavior

创建Behavior

创建behavior非常简单:使用extend Behavior就可以了。

public class FancyBehavior<V extends View>
    extends CoordinatorLayout.Behavior<V> {
  /**
   * Default constructor for instantiating a FancyBehavior in code.
   */
  public FancyBehavior() {
  }
  /**
   * Default constructor for inflating a FancyBehavior from layout.
   *
   * @param context The {@link Context}.
   * @param attrs The {@link AttributeSet}.
   */
  public FancyBehavior(Context context, AttributeSet attrs) {
    super(context, attrs);
    // Extract any custom attributes out
    // preferably prefixed with behavior_ to denote they
    // belong to a behavior
  }
}

注意: 这里绑定了泛型类型,也就是说,可以将FancyBehavior绑定到任意视图类上。不过,如果只想将Behavior绑定到特定种类的视图上,就可以用这段代码:

public class FancyFrameLayoutBehavior
    extends CoordinatorLayout.Behavior<FancyFrameLayout>

这样一来,当从视图收到方法调用时,就无需再费神将大量参数转到正确的子类中了,简单又便捷。

使用Behavior.setTag()/Behavior.getTag() 可以保存临时数据, 使用onSaveInstanceState()/onRestoreInstanceState()还可以保存Behavior相关的实例状态。虽然笔者建议要保证Behavior尽可能轻量级,不过这些方法可以让Behavior更具状态性。

关联Behavior

当然,Behavior无法独立完成工作,必须与实际调用的CoordinatorLayout子视图相绑定。具体有三种方式:通过代码绑定、在XML中绑定或者通过注释实现自动绑定。

通过代码绑定Behavior

如果将Behavior当作绑定到CoordinatorLayout中每个视图的附加数据,那么发现Behavior实际上是存储在各个视图的LayoutParams中也就不足为奇了(之前有关于布局的博文)。也是因此,Behavior需要绑定到CoordinatorLayout的直接子项中,因为只有那些子项会包含LayoutParams的特定Behavior子类。

FancyBehavior fancyBehavior = new FancyBehavior();
CoordinatorLayout.LayoutParams params =
    (CoordinatorLayout.LayoutParams) yourView.getLayoutParams();
params.setBehavior(fancyBehavior);

在这种情况下,我们会使用默认的无参数构造函数,不过这并不代表有参数的构造函数就不能用了,反正想用代码编写什么都没有限制。

在XML中绑定Behavior

当然,每次都用代码绑定的话总是有些麻烦,正如大多自定义的LayoutParams一样,完成这件工作也有相应的layout_ 属性,这里是layout_behavior属性:

<FrameLayout
  android:layout_height=”wrap_content”
  android:layout_width=”match_parent”
  app:layout_behavior=”.FancyBehavior” />

与代码绑定不同,这里调用的总是FancyBehavior(Context context, AttributeSet attrs) 构造函数。此外还能声明任何自定义属性,并将其从XML AttributeSet中提取出来,如果想要赋予开发者通过XML自定义Behavior的功能,这一点非常重要。

注意: 与父类负责解析与诠释的Layout_属性的命名规则相类似,在Behavior中我们使用behavior_作为属性前缀。

自动绑定Behavior

如果构建了需要自定义Behavior的自定义视图(就像Design Library中很多组件中那样),也许你会想要默认绑定某个behavior,而无需每次手动在代码中或XML中指定。为了达到这个目的,只需在自定义视图顶层添加简单的注释:

@CoordinatorLayout.DefaultBehavior(FancyFrameLayoutBehavior.class)
public class FancyFrameLayout extends FrameLayout {
}

这样,默认的构造函数就会调用Behavior,与使用代码绑定非常类似。注意:目前任何layout_behavior代表的属性都会重写DefaultBehavior。

拦截触摸事件

一旦将所有behavior设置完毕,就可以准备实际开工了。Behavior能做的事情之一包括拦截触摸事件。

不用CoordinatorLayout时,一般会使用各个ViewGroup的子类,Managing Touch Events training一文有提到过这个问题。不过有了CoordinatorLayout,通过Behavior的onInterceptTouchEvent(),将调用传递给它的onInterceptTouchEvent(),让Behavior获得拦截触摸事件的机会。通过返回为true,那么Behavior会通过onTouchEvent()接收后续的所有触摸事件,而且无需视图了解后续情况。SwipeDismissBehavior就是通过这样的方式在视图中执行任务的。

不过更严重的触摸拦截就是拦截任何交互,只要在blocksInteractionBelow()中返回true就会出现这样的情况。当然,在互动被拦截时也许你会希望有些视觉信号提示(以免使用者以为应用完全不能用了)——这就是为什么blocksInteractionBelow()的默认功能实际上依赖于getScrimOpacity()值——返回非零值会为视图提供一层颜色遮罩(用getScrimColor()来确定颜色,默认为黑),并立即禁用所有的触摸互动。非常方便。

拦截window insets

假设本文读者已经看过Why would I want to fitsSystemWindows一文, 在该文中我们就fitsSystemWindows的实际作用做了深入探讨,不过可归结为:window insets需要避免在系统窗口(比如状态栏和导航栏)之下出现。这里Behavior也能发挥作用:如果视图为fitsSystemWindows=“true”,则onApplyWindowInsets()会调用绑定Behavior,且优先级高于视图自身。

注意: 大多情况下,如果Behavior没有消耗掉整个window insets,则应当通过ViewCompat.dispatchApplyWindowInsets() 来传递这个insets,以确保视图的任何子项有机会看到这个WindowInsets。

拦截Measurement和Layout

Measurement和layout是Android绘制视图的关键组件,因此Behavior只有在onMeasureChild()和onLayoutChild()回调前拦截父视图的measurement和layout,才能达到预计的效果。

例如:我们采用泛型ViewGroup并为其添加一个maxWidth:

深入理解Android开发中的CoordinatorLayout Behavior
深入理解Android开发中的CoordinatorLayout Behavior
深入理解Android开发中的CoordinatorLayout Behavior

编写适用所有项目的通用Behavior非常有用,不过切记:尽量考虑在应用内使用behavior的办法,这样会让应用更为简单。(并非所有Behavior都应当是泛型的!)

理解视图间的依赖

上述所有功能都仅需要单个视图便可实现。不过Behavior的强大之处源自构建视图间的依赖,也就是说:当另一个视图改变时,你的Behavior会获得回调,根据外部情况来变更自身功能。

Behavior在两种情况下会成为视图的依赖:一种是将Behavior相应的视图锚定在另一个视图上时(隐性依赖),还有一种是在layoutDependsOn()中明确返回true时。

在视图中使用CoordinatorLayout的layout_anchor属性,就能起到锚定的作用。与layout_anchorGravity属性一同使用,就能将两个视图一并有效地固定在某个位置上。例如:可以将FloatingActionButton锚定到AppBarLayout上,而在AppBarLayout滚动出屏幕时,FloatingActionButton.Behavior就会通过隐性依赖将自身隐藏起来。

无论哪种情况,当依赖视图被移除时,Behavior会获得onDependentViewRemoved()的回调;而只要依赖视图出现变更,Behavior就会获得onDependentViewChanged()的回调(即调整大小或自身位置)。

将视图固定在一起的能力正是Design Library实现诸多炫酷功能的办法——比如FloatingActionButton与Snackbar之间的互动。FAB的Behavior依赖于添加到CoordinatorLayout上的Snackbar实例,再通过onDependentViewChanged()回调将FAB向上移动,避免遮住Snackbar。

注意: 在添加依赖时,视图总是会在依赖视图布局后进行布局,无视子项次序。

嵌套滚动

说到嵌套滚动,有详细介绍它的相关文章,在本文中笔者只做粗浅概述。需要牢记这几件事:

  1. 无需在嵌套滚动视图中声明依赖,因为CoordinatorLayout的每个子项都有可能接收到嵌套滚动事件。
  2. 嵌套滚动不仅可以在CoordinatorLayout的直接子项中发起,也能在任何子视图(比如CoordinatorLayout的子项的子项的子项中)发起。
  3. 虽然我们称之为嵌套滚动,不过实际上包括滚动(按照滚动做1:1的位移)与滑动(flinging)两种动作。

因此,通过onStartNestedScroll()来发起感兴趣的嵌套滚动事件吧。收到滚动轴(例如横向或纵向——使它容易忽略在特定方向上的滚动)后,必须在该方向上返回true,以获得随后的滚动事件。

在向onStartNestedScroll()返回true之后,嵌套滚动分两步运行:

  • onNestedPreScroll()在滚动视图获得滚动事件前运行,允许相应Behavior消耗一部分或所有的滚动事件(最后消耗的int[]是一个“外部”参数,在其中指明消耗掉的滚动)。
  • 滚动视图在滚动后会调用onNestedScroll(),可以知道滚动了多少view,未消耗掉的(overscroll)数量又有多少。

还有类似滑动操作(尽管pre-fling调用必须要么消耗掉所有的滑动,要么不消耗滑动——没有部分消耗的选项)。

在嵌套滚动(或滑动)停止后,就能获得onStopNestedScroll()的调用。这表示滚动结束:在下一个滚动开始前,等待重新调用onStartNestedScroll()。

举个例子:如果想要在向下滚动时隐藏FloatingActionButton,并在向上滚动时显示它,只用重写onStartNestedScroll() 和onNestedScroll(),就像在这个ScrollAwareFABBehavior中看到的那样。

这只是开始

Behavior的每个部分都很有趣,不过在把它们放在一起时,就出现了奇迹。笔者强烈建议,请读者查看Design Library的原代码库,找出更高级的behavior。Android SDK Search Chrome的扩展组件是我最喜欢搜索开源代码的来源之一(尽管包含在/extras/android/m2repository中的源码总是最新的)。

在深刻理解Behavior的功能之后,希望你们能使用它们构建更好的应用。

文章来源: Intercepting everything with CoordinatorLayout Behaviors
作者: Ian Lake,Android Developer Advocate
翻译: 孙薇
审校/责任编辑:唐小引,欢迎技术投稿、约稿,给文章挑错,请邮件发送至[email protected]

第一时间掌握最新移动开发相关信息和技术,请关注mobilehub公众微信号(ID: mobilehub)。

深入理解Android开发中的CoordinatorLayout Behavior