一、FloatingActionButton 是什么?

FAB,全称 FloatingActionButton,是 Material Design 里一个标志性组件。说白了,就是一个悬浮在界面上的圆形按钮,通常用来触发 App 里最核心的那个操作。

我刚开始接触 Material Design 时,觉得 FAB 不就是个花哨的按钮吗?后来在项目里用多了才发现,它背后其实有一套完整的设计哲学——突出主要操作。比如邮件 App 里的「写邮件」、地图 App 里的「定位」、笔记 App 里的「新建笔记」。

嗯,这里要注意:FAB 不是装饰品。每个页面最多用一个 FAB,多了反而让用户困惑。

二、FAB 的基本使用

2.1 添加依赖

在 build.gradle 里加上 Material 库:

dependencies {
    implementation 'com.google.android.material:material:1.9.0'
}

2.2 布局文件

直接在 XML 里声明就行:

<com.google.android.material.floatingactionbutton.FloatingActionButton
    android:id="@+id/fab_add"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:src="@drawable/ic_add"
    android:contentDescription="添加"
    app:fabSize="normal" />

我个人习惯把 FAB 放在 CoordinatorLayout 里,这样它才能跟 Snackbar、AppBarLayout 这些组件联动。你想想看,如果 FAB 不在 CoordinatorLayout 里,Snackbar 弹出来时就会直接盖住 FAB,多难看。

2.3 常用属性

属性 说明 可选值
app:fabSize 按钮尺寸 auto / normal / mini
app:backgroundTint 背景颜色 颜色值
app:rippleColor 点击涟漪颜色 颜色值
app:elevation 阴影高度 数值(dp)
app:useCompatPadding 是否使用兼容内边距 true / false

三、FAB 的行为与动画

3.1 点击事件

跟普通按钮一样,设置 OnClickListener:

fabAdd.setOnClickListener { view ->
    // 执行主要操作
    Snackbar.make(view, "已添加", Snackbar.LENGTH_SHORT).show()
}

3.2 显示与隐藏动画

FAB 自带缩放动画。调用 show() 和 hide() 就行:

fabAdd.hide()  // 隐藏,带缩放动画
fabAdd.show()  // 显示,带缩放动画

我在项目中遇到过一个问题:列表滚动时,FAB 应该隐藏,但用户快速滚动时动画会卡顿。后来发现是动画队列冲突了。解决办法是判断动画状态:

if (!fabAdd.isAnimating) {
    fabAdd.hide()
}

3.3 自定义动画

如果你觉得默认动画不够炫,可以用 Transition 自定义:

val scale = ScaleTransition()
scale.duration = 300
fabAdd.transition = scale
fabAdd.show()

四、FAB 与 Snackbar 联动

这是 Material Design 里最经典的组合之一。Snackbar 弹出时,FAB 会自动上移,避免被遮挡。这个效果全靠 CoordinatorLayout 的 Behavior 机制实现。

4.1 核心原理

FAB 内部有一个默认的 Behavior——FloatingActionButton.Behavior。它会监听 CoordinatorLayout 里的子 View 变化。当 Snackbar 出现时,Behavior 会计算 Snackbar 的高度,然后让 FAB 向上平移同样的距离。

说白了,就是 CoordinatorLayout 在中间当「调度员」,Snackbar 通知它「我要出来了」,它再通知 FAB「你往上挪一挪」。

4.2 代码实现

布局结构必须是这样:

<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 你的内容区域 -->
    <FrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="16dp"
        android:src="@drawable/ic_add" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

然后在 Activity 里:

val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener { view ->
    Snackbar.make(view, "操作成功", Snackbar.LENGTH_LONG)
        .setAction("撤销") {
            // 撤销操作
        }
        .show()
}

注意:Snackbar.make() 的第一个参数必须是 CoordinatorLayout 里的子 View。我习惯传 FAB 本身,这样 Snackbar 会显示在底部,FAB 自动上移。

4.3 避坑指南

我曾经踩过一个坑:FAB 没有放在 CoordinatorLayout 里,而是放在 RelativeLayout 里。结果 Snackbar 弹出来时,FAB 纹丝不动,直接被 Snackbar 盖住一半。用户点 FAB 时老是点到 Snackbar 上。

解决方案:确保 FAB 的父布局是 CoordinatorLayout,并且 FAB 的 layout_gravity 设置为 bottom|end。

五、FAB 的进阶用法

5.1 扩展 FAB(ExtendedFloatingActionButton)

有时候光一个图标不够直观,比如「新建」按钮,用户可能不知道是新建什么。这时候可以用 ExtendedFloatingActionButton,它带文字:

<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
    android:id="@+id/fab_extended"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:text="新建笔记"
    android:icon="@drawable/ic_add" />

我个人习惯在 App 首次引导时用 ExtendedFAB,等用户熟悉了再缩成普通 FAB。这样既降低了学习成本,又保持了界面简洁。

5.2 多个 FAB 的展开动画

有些 App 会用一个主 FAB 展开多个子 FAB,比如 Gmail 的「写邮件」展开后还有「写便签」「写提醒」。实现方式是用动画控制子 FAB 的平移和透明度:

fun toggleFabMenu() {
    if (isMenuOpen) {
        // 收起
        fabSub1.animate().translationY(0f).alpha(0f)
        fabSub2.animate().translationY(0f).alpha(0f)
    } else {
        // 展开
        fabSub1.animate().translationY(-100f).alpha(1f)
        fabSub2.animate().translationY(-200f).alpha(1f)
    }
    isMenuOpen = !isMenuOpen
}

嗯,这里要注意:子 FAB 的点击区域要足够大,不然用户很难点中。我一般把子 FAB 的 layout_margin 设成 8dp 以上。

六、知识体系总览

下面这张图总结了 FAB 的核心知识点和它们之间的关系:

FloatingActionButton 基本使用 布局声明 + 属性配置 点击事件处理 行为与动画 show() / hide() 缩放动画 自定义 Transition Snackbar 联动 CoordinatorLayout 机制 Behavior 自动上移 进阶用法 ExtendedFAB 带文字 多 FAB 展开动画 常见坑点 父布局必须是 CoordinatorLayout 动画队列冲突处理 核心:一个页面一个 FAB,配合 CoordinatorLayout 实现完美联动

七、总结

FAB 看起来简单,但用好了能让 App 的交互体验提升一个档次。我总结几个要点:

  • 位置固定:右下角,不要乱放
  • 操作唯一:一个页面只放一个 FAB
  • 联动必配:FAB + CoordinatorLayout + Snackbar 是铁三角
  • 动画适度:不要为了炫技加太多动画,用户会晕

小技巧:如果你想让 FAB 在列表滚动时隐藏,可以用 RecyclerView 的 OnScrollListener 监听滚动方向,向下滚动时调用 fab.hide(),向上滚动时调用 fab.show()。这样用户体验会好很多。

好了,FAB 的内容就这些。你在项目里用 FAB 时遇到过什么奇怪的问题吗?欢迎交流。


公众号:蓝海资料掘金营,微信deep3321