前言
哼,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
动作菜单固定在工具栏的末尾
使用示例
- 添加依赖
implementation 'androidx.appcompat:appcompat:1.2.0'
- 使用继承AppCompatActivity的活动页
- 在配置文件中,设置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.
- 往布局中添加一个工具栏,一般在顶部
<?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看上去更有立体感。
- 设置工具栏作为活动栏
//将活动栏指定为工具栏
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>
- 设置工具栏作为活动栏(同上)
MaterialToolbar是Toolbar实现某些 Material 功能的一个,例如深色主题和居中标题的高程叠加。
补充1:设置Title的文本颜色
- 可以在布局样式中设置,然后通过style引用样式
<item name="actionMenuTextColor">@android:color/black</item>
- 可以在xml中通过titleTextColor属性设置
app:titleTextColor="@android:color/black"
- 在代码中设置
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属性
- ifRoom:有空间则显示在右边。
- collapseActionView:通常配合actionViewClass一起使用,意思是使用指定控件充满剩余区域,比如上图中的SearchView控件。
- 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 拥有的应用栏。
错误记录
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>
原因是仅当有自定义 actionView 时getActionView 才有效。那么如何获取 MenuItem 的视图呢?
- 在menu菜单中使用actionLayout属性指定布局
- 在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