26、Material Design入门:Toolbar、FloatingActionButton、Snackbar、NavigationView、TabLayout

Material Design 是 Google 在 2014 年推出的一套设计语言。说白了,它就是想统一 Android、Web 和 iOS 上的视觉体验。我刚开始接触那会儿,觉得不就是换个皮肤嘛。后来真正做项目才发现,这套规范背后藏着很多交互细节和性能考量。

今天咱们就聊五个最常用的 Material 组件:Toolbar、FloatingActionButton、Snackbar、NavigationView 和 TabLayout。这些都是日常开发中几乎必用的东西,掌握了它们,你的 App 至少能有个「专业相」。

Toolbar:取代 ActionBar 的正确姿势

老 Android 开发者都知道 ActionBar,但 Google 后来推荐用 Toolbar。为什么?因为 Toolbar 更灵活。它可以放在布局的任何位置,可以嵌套,可以自定义样式。

我个人习惯在 styles.xml 里先把默认的 ActionBar 干掉:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

然后在布局里加上 Toolbar:

<androidx.appcompat.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    app:title="首页"
    app:titleTextColor="@android:color/white"
    app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

在 Activity 里记得调用 setSupportActionBar(toolbar)。嗯,这里要注意:如果你用了 NoActionBar 主题,但忘了调用这个方法,Toolbar 就只是个普通 View,不会拥有 ActionBar 的特性(比如菜单回调)。

小技巧: 我经常在 Toolbar 里加一个自定义的搜索框。用 app:contentInsetStart="0dp" 去掉默认的左边距,然后直接往 Toolbar 里塞 EditText 就行。Toolbar 本质上是个 ViewGroup,可以放子 View。

FloatingActionButton:悬浮按钮的讲究

FloatingActionButton(简称 FAB)是 Material Design 的标志性组件。它代表页面上最核心的操作。比如邮件 App 的「写邮件」按钮,地图 App 的「定位」按钮。

用法很简单:

<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"
    app:backgroundTint="@color/colorAccent" />

但有几个坑我得提醒你:

  • 不要滥用 FAB。一个页面最多一个 FAB。放多了用户会困惑。
  • 注意 z 轴高度。FAB 默认有 elevation,如果页面里有 BottomSheet 或 Dialog,可能会出现层级遮挡。我曾经遇到过 FAB 被 Dialog 盖住一半的情况,后来用 android:elevation="8dp" 解决了。
  • 动画要平滑。FAB 的显示/隐藏建议用 FloatingActionButton.show()hide(),它们自带缩放动画。别自己写 Visibility 切换,太生硬。
避坑指南: 我曾经在 RecyclerView 的列表页里放了 FAB,结果列表滚动时 FAB 挡住了最后一条数据。解决方案是用 CoordinatorLayout 配合 FAB 的 app:layout_anchor 属性,让 FAB 锚定在某个 View 上,或者用 app:layout_behavior 实现滚动时自动隐藏。

Snackbar:比 Toast 更优雅的反馈

Snackbar 是 Toast 的升级版。它从底部弹出,可以带一个操作按钮。比如「删除成功」后面跟一个「撤销」按钮。

Snackbar.make(view, "数据已删除", Snackbar.LENGTH_LONG)
    .setAction("撤销") {
        // 恢复数据
    }
    .show()

注意 make() 的第一个参数必须是 CoordinatorLayout 或者它的子类。为什么?因为 Snackbar 需要依赖 CoordinatorLayout 来实现滑动关闭和自动偏移(比如避开 FAB)。

如果你传了一个普通的 View,Snackbar 也能弹出来,但不会有滑动关闭的效果,也不会自动避开 FAB。嗯,这就是很多新手觉得 Snackbar「不好用」的原因——其实是布局没搭对。

核心要点: Snackbar 一定要配合 CoordinatorLayout 使用。布局结构建议:
CoordinatorLayout → AppBarLayout + Toolbar → 内容区域 → FAB

NavigationView:侧滑菜单的标准实现

NavigationView 是 DrawerLayout 的标配。它提供了一个标准的侧滑菜单模板,支持 header 和 menu 两部分。

布局示例:

<androidx.drawerlayout.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:menu="@menu/nav_menu"
        app:headerLayout="@layout/nav_header" />

</androidx.drawerlayout.widget.DrawerLayout>

menu 文件里可以定义分组和 item:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:checkableBehavior="single">
        <item android:id="@+id/nav_home" android:title="首页" android:icon="@drawable/ic_home" />
        <item android:id="@+id/nav_settings" android:title="设置" android:icon="@drawable/ic_settings" />
    </group>
</menu>

我个人习惯在 onNavigationItemSelected 里用 item.isChecked = true 来标记选中状态。但要注意:如果你用了 checkableBehavior="single",系统会自动处理选中态,你只需要返回 true 就行。

经验之谈: NavigationView 的 header 布局里,我通常会放用户头像和昵称。点击头像跳转到个人中心。这个交互很常见,但记得在 header 的点击事件里用 nav_view.getHeaderView(0) 来获取 header 布局。

TabLayout:标签页的优雅方案

TabLayout 通常和 ViewPager 或 ViewPager2 配合使用。它提供了 Material 风格的标签切换效果。

<com.google.android.material.tabs.TabLayout
    android:id="@+id/tab_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:tabMode="fixed"
    app:tabGravity="fill"
    app:tabIndicatorColor="@color/colorAccent"
    app:tabSelectedTextColor="@color/colorAccent" />

<androidx.viewpager2.widget.ViewPager2
    android:id="@+id/view_pager"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1" />

绑定代码:

val adapter = ViewPagerAdapter(this)
view_pager.adapter = adapter
TabLayoutMediator(tab_layout, view_pager) { tab, position ->
    tab.text = "标签${position + 1}"
}.attach()

这里有个细节:TabLayoutMediator 是 Material 库提供的桥梁类。它负责把 TabLayout 和 ViewPager2 同步起来。如果你用 ViewPager(旧版),可以用 tab_layout.setupWithViewPager()。但 ViewPager2 必须用 Mediator。

你想想看,如果不用 Mediator,手动监听 ViewPager2 的页面切换事件,再手动更新 TabLayout 的选中状态,那代码得多啰嗦。

注意: TabLayout 的 tabMode 有两种:fixedscrollable。标签少用 fixed,标签多用 scrollable。我见过有人把 10 个标签全用 fixed 模式,结果每个标签被压缩得只剩两个字,根本看不清。这种问题其实在 UI 评审阶段就该发现。

知识体系总览

下面这张图帮你理清这五个组件的关系和典型用法:

Material Design 核心组件关系图 Material Design Toolbar 取代 ActionBar FloatingActionButton 核心操作入口 Snackbar 轻量反馈 NavigationView 侧滑菜单 TabLayout 标签页切换 配合 CoordinatorLayout 使用效果最佳 所有组件均来自 com.google.android.material 包

组件对比速查表

组件 主要用途 依赖布局 常见坑点
Toolbar 顶部导航栏 AppBarLayout 忘记 setSupportActionBar
FloatingActionButton 核心操作按钮 CoordinatorLayout z 轴遮挡、滥用
Snackbar 操作反馈 CoordinatorLayout 参数传错 View 类型
NavigationView 侧滑菜单 DrawerLayout header 点击事件处理
TabLayout 标签页切换 ViewPager/ViewPager2 tabMode 选择不当

好了,这五个组件是 Material Design 的入门必修课。说白了,它们解决的都是 App 里最常见的交互场景:导航、操作、反馈、菜单、切换。把这些组件用熟了,你的 App 至少看起来像个「正经」的 Material Design 应用。

我个人建议你动手搭一个 Demo,把 Toolbar、FAB、Snackbar、NavigationView 和 TabLayout 全部塞到一个页面里。用 CoordinatorLayout 把它们串起来。你会发现,这些组件之间的联动其实很巧妙——比如 Snackbar 弹出时 FAB 会自动上移,TabLayout 切换时 ViewPager 会跟着滑动。这些细节,就是 Material Design 的魅力所在。

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