Toolbar和ActionBar的小结

前言

哼,Toolbar,有够厉害的。算google致力于简化开发,提供的封装好的控件。像这种封装好的自定义控件有很多,比如BottomNavigation、比如TextInputLayout等。贴个material design风格控件网址:Components,有兴趣的可以看看。

ActionBar

  • 描述:活动中的主要工具栏,可以显示活动标题,应用程序级别的导航功能和其他交互式项。
  • 诞生:是Google在Android 3.0(api 11)时代推出的。

Android 5.0之前

要使用ActionBar,活动Activity必须要使用系统的Holo主题。(不管是直接还是间接)

Android 5.0开始

ActionBar可以由任何Toolbar来表示。Activity中可以指定具体的Toolbar作为活动栏。活动必须使用系统的NoActionBar主题。(不管直接还是间接)此外不能在活动主题里设置属性windowActionBar为false,此属性将关闭窗口特性。

延申-Window类中的特征标志

//选项面板功能,默认开启
FEATURE_OPTIONS_PANEL
//无标题
FEATURE_NO_TITLE
//@Deprecated {FEATURE_PROGRESS ; FEATURE_INDETERMINATE_PROGRESS; FEATURE_SWIPE_TO_DISMISS; 
//上下文菜单,默认开启
FEATURE_CONTEXT_MENU
//标题栏左边图标
FEATURE_LEFT_ICON
//标题栏右边图标
FEATURE_RIGHT_ICON
//自定义标题(不能将此功能与其他标题功能结合使用)
FEATURE_CUSTOM_TITLE

//用于启用操作栏的标志。
//默认情况下,某些设备已启用此功能。
//操作栏代替标题栏,并为某些设备上的屏幕菜单按钮提供了备用位置。
FEATURE_ACTION_BAR

//请求覆盖窗口内容的操作栏
//常配合View.SYSTEM_UI_FLAG_FULLSCREEN用,该模式可以无缝隐藏ActionBar和其他屏幕装饰
//从api 16开始,当ActionBar处于此模式时,他将提供给设置了View#fitSystemWindows(android.graphics.Rect)的控件空间(包括ActionBar所覆盖的内容)您可以在该空间内进行布局。
//FEATURE_ACTION_BAR_OVERLAY

//用于在不存在操作栏时指定操作模式行为的标志。
//如果启用了覆盖,则将允许操作模式UI覆盖现有窗口内容。
FEATURE_ACTION_MODE_OVERLAY
//窗口内容变化应该使用TransitionManager管理的标志。
FEATURE_CONTENT_TRANSITIONS
//创建一个ActivityOptions以使用跨活动场景在活动之间进行过渡动画。
//允许活动之间使用跨活动场景动画进行过渡
FEATURE_ACTIVITY_TRANSITIONS

在Activity中可以通过下面方法启用窗口扩展功能

//返回开启结果,可能失败
boolean requestWindowFeature(int featureId)

Toolbar

传统意义上讲,操作栏是由框架控制的Activity不透明窗口装饰的一部分,而工具栏则可以放置在视图层次结构中的任意嵌套级别。应用程序可以选择使用setSupportActionBar()方法将工具栏指定为Activity的操作栏。

工具栏可能包含以下可选元素的组合:

  • 导航按钮
  • 品牌logo和描述
//设置logo图标
setLogo(Drawable drawable)
//设置logo描述
setLogoDescription(@StringRes int resId)
  • 标题和副标题
    如果设置了logo的话,建议省略标题和副标题。
  • 一个或者多个自定义视图
    如果子视图的Toolbar.LayoutParams设置Gravity的值为CENTER_HORIZONTAL,那么在其他元素测量之后,子视图将在工具栏中剩余的可用空间内居中。
  • action_menu
    动作菜单固定在工具栏的末尾

使用示例

  1. 添加依赖
implementation 'androidx.appcompat:appcompat:1.2.0'
  1. 使用继承AppCompatActivity的活动页
  2. 在配置文件中,设置application样式,必须直接或者间接使用NoActionBar主题样式。
    <application
        android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"
        />

不然设置toolbar的时候可能报错:

This Activity already has an action bar supplied by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set windowActionBar to false in your theme to use a Toolbar instead.

  1. 往布局中添加一个工具栏,一般在顶部
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?actionBarSize"
            android:background="?attr/colorPrimary"
            app:titleTextColor="@color/white"
            android:elevation="10dp"
            android:theme="@style/ThemeOverlay.MaterialComponents.ActionBar"
            app:popupTheme="@style/ThemeOverlay.MaterialComponents.Light"/>
</LinearLayout>

elevation为toolbar深度,让toolbar看上去更有立体感。

  1. 设置工具栏作为活动栏
//将活动栏指定为工具栏
setSupportActionBar(toolbar)
//这里设置title不行,需要使用supportActionBar?.title=""
//toolbar.title="My Name"
//设置左边图标,其实也可以使用supportActionBar?.setDisplayHomeAsUpEnabled(true)
toolbar.setNavigationIcon(R.drawable.ic_back)
//设置图标按钮点击事件
toolbar.setNavigationOnClickListener {
    Snackbar.make(it.rootView,"My Name",Snackbar.LENGTH_SHORT).show()
}
//隐藏actionbar中标题、副标题 
//supportActionBar?..setDisplayShowTitleEnabled(false)

点击返回截图如下
在这里插入图片描述

扩充菜单、点击监听设置、子view属性设置、更新菜单关注几个方法

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    	//todo 使用menuInflater.inflate方法设置膨胀菜单,并将super方法改为 true,貌似是这样做每次不会重复创建
    	//todo 获取子view进行设置
        return super.onCreateOptionsMenu(menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
    	//可以根据itemId监听点击
        return super.onOptionsItemSelected(item)
    }

    override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
        return super.onPrepareOptionsMenu(menu)
    }

更新菜单时(例如,当用户按修改按钮以修改个人资料信息时),您必须对宿主 Activity 调用 invalidateOptionsMenu() 以请求系统调用 onCreateOptionsMenu()。失效后,您可以在 onCreateOptionsMenu() 中进行更新。菜单膨胀后,系统会调用 onPrepareOptionsMenu() 并更新菜单以反映 Fragment 的当前状态。

示例补充

上述示例使用了MaterialComponents主题,所以使用了MaterialToolbar。下面贴一部分AppCompat主题下,Toolbar简单示例。
1.添加依赖
2.使用继承AppCompatActivity的活动页
3.在配置文件中,设置application样式,必须直接或者间接使用NoActionBar主题样式。

    <application
        android:theme="@style/Theme.AppCompat.Light.NoActionBar"
        />

4.布局中添加一个工具栏

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
     <androidx.appcompat.widget.Toolbar
         android:id="@+id/toolbar"
         android:layout_width="match_parent"
         android:layout_height="?actionBarSize"
         app:background="?attr/colorPrimary"
         android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
         android:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</LinearLayout>
  1. 设置工具栏作为活动栏(同上)

MaterialToolbar是Toolbar实现某些 Material 功能的一个,例如深色主题和居中标题的高程叠加。

补充1:设置Title的文本颜色
  1. 可以在布局样式中设置,然后通过style引用样式
<item name="actionMenuTextColor">@android:color/black</item>
  1. 可以在xml中通过titleTextColor属性设置
app:titleTextColor="@android:color/black"
  1. 在代码中设置
mToolbar.setTitleTextColor(ColorUtils.getColor(android.R.color.black))
补充2:Toolbar中添加一个居中的文本

Toolbar是一个ViewGroup,可以随意自定义。很多app都需要居中文本,而不是用那个偏左的。

 <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/materialToolbar"
        android:layout_width="match_parent"
        android:layout_height="?actionBarSize"
        android:background="@color/colorPrimary"
        app:titleTextColor="@android:color/black"
        android:theme="@style/ThemeOverlay.MaterialComponents.ActionBar"
        android:popupTheme="@style/ThemeOverlay.MaterialComponents.Light">

        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/centerTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:textColor="@android:color/black"
            android:textSize="@dimen/toolbar_txt_size" />
    </com.google.android.material.appbar.MaterialToolbar>

加一个就完事了,当然还可以加其他的。

也可以改用MaterialToolbar,从material1.4.0版本开始,就支持标题和副标题居中了,具体在xml中使用titleCentered和subtitleCentered属性设置,也可以调用setTitleCentered和setSubtitleCentered方法设置。
在这里插入图片描述

补充3:如何设置toolbar中actionViewClass中的icon颜色和字体颜色

其实只需要给toolbar设置一个属性,然后修改这个属性的2个参数就ok

    <style name="AppBar" parent="AppTheme">
        <!--This line changes the color of text in Toolbar-->
        <item name="android:textColorPrimary">@color/white</item>
        <!--This line changes the color of icons in toolbar (back, overflow menu icons)-->
        <item name="android:textColorSecondary">@color/white</item>
    </style>

    <style name="ToolBarPop" parent="AppTheme" />

设置样式

            <com.google.android.material.appbar.MaterialToolbar
                android:layout_width="match_parent"
                android:layout_height="?actionBarSize"
                android:theme="@style/AppBar"
                app:popupTheme="@style/ToolBarPop"
                android:background="?attr/colorPrimary"
                app:title="Title"
                app:titleTextColor="@color/white"
                app:subtitleTextColor="@color/white"
                app:subtitle="subtitle"
                app:subtitleCentered="true"
                app:titleCentered="true"/>

AppTheme是项目的主题,textColorSecondary属性就是修改actionbar中那些icon的颜色的,让人看上去有统一的感觉。
在这里插入图片描述
如图所示,SearchView相关icon和字体就改成了白色

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

补充4:当menu设置为收起时,图标不显示问题

示例

    <item android:id="@+id/action_add"
        android:icon="@drawable/ic_add_list_item"
        app:showAsAction="never"
        android:orderInCategory="100"
        android:title="@string/add"/>

showAsAction设置为never,menu会被收起,这时候弹出框是没有icon的。

以前是这样设置即可显示icon

 @SuppressLint("RestrictedApi")
    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        //设置膨胀菜单
        menuInflater.inflate(R.menu.toolbar_learn, menu)
        if (menu is MenuBuilder){
            val menuBuilder=menu as MenuBuilder
            menuBuilder.setOptionalIconsVisible(true)
        }
        return true
    }

注意看RestrictedApi注解,看来使用setOptionalIconsVisible方法是危险的,此方法不被视为公共API,可能会随着版本变更随意更改。那么只能这样了,要么设置showAsAction为ifRoom,然后处理下,要么使用PopupWindow自己写一个弹出框。

补充5:如何给MenuItem设置角标Badge

提供一个思路:通过actionLayout属性指定一个layout,layout中有一个菜单icon和一个角标icon。然后在onCreateOptionsMenu初始化膨胀菜单的时候获取这个view进行操作,设置角标的显示和数字。
参考stackoverflow的问题how-to-add-badges-on-toolbar-menuitem-icons

菜单相关

在这里插入图片描述
首先在res下创建menu文件夹,然后新建main.xml

<?xml version="1.0" encoding="utf-8"?>
<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="@string/action_search"
        app:showAsAction="ifRoom|collapseActionView"
        app:actionViewClass="android.widget.SearchView"/>
    <item android:id="@+id/action_favorite"
        android:icon="@drawable/ic_like"
        android:title="@string/action_favorite"
        app:showAsAction="ifRoom"/>
    <item android:id="@+id/action_settings"
        android:title="@string/action_settings"
        app:showAsAction="never"/>
</menu>
  • 主要看showAsAction属性
  1. ifRoom:有空间则显示在右边。
  2. collapseActionView:通常配合actionViewClass一起使用,意思是使用指定控件充满剩余区域,比如上图中的SearchView控件。
  3. never:隐藏显示在右边的“3个黑点”,点击弹出下拉菜单。

在活动页,创建菜单,并创建点击回调

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return when(item.itemId){
        R.id.action_search ->{
            true
        }
        R.id.action_favorite->{
            true
        }
        R.id.action_settings->{
            true
        }
        else ->{
            super.onOptionsItemSelected(item)
        }
    }

}

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    menuInflater.inflate(R.menu.main, menu)
    //可以在这获取菜单Item和他的actionView,做一些设置,比如给searchView添加文本变化监听和提交搜索监听
    val searchView=menu!!.findItem(R.id.action_search).actionView as SearchView
        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(query: String?): Boolean {
                //搜索文本提交回调
                return true
            }

            override fun onQueryTextChange(newText: String?): Boolean {
                //文本变化回调
                return true
            }
        })
    return true
}

补充1:在Fragment中使用menu

首先可以通过其父亲Activity来设置,因为ActionBar 是一个 Activity 属性。然后需要在onCreate中调用

setHasOptionsMenu(true)

setHasOptionsMenu(true) 可告知系统您的 Fragment 想要接收与菜单相关的回调。当发生与菜单相关的事件(创建、点击等等)时,先对 Activity 调用事件处理方法,然后再对 Fragment 调用。如果一个Activity管理一群Fragment,回调顺序取决于添加顺序。

补充2:在Fragment中使用Toolbar

使用 Fragment 拥有的应用栏时,建议直接使用 Toolbar API。请勿使用 setSupportActionBar() 和 Fragment 菜单 API,它们只适合 Activity 拥有的应用栏。

错误记录

  • UnsupportedOperationException

xml出现错误

UnsupportedOperationException Failed to resolve attribute at index ‘xxx’

报错原因与主题还有属性有关系。可能找到了多个,但是程序不知道选哪一个(比如我犯的错,全局使用MaterialComponents主题样式,然后我给Toolbar设置了AppCompat主题的样式,但是给Toolbar设置属性的时候又不使用自定义命名空间)。看下面例子。

  • 出错前:
<androidx.appcompat.widget.Toolbar
    ...
    android:background="?attr/colorPrimary"
    .../>
  • 解决后:
<LinearLayout
    ...
    xmlns:app="http://schemas.android.com/apk/res-auto" 
    .../>
    <androidx.appcompat.widget.Toolbar
    	...
    	app:background="?attr/colorPrimary"
    	.../>
</LinearLayout>
  • getActionView NullPointerException

原因是仅当有自定义 actionView 时getActionView 才有效。那么如何获取 MenuItem 的视图呢?

  1. 在menu菜单中使用actionLayout属性指定布局
  2. 在menu菜单中使用actionViewClass属性指定自定义 actionView
    当然使用全局命名空间 app,而不是android,像 app:actionLayout

后话

https://developer.android.google.cn/reference/androidx/appcompat/app/ActionBar
https://developer.android.google.cn/reference/androidx/appcompat/widget/Toolbar
https://developer.android.google.cn/training/appbar
https://developer.android.com/reference/com/google/android/material/appbar/MaterialToolbar

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值