Cursor blinking

CoordinatorLayout 的常见用法

Android 基础|AndroidView|字数 1,668|阅读时长≈ 5 分钟

简介

CoordinatorLayout 是 Android 设计库中的一个特殊布局,用于协调其子视图之间的交互,实现复杂的用户界面效果。CoordinatorLayout 可以与各种 Behavior(行为)一起使用,这些行为可以控制子视图在 CoordinatorLayout 中的交互。

基本用法

以下示例展示 CoordinatorLayout 配合其子视图 AppBarLayout、CollapsingToolbarLayout 和滚动视图的常见用法

screen1.gif
screen1.gif
Code
<?xml version="1.0" encoding="utf-8"?><androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width="match_parent"    android:layout_height="match_parent">     <com.google.android.material.appbar.AppBarLayout        android:id="@+id/abl_bar_layout"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:fitsSystemWindows="true"        android:minHeight="0dp"        app:liftOnScrollTargetViewId="@+id/rv_recyclerview">         <com.google.android.material.appbar.CollapsingToolbarLayout            android:id="@+id/collapsing_Toolbar"            android:layout_width="match_parent"            android:layout_height="wrap_content"            app:layout_scrollFlags="scroll|exitUntilCollapsed">             <androidx.appcompat.widget.AppCompatImageView                android:layout_width="match_parent"                android:layout_height="300dp"                android:scaleType="centerCrop"                android:src="@mipmap/cover_01"                app:layout_collapseMode="parallax"                app:layout_collapseParallaxMultiplier="0.7" />             <com.google.android.material.textview.MaterialTextView                android:id="@+id/tv_mode"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:layout_gravity="bottom"                android:layout_marginBottom="56dp"                android:paddingHorizontal="16dp"                android:textColor="@color/white"                android:textAppearance="?attr/textAppearanceTitleMedium" />             <com.google.android.material.appbar.MaterialToolbar                android:id="@+id/toolbar"                android:layout_width="match_parent"                android:layout_height="?attr/actionBarSize"                android:background="#20ff0000"                app:layout_collapseMode="pin"                app:title="ToolBar" />         </com.google.android.material.appbar.CollapsingToolbarLayout>    </com.google.android.material.appbar.AppBarLayout>     <androidx.core.widget.NestedScrollView        android:layout_width="match_parent"        android:layout_height="match_parent"        app:layout_behavior="@string/appbar_scrolling_view_behavior">         <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_margin="@dimen/text_margin"            android:text="@string/large_text" />     </androidx.core.widget.NestedScrollView> </androidx.coordinatorlayout.widget.CoordinatorLayout>

layout_scrollFlags

layout_scrollFlags是在 AppBarLayout 中使用的一个属性,用于定义子视图在滚动时的行为。这个属性通常与 AppBarLayout 和 CollapsingToolbarLayout 等组件一起使用,这些标志可以单独使用,也可以通过位运算符 | 组合使用,以实现复合行为。下面是一个关于 layout_scrollFlags 可用参数及其作用的表格:

Flag描述
scroll这是滚动标志的基础,需要与其他标志组合使用。它指示视图会随着滚动事件一起滚动出或进屏幕。
enterAlways快速返回模式。向下滚动时,视图会立即进入屏幕。这通常用于确保应用栏(AppBar)对于下滑动作是响应的。
enterAlwaysCollapsed与 enterAlways 标志结合使用时,视图只会滚动到其最小高度,然后才可能进一步滚动显示。这通常与 minHeight 属性结合使用。
exitUntilCollapsed当向上滚动时,视图将滚动出屏幕,直到只剩下设定的 minHeight。这允许创建折叠效果,其中应用栏仍然显示一部分内容,而不是完全消失。
snap这会使视图通过滚动事件的最后一部分能够折叠或展开,而不是中断停留在中间状态。该行为依赖于视图当前的滚动位置,以及视图的高度是否超过其折叠点。
snapMarginsAndroid Support Library 26.0.0 引入。这允许在滚动时,视图的边缘(例如顶部或底部)与父容器的边缘对齐。适用于需要精确控制视图边缘如何响应滚动事件的场景。
组合使用
scroll \enterAlways向下滚动时,视图会立即进入屏幕。适用于需要快速访问顶部导航元素的场景。
scroll \exitUntilCollapsed向上滚动时,视图会滚出屏幕直至最小高度,适合需要在AppBar收起状态下显示关键信息的场景。
scroll \enterAlways \enterAlwaysCollapsed结合了快速返回和折叠行为。向下滚动时,视图立即以折叠形态进入,直至完全展开。适用于节省空间又不失功能访问性的设计。
scroll \exitUntilCollapsed \enterAlways视图在向上滚动时部分隐藏,向下滚动时立即进入,适合同时需要优化空间使用和提升用户界面友好性的场景。
scroll \snap视图会在停止滚动时自动收起或展开,避免停留在半展开或半收起的状态,提供流畅的用户体验。

这些标志的组合可以用来创建各种动态效果,例如应用栏在滚动时展开或折叠、快速返回顶部、粘性头部等。通过调整这些标志,开发者可以实现复杂的滚动交互,从而提升用户体验。

layout_collapseMode

layout_collapseMode 是 CollapsingToolbarLayout 子视图的属性,用于指定子视图在滚动事件发生时的折叠行为。这个属性对于实现动态交互效果,如图片的展开和折叠、工具栏的隐藏和显示等非常有用。以下是layout_collapseMode的可选值及其描述的表格:

layout_collapseMode 的值描述
off (默认)子视图不会参与折叠效果。这意味着子视图将保持其大小和位置,不管滚动事件如何。
pin当滚动发生时,子视图会被固定在顶部,直到折叠过程完成。这通常用于固定工具栏的位置,使其在内容滚动时仍然可见。
parallax子视图会以视差效果参与折叠过程。你可以通过 app:layout_collapseParallaxMultiplier (范围从 0.0 到 1.0) 设置视差效果的强度。视差效果使得背景图片在内容滚动时以不同于内容的速度移动,从而创造出深度感。

使用这些选项,你可以为 CollapsingToolbarLayout 中的各种元素(如图片、工具栏等)设置不同的折叠行为,以实现吸引人的用户界面和流畅的用户体验。

Behavior

在Android开发中,Behavior 是一种用于控制 CoordinatorLayout 中子视图行为的机制。Behavior可以通过自定义来实现各种复杂的交互效果,例如响应滚动事件、调整布局、处理碰撞等。

通过继承 CoordinatorLayout.Behavior 类并实现其中的方法,开发者可以定义自己的 Behavior,然后将其应用于 CoordinatorLayout 中的特定子视图。这样,该子视图就会按照自定义的Behavior来响应用户操作和布局变化。

Behavior类中常用的方法包括:

  1. onDependentViewChanged():当依赖的视图发生变化时调用,可以在这里处理子视图的位置或大小变化。
  1. layoutDependsOn():用于确定一个视图是否依赖于另一个视图,返回 true 表示依赖,false 表示不依赖。
  1. onStartNestedScroll() 和 onNestedScroll():用于处理滚动事件,可以在这里响应滚动事件并调整子视图的位置或大小。
  1. onNestedPreScroll() 和 onNestedScroll():用于在父视图和子视图之间协调滚动事件。

通过自定义Behavior,开发者可以实现各种复杂的交互效果,从简单的视图移动到复杂的折叠、隐藏、显示等效果。Behavior 的灵活性使得开发者能够更好地控制应用界面的交互行为,提升用户体验。

!icon Behavior 原理可以简单理解为:CoordinatorLayout 从实现 NestedScrollingParent3 接口的 直接子视图 获取到滚动行为,并把滚动行为传递给实现 Behavior 接口的子 View。

以下是一个自定义 Behavior 的示例:

screen2.gif
screen2.gif

如上图,通过自定义 Behavior,实现 Fab 按钮的移动和展开收起

Code
open class ExtendedFAB @JvmOverloads constructor(    context: Context,    attributeSet: AttributeSet? = null,) : ExtendedFloatingActionButton(context, attributeSet) {     private val fabBehavior = ExtendedFABBehavior(context, attributeSet)     override fun getBehavior(): CoordinatorLayout.Behavior<ExtendedFloatingActionButton> {        return fabBehavior    }     /**     * 自定义 Behavior     */    protected class ExtendedFABBehavior(        context: Context,        attributeSet: AttributeSet?    ) : ExtendedFloatingActionButtonBehavior<ExtendedFloatingActionButton>(context, attributeSet) {              /**         * 因为实现了 Behavior 接口,CoordinatorLayout 始终会调用该视图的 onLayoutChild。         * 在这里自定义或修改布局,以代替默认的子布局行为。         */        @SuppressLint("RestrictedApi")        override fun onLayoutChild(parent: CoordinatorLayout, child: ExtendedFloatingActionButton, layoutDirection: Int): Boolean {            val paramsCompat = child.layoutParams as ViewGroup.MarginLayoutParams            height = child.measuredHeight + paramsCompat.bottomMargin            return super.onLayoutChild(parent, child, layoutDirection)        }         /**         * 当依赖的视图发生变化时调用,可以在这里处理子视图的位置或大小变化。         */        override fun onDependentViewChanged(parent: CoordinatorLayout, child: ExtendedFloatingActionButton, dependency: View): Boolean {            return super.onDependentViewChanged(parent, child, dependency)        }         /**         * 用于确定一个视图是否依赖于另一个视图,返回true表示依赖,false表示不依赖。         */        override fun layoutDependsOn(parent: CoordinatorLayout, child: ExtendedFloatingActionButton, dependency: View): Boolean {            return super.layoutDependsOn(parent, child, dependency)        }         /**         * 用于处理滚动事件,可以在这里响应滚动事件并调整子视图的位置或大小。         */        override fun onStartNestedScroll(            coordinatorLayout: CoordinatorLayout,            child: ExtendedFloatingActionButton,            directTargetChild: View,            target: View,            axes: Int,            type: Int        ): Boolean {            return axes == ViewCompat.SCROLL_AXIS_VERTICAL        }         /**         * 用于处理滚动事件,可以在这里响应滚动事件并调整子视图的位置或大小。         */        override fun onNestedScroll(            coordinatorLayout: CoordinatorLayout,            child: ExtendedFloatingActionButton,            target: View,            dxConsumed: Int,            dyConsumed: Int,            dxUnconsumed: Int,            dyUnconsumed: Int,            type: Int,            consumed: IntArray        ) {            if (dyConsumed > 0) {                shrink(child)                slideDown(child)            } else {                extend(child)                if (dyConsumed < 0) {                    slideUp(child)                }            }        }    }}

参考文档