「Behavior」使用Behavior“协调”布局,实现酷炫交互

CoordinatorLayout 从诞生之初起,因其特殊的"协调"手段,非常适用于实现复杂的视图交互和动画,比如响应滚动事件、展开或折叠工具栏(Toolbar)等,一直以来被广大Android开发者津津乐道。而实现这一关键效果,正是仰仗CoordinatorLayout.Behavior。本文尝试追本溯源,详细讲讲Behavior到底是个啥?我该怎么用?

Behavior是什么

Android团队是这么定义Behavior的:

Behavior:CoordinatorLayout 子视图的交互行为插件。Behavior 实现了用户可以对子视图进行的一个或多个交互操作。这些交互操作可能包括拖拽、滑动、甩动或任何其他手势。

Behavior是一个抽象类,除开构造方法外,其主要包含以下几个方法:

  • onAttachedToLayoutParams(CoordinatorLayout.LayoutParams params)onDetachedFromLayoutParams()

对称式API,CoordinatorLayout调用setBehavior()方法设置behavior 时被调用,通知对应的behavior它所持有的LayoutParams实例已被 绑定/分离 。

  • onInterceptTouchEvent/ onTouchEvent

熟悉事件分发机制应该懂这两个Api的含义:是否要拦截触摸事件 以及 如何消费事件。

  • getScrimColor(CoordinatorLayout parent, V child) / getScrimOpacity(CoordinatorLayout parent, V child)

介绍这两个API前,先介绍一个前端交互上常见的概念,遮罩(Scrim):UI设计为了凸显用户当前操作/交互的元素时,往往会给该元素上面或下面盖上一层带颜色的图层,这个图层我们称之为Scrim。CoordinatorLayout作为侧重交互效果的布局容器,自然也有这个特性,对应的API就是behavior中的这两个方法,一个设置遮罩颜色,一个设置遮罩的透明度。CoordiantorLayout中的遮罩位于元素下方作为背景呈现。

  • public boolean blocksInteractionBelow(CoordinatorLayout parent, V child)

从方法名不难看出,该方法是用来决定是否要阻止位于给定child视图下方的View的交互行为的,默认当getScrimOpacity()大于0时,拦截该child视图下方的View的交互行为。

  • public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency)

指定一个视图是否依赖于 CoordinatorLayout 中的另一个视图。通过这种方式,开发者可以定义复杂的布局行为和交互模式,其中一个视图的状态或位置变化可以影响到另一个视图。三个参数分别对应父 CoordinatorLayout 的实例、子视图(child)的实例以及另一个可能被依赖的视图(dependency)的实例。它返回一个布尔值,true则表示子视图依赖于指定的视图。

  • public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency)

layoutDependsOn 方法并返回 true 表示存在依赖关系时,每当依赖视图的位置、大小或其他属性发生变化,onDependentViewChanged 方法就会被调用。这为开发者提供了一个机会,可以根据依赖视图的变化来调整当前视图(child view)的状态、位置等属性。 返回一个 boolean 值,表示子视图是否因为依赖视图的变化而发生了改变。如果返回 true,则表示子视图发生了改变,这将导致布局的再次计算和重绘。如果返回 false,则表示子视图没有因依赖视图的变化而发生改变,不需要重新布局或绘制。

  • public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency)

onDependentViewChanged方法类似,不同的是该方法在依赖视图从CoordinatorLayout中移除时被调用。这个回调提供了一个机会,让开发者可以响应依赖视图的移除事件,执行一些清理工作或者更新当前视图(child view)的状态、位置等。

  • onMeasureChild() / onLayoutChild()

这两个方法用于在测量阶段允许开发者介入子视图(即行为关联的视图)的测量/布局过程。通过重写这两个方法,可以自定义子视图的测量/布局逻辑,从而实现特定的布局需求或调整。


Behavior中同样可以对嵌套滚动进行处理,包含了onStartNestedScrollonStartNestedScrollonNestedScrollAcceptedonStopNestedScrollonNestedScrollonNestedPreScrollonNestedFlingonNestedPreFling等方法,他们的含义和NestedScrollingParent2NestedScrollingParent3接口中的含义一致,此处不做额外介绍。

Android中内置的Behavior

Android中预定义几种Behavior,用以实现各种复杂的交互逻辑,以下是一些常见的内置Behavior

  1. FloatingActionButton.Behavior:这个行为是为 FloatingActionButton(浮动操作按钮)设计的。它能够响应 Snackbar 的出现,自动移动浮动按钮以避免遮挡 Snackbar。

  2. SwipeDismissBehavior:这是一个通用行为,可以用于任何 View。它为视图添加了滑动以消失的交互模式,类似于 Snackbar 的滑动消失效果。

  3. AppBarLayout.ScrollingViewBehavior:这个行为专为与 AppBarLayout 一起使用的滚动视图(如 RecyclerView 或 NestedScrollView)设计。它确保滚动视图和 AppBarLayout 的滚动行为协同工作,实现材料设计的滚动效果。

  4. AppBarLayout.Behavior:这是 AppBarLayout 的默认行为,支持滚动手势,使得 AppBarLayout 可以响应内容滚动,实现折叠工具栏的效果。

  5. BottomSheetBehavior:这个行为用于 BottomSheet,即从屏幕底部滑出的面板。BottomSheetBehavior 允许用户通过拖动或滑动来展开或隐藏底部面板。

  6. CollapsingToolbarLayout.Behavior:这是为 CollapsingToolbarLayout 设计的行为,它可以在滚动时实现复杂的折叠动画效果,通常与 AppBarLayout 结合使用。

  7. ... ...

介绍完了API,可以尝试一下自定义Behavior练练手。

定制自己的Behavior

作为CoordinatorLayout协调布局实现的关键,Behavior实现的交互效果大致可以分为两类:

  1. 协调子View之间的关系:重写layoutDependsOnonDependentViewChanged等方法,建立View间的依赖关系,每当被依赖视图(dependency)的位置、大小或其他属性发生变化时调整当前视图(child)的状态、位置等属性。
  2. 协调嵌套嵌套滑动:重写嵌套滑动相关API实现不同的交互。

接下来我们从具体需求出发,一个一个来看。

协调子View之间的关系:避让BottomSheetBehavior的Behavior

我们来实现这样一个Behavior:当设置了BottomSheetBehavior的View弹出时,自动避让该View(dependency),隐藏View(child)。 代码如下:

kotlin 复制代码
/**
 * 根据BottomSheetBehavior控制View的显示隐藏以避让BottomSheet
 */
class VisibilityByBottomSheetBehavior(
	context: Context,
	attributeSet: AttributeSet,
): CoordinatorLayout.Behavior<View>(context, attributeSet) {

	override fun layoutDependsOn(
		parent: CoordinatorLayout,
		child: View,
		dependency: View
	): Boolean {
		// 如果依赖的View是BottomSheet,则建立依赖关系
		return isBottomSheet(dependency)
	}

	override fun onDependentViewChanged(
		parent: CoordinatorLayout,
		child: View,
		dependency: View
	): Boolean {
		if (isBottomSheet(dependency)) {
			// 如果依赖的View的顶部位置大于等于child的底部位置,则隐藏child
			child.isVisible = dependency.top >= child.bottom
		}
		// 返回false,布局的不会再次计算和重绘
		return false
	}

	private fun isBottomSheet(view: View): Boolean {
		val lp = view.layoutParams
		return if (lp is CoordinatorLayout.LayoutParams) {
			lp.behavior is BottomSheetBehavior<*>
		} else false
	}
}

实现的效果如下:

简简单单几行代码,就实现了根据BottomSheetBehavior控制View的显示隐藏,达到避让BottomSheet的目的。

协调嵌套滑动

如果熟悉Android中嵌套滚动的相关接口(NestedScrollingParentNestedScrollingChild)的话,看到CoordinatorLayout.Behavior中的onStartNestedScrollonStartNestedScrollonNestedScrollAcceptedonStopNestedScrollonNestedScrollonNestedPreScrollonNestedFlingonNestedPreFling等方法一定不陌生,没错,CoordinatorLayout也是基于这套机制通过Behavior来协调嵌套滑动的。 不熟悉这套机制的,可以先移步熟悉这套机制:
NestedScrollParent2

自定义Behavior实现各种炫酷交互

熟悉了Behavior基础用法后,就可以使用灵活运动这套机制实现各种炫酷交互,网上相关案例也非常多,基本都是仿写各种炫酷交互,这里引用几个:
通过来模仿稀土掘金个人页面的布局来学习使用 CoordinatorLayout
一起动才够嗨!Android CoordinatorLayout 自定义 Behavior
自定义 Behavior,实现嵌套滑动、平滑切换周月视图的日历

以后UI交互提出什么复杂的交互效果时,不妨换个思路,尝试自定义Behavior来实现,也许能事半功倍。

相关推荐
少说多做3434 分钟前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee1 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯2 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey3 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!5 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟6 小时前
Android音频采集
android·音视频
小白也想学C7 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程7 小时前
初级数据结构——树
android·java·数据结构
闲暇部落9 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX11 小时前
Android 分区相关介绍
android