38、Material Design之Toolbar:替换ActionBar、自定义标题、菜单、搜索框

说实话,Android开发里最让我头疼的旧组件之一,就是那个老旧的ActionBar。它太死板了,想改个颜色都得绕好几个弯。后来Google推出了Toolbar,我第一时间就切过去了。这玩意儿说白了就是一个更灵活的View,你想放哪就放哪,想怎么打扮就怎么打扮。

今天我们就来聊聊Toolbar。我会从替换ActionBar开始,再到自定义标题、添加菜单、集成搜索框,一步步带你吃透它。

为什么我们要用Toolbar替换ActionBar?

ActionBar是Android早期就有的东西,它被硬生生地塞进了Activity的装饰视图里。你想动它?得用一堆主题属性去配置,灵活性很差。Toolbar就不一样了,它就是个普通的ViewGroup,你可以把它放在布局的任何位置,甚至嵌套在CoordinatorLayout里做折叠效果。

我个人习惯是,新项目一律不用ActionBar,直接上Toolbar。为什么?因为可控性太强了。你想想看,一个普通的View,你可以随意设置背景、间距、动画,甚至可以在里面塞别的子View——这在ActionBar时代是想都不敢想的。

核心要点: Toolbar是View,ActionBar是系统装饰。用Toolbar,你掌控一切。

第一步:干掉ActionBar,启用Toolbar

要使用Toolbar,首先得把系统自带的ActionBar隐藏掉。方法很简单,在主题里设置一下就行。

res/values/themes.xml 中,把主题的父类改成不带ActionBar的:

<!-- 使用 NoActionBar 主题,干掉系统ActionBar -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
    <item name="colorPrimary">@color/purple_500</item>
    <item name="colorPrimaryVariant">@color/purple_700</item>
    <item name="colorOnPrimary">@color/white</item>
</style>

然后在Activity的布局文件里,把Toolbar加进去:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- 这就是我们的Toolbar -->
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <!-- 其他内容 -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="这里是正文内容"
        android:gravity="center" />

</LinearLayout>

最后在Activity里,把Toolbar设置成ActionBar:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)  // 这一句是关键
    }
}

嗯,这里要注意:setSupportActionBar() 调用之后,Toolbar就拥有了ActionBar的所有能力,比如菜单回调、标题显示、返回导航等。但它本质上还是一个View,你可以随时修改它的属性。

自定义标题:别再用默认的了

默认的标题就是Activity的label,但很多时候我们需要更灵活的控制。比如标题居中对齐、加个副标题、甚至放个自定义的搜索框。

Toolbar提供了几个直接的方法:

toolbar.title = "我的标题"
toolbar.subtitle = "副标题在这里"
toolbar.setTitleTextColor(Color.WHITE)
toolbar.setSubtitleTextColor(Color.LTGRAY)

但如果你想要更复杂的布局,比如标题左边加个图标,右边加个按钮——那就直接在Toolbar里嵌套子View吧。

<androidx.appcompat.widget.Toolbar ...>

    <!-- 完全自定义的标题区域 -->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center">

        <ImageView
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:src="@drawable/ic_logo" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="首页"
            android:textSize="18sp"
            android:textColor="@android:color/white"
            android:layout_marginStart="8dp" />

    </LinearLayout>

</androidx.appcompat.widget.Toolbar>

我在项目中遇到过一种情况:产品要求标题在左边,但右边还要显示一个用户头像。用Toolbar嵌套布局,几分钟就搞定了。要是用ActionBar,估计得折腾半天。

菜单:Toolbar的灵魂

Toolbar的菜单机制和ActionBar完全一样,都是通过Menu资源文件来定义的。但Toolbar更灵活——你可以控制菜单项的显示位置,甚至动态添加和移除。

先创建一个菜单文件 res/menu/menu_main.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_search"
        android:icon="@drawable/ic_search"
        android:title="搜索"
        app:showAsAction="ifRoom" />

    <item
        android:id="@+id/action_settings"
        android:title="设置"
        app:showAsAction="never" />

    <item
        android:id="@+id/action_about"
        android:title="关于"
        app:showAsAction="never" />

</menu>

然后在Activity里重写 onCreateOptionsMenu()onOptionsItemSelected()

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    menuInflater.inflate(R.menu.menu_main, menu)
    return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return when (item.itemId) {
        R.id.action_search -> {
            // 处理搜索
            true
        }
        R.id.action_settings -> {
            // 跳转到设置
            true
        }
        else -> super.onOptionsItemSelected(item)
    }
}

这里有个小技巧:app:showAsAction 控制菜单项是显示在Toolbar上(图标形式),还是隐藏在溢出菜单里(三个点)。我个人习惯把最常用的操作设为 ifRoom,次要的放在溢出菜单里。

小提示: 如果你想让菜单项的图标显示为白色,可以在Toolbar的theme里设置 actionMenuTextColordrawableTint

搜索框:集成SearchView

搜索功能是很多App的刚需。Toolbar配合SearchView,可以做出非常流畅的搜索体验。

首先,在菜单文件里把搜索项改成SearchView:

<item
    android:id="@+id/action_search"
    android:icon="@drawable/ic_search"
    android:title="搜索"
    app:actionViewClass="androidx.appcompat.widget.SearchView"
    app:showAsAction="ifRoom|collapseActionView" />

注意 collapseActionView 这个flag——它让SearchView默认收起来,点击图标才展开。用户体验很好。

然后在Activity里配置SearchView的监听:

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    menuInflater.inflate(R.menu.menu_main, menu)

    val searchItem = menu?.findItem(R.id.action_search)
    val searchView = searchItem?.actionView as SearchView

    searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
        override fun onQueryTextSubmit(query: String?): Boolean {
            // 用户点击搜索按钮时触发
            // 在这里执行搜索逻辑
            return true
        }

        override fun onQueryTextChange(newText: String?): Boolean {
            // 用户输入文字时实时触发
            // 可以用来做实时搜索建议
            return false
        }
    })

    return true
}

我曾经踩过一个坑:SearchView的样式和Toolbar不搭,背景是白色的,在深色Toolbar上特别突兀。解决办法是给SearchView设置一个自定义样式:

<style name="CustomSearchViewStyle" parent="Widget.AppCompat.SearchView">
    <item name="android:background">@drawable/search_view_bg</item>
</style>

然后在菜单里引用:

app:actionViewClass="androidx.appcompat.widget.SearchView"
app:searchViewStyle="@style/CustomSearchViewStyle"
注意: SearchView在API 28以上有行为变化。如果你要支持低版本,建议用 androidx.appcompat.widget.SearchView 而不是原生的。

知识体系总览

下面这张图总结了Toolbar的核心知识点和它们之间的关系。你可以把它当作一个快速参考。

Toolbar 核心知识 1. 替换 ActionBar NoActionBar 主题 + setSupportActionBar() 2. 自定义标题 title / subtitle / 嵌套子View 3. 菜单集成 Menu资源 + showAsAction控制 4. 搜索框 SearchView集成 ⚠️ 常见坑点 样式冲突 / 版本兼容

避坑指南:我踩过的那些坑

Toolbar虽然好用,但也不是没有坑。我把自己遇到过的几个典型问题列出来,你遇到了可以少走弯路。

问题 原因 解决方案
Toolbar标题不显示 没有调用 setSupportActionBar() 确保在Activity中调用了该方法
菜单项图标不显示 主题冲突或图标颜色问题 给Toolbar设置 app:theme 为深色主题
SearchView背景突兀 默认样式与Toolbar不匹配 自定义SearchView样式,设置背景和圆角
Toolbar高度不对 使用了固定高度而非 ?attr/actionBarSize 推荐使用 ?attr/actionBarSize 作为高度

我曾经在一个项目里,因为忘了调用 setSupportActionBar(),结果Toolbar的菜单死活不显示。排查了半天才发现是这个问题。你想想看,一个方法就能解决的事,愣是让我折腾了俩小时。所以,记住这个步骤。

总结一下

Toolbar的本质就是一个灵活的View容器。它替换了死板的ActionBar,给了我们自定义标题、菜单、搜索框的能力。核心就三步:

  1. 用NoActionBar主题干掉系统ActionBar
  2. 在布局里添加Toolbar并调用 setSupportActionBar()
  3. 通过Menu资源和SearchView实现交互功能

说白了,Toolbar就是让你从系统束缚中解放出来的工具。掌握了它,你的App标题栏就能随心所欲了。


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