Android Toolbar 避坑最佳实践

不要再封装自己的Bar了;

1. 简介

Android 3.0  Android 推了 ActionBar, 本意是想要逐渐改善过去 android 纷乱的界面设计,但ActionBar 使用并不方便,限制了App的开发与设计的弹性,实际使用的也不多了。
Toolbar 是android 5.0的推出的,放在了v7包中作为控件,它是为了取代ActionBar而产生的,意味着官方在某些程度上认为 ActionBar 限制了App开发与设计的弹性,而在 Material Design 也对之做了名称的定义:App bar

2. 基本使用

2.1 定义Style

Style要调整两个地方

  • 一是 res/values/styles.xml中
  • 二是 res/values-v21/styles.xml中

为了之后设定方便,我们先在 res/values/styles.xml 里增加一个名为 AppTheme.Base 的风格

<style name="AppTheme.Base" parent="Theme.AppCompat">
<item name="windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>

因为此范例只使用 Toolbar,所以我们要将让原本的 ActionBar 隐藏起来,然后将原本 AppTheme 的 parent 属性 改为上面的AppTheme.Base,代码如下:

<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="AppTheme.Base">
</style>

<style name="AppTheme.Base" parent="Theme.AppCompat">
<item name="windowActionBar">false</item>
<del><item name="android:windowNoTitle">true</item></del>
<!-- 使用 API Level 22 編譯的話,要拿掉前綴字 -->
<item name="windowNoTitle">true</item>
</style>
</resources>

再来调整Android 5.0的style:/res/values-v21/styles.xml,也将其 parent 属性改为 AppTheme.Base:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="AppTheme.Base">
</style>
</resources>

2.2 界面(Layout)

在 activity_main.xml 里面添加 Toolbar 控件:

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent" >
</android.support.v7.widget.Toolbar>

请记得用 support v7 里的 toolbar,不然然只有 API Level 21 也就是 Android 5.0 以上的版本才能使用。

2.3 代码(Java)

在 MainActivity.java 中加入 Toolbar 的声明:

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

声明后,再将之用 setSupportActionBar 设定,Toolbar即能取代原本的 ActionBar 了

3. Toolbar封装复用

3.1 创建toolbar.xml,Activity布局include即可

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:navigationIcon="@drawable/ic_back"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/Theme.Toolbar">

<!-- 标题居中 -->
<TextView
android:id="@+id/toolbar_title"
style="@style/Theme.ToolBar.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Title" />

</android.support.v7.widget.Toolbar>

@drawable/ic_back

<vector android:height="24dp" android:viewportHeight="1024.0"
android:viewportWidth="1024.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#ffffff" android:pathData="M387.8,164.9a22.1,22.1 0,0 0,-0.4 -30.7,20.5 20.5,0 0,0 -29.7,0.4L0,512.9l357.8,378.3c8,8.5 21.3,8.7 29.7,0.3 8.3,-8.3 8.5,-22 0.4,-30.7L60.3,512.9 387.9,164.9z"/>
</vector>

@style

<!-- Toolbar -->
<style name="Theme.Toolbar" parent="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<!-- 修改toolbar中按钮图标的颜色 -->
<item name="colorControlNormal">@color/white</item>
</style>

<!-- Toolbar Title-->
<style name="Theme.ToolBar.Title" parent="@style/TextAppearance.Widget.AppCompat.Toolbar.Title">
<item name="android:textSize">18sp</item>
<item name="android:textColor">@android:color/white</item>
</style>

<!-- Toolbar 溢出图标颜色 -->
<style name="Theme.Toolbar.OverflowMenuTheme" parent="Theme.AppCompat.NoActionBar">
<!-- 设置Menu菜单背景色 -->
<item name="android:itemBackground">@android:color/white</item>
<!-- 设置Menu菜单字体颜色 -->
<item name="android:textColorPrimary">@android:color/black</item>
<!-- 设置Menu窗口不覆盖Toolbar视图 -->
<item name="overlapAnchor">false</item>
</style>

3.2 BaseActivity

public abstract class BaseActivity extends  RxAppCompatActivity {

// 让5.0之前的系统可以用Vector图标
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}

/**
* 沉浸式状态栏和沉浸式导航栏管理,支持Android 4.4 以上
*/
private ImmersionBar mImmersionBar;
private Toolbar mToolBar;
private TextView mToolbarTitle;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// 修复系统输入法Bug:在15<=API<=23存在内存泄漏
// https://zhuanlan.zhihu.com/p/20828861
IMMLeaks.fixFocusedViewLeak(getApplication());

// 沉浸式状态栏
mImmersionBar = ImmersionBar.with(this);
// 解决状态栏与布局顶部重叠解决方案
mImmersionBar.fitsSystemWindows(true)
.statusBarColor(R.color.colorPrimary)
.navigationBarColor(R.color.colorPrimary)
.init();
}

/**
* 统一Toolbar:Activity布局内<include layout="@layout/toolbar"/>
* 1. 标题
* 标题居中显示
* 默认使用AndroidManifest的android:label属性值
* 可以在onCreate->setContentView之后,调用setToolbarTitle更新Title
* 2. Icon
* 3. 间距
* @param layoutResID
*/
@Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
mToolBar = findViewById(R.id.toolbar);
if (mToolBar != null) {
mToolbarTitle = mToolBar.findViewById(R.id.toolbar_title);
setSupportActionBar(mToolBar);
ActionBar actionBar = getSupportActionBar();
// 显示返回键
if(actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
// 隐藏默认标题
if (mToolbarTitle != null && actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
}
}
}

/**
* 统一Toolbar返回按钮处理
* @param item
* @return
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}

/**
* 覆盖onTitleChanged:让 title 默认显示到 mToolbarTitle,而且不需要暴露 mToolbarTitle 属性
* 1. 默认显示:AndroidManifest的android:label属性值
* 2. 代码设置:直接调用Activity.setTitle()方法动态修改
* @param title
* @param color
*/
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
if (mToolbarTitle != null) {
mToolbarTitle.setText(title);
}
}

@Override
protected void onDestroy() {
super.onDestroy();
if (mImmersionBar != null) {
mImmersionBar.destroy();
}
}

}

完整代码

参考项目内 android-library MainActivty

4. 参考

  • Android Toolbar,你想知道的都在这里了
  • Toolbar作为ActionBar与标题居中-封装
  • Android:改变 Toolbar 的文字和溢出图标颜色
  • ToolBar随心定制 - 简书:介绍了如何在ToolBar左侧放置Menu等