最詳細的 Android Toolbar 開發實踐總結

D_clock發表於2016-02-25

過年前發了一篇介紹 Translucent System Bar 特性的文章 Translucent System Bar 的最佳實踐 ,收到很多開發者的關注和反饋。今天開始寫第二篇,全面的介紹一下 Toolbar 的使用。說起 Toolbar ,可能有很多開發的童鞋還比較陌生,沒關係,請接著往下看。

初識 Toolbar

Toolbar是在 Android 5.0 開始推出的一個 Material Design 風格的導航控制元件 ,Google 非常推薦大家使用 Toolbar 來作為Android客戶端的導航欄,以此來取代之前的 Actionbar 。與 Actionbar 相比, Toolbar 明顯要靈活的多。它不像 Actionbar 一樣,一定要固定在Activity的頂部,而是可以放到介面的任意位置。除此之外,在設計 Toolbar 的時候,Google也留給了開發者很多可定製修改的餘地,這些可定製修改的屬性在API文件中都有詳細介紹,如:

  • 設定導航欄圖示;
  • 設定App的logo;
  • 支援設定標題和子標題;
  • 支援新增一個或多個的自定義控制元件;
  • 支援Action Menu;

Android開發:最詳細的 Toolbar 開發實踐總結

Toolbar支援的特性

總之,與 Actionbar 相比, Toolbar 讓我感受到Google滿滿的誠意。怎樣?是否已經對 Toolbar 有大概的瞭解,躍躍欲試的感覺出來了有木有?接下來,我們就一步一步的來看如何使用 Toolbar (其實是我使用 Toolbar 踩坑填坑的血淚史,你們接下去看,我先擦個眼淚…. )。

開始使用 Toolbar

前面提到 Toolbar 是在 Android 5.0 才開始加上的,Google 為了將這一設計向下相容,自然也少不了要推出相容版的 Toolbar 。為此,我們需要在工程中引入 appcompat-v7 的相容包,使用 android.support.v7.widget.Toolbar 進行開發。下面看一下程式碼結構,同樣把重點部分已經紅圈圈出:

Android開發:最詳細的 Toolbar 開發實踐總結
關鍵部分程式碼

  • ToolbarActivity 包含了 Toolbar 的一些基本使用, ZhiHuActivity 是在熟悉了 Toolbar 後對知乎主頁面的一個高仿實現。
  • layout和menu資料夾分別是上面提到的兩個Activity的佈局檔案 和 actionmenu 選單檔案。
  • values、values-v19、values-v21 中包含了一些自定義的 theme,後面用到的時候會順帶講解。

我們先來看一下 ToolbarActivity 的執行效果

Android開發:最詳細的 Toolbar 開發實踐總結

ToolbarActivity效果圖

按照效果圖,從左到右分別是我們前面提及到的 導航欄圖示App的logo標題和子標題自定義控制元件 、以及 ActionMenu 。接著,我們來看下佈局檔案和程式碼實現。

首先,在佈局檔案 activity_tool_bar.xml 中新增進我們需要的 Toolbar 控制元件

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

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/color_0176da">

        <!--自定義控制元件-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Clock" />
    </android.support.v7.widget.Toolbar>
</LinearLayout>

接著在 base_toolbar_menu.xml 中新增 action menu 選單項

<?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="@mipmap/ic_search"
        android:title="@string/menu_search"
        app:showAsAction="ifRoom" />

    <item
        android:id="@id/action_notification"
        android:icon="@mipmap/ic_notifications"
        android:title="@string/menu_notifications"
        app:showAsAction="ifRoom" />

    <item
        android:id="@+id/action_item1"
        android:title="@string/item_01"
        app:showAsAction="never" />

    <item
        android:id="@+id/action_item2"
        android:title="@string/item_02"
        app:showAsAction="never" />
</menu>

最後到 ToolbarActivity 中呼叫程式碼拿到這 Toolbar 控制元件,並在程式碼中做各種setXXX操作。

/**
 * Toolbar的基本使用
 */
public class ToolBarActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tool_bar);

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

        toolbar.setNavigationIcon(R.mipmap.ic_drawer_home);//設定導航欄圖示
        toolbar.setLogo(R.mipmap.ic_launcher);//設定app logo
        toolbar.setTitle("Title");//設定主標題
        toolbar.setSubtitle("Subtitle");//設定子標題

        toolbar.inflateMenu(R.menu.base_toolbar_menu);//設定右上角的填充選單
        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                int menuItemId = item.getItemId();
                if (menuItemId == R.id.action_search) {
                    Toast.makeText(ToolBarActivity.this , R.string.menu_search , Toast.LENGTH_SHORT).show();

                } else if (menuItemId == R.id.action_notification) {
                    Toast.makeText(ToolBarActivity.this , R.string.menu_notifications , Toast.LENGTH_SHORT).show();

                } else if (menuItemId == R.id.action_item1) {
                    Toast.makeText(ToolBarActivity.this , R.string.item_01 , Toast.LENGTH_SHORT).show();

                } else if (menuItemId == R.id.action_item2) {
                    Toast.makeText(ToolBarActivity.this , R.string.item_02 , Toast.LENGTH_SHORT).show();

                }
                return true;
            }
        });

    }

}

程式碼到此已經完成了 Toolbar 的基本使用,注意,是基本使用而已!!!!!下面有幾個程式碼裡面需要注意的地方:

  1. 我們在使用 Toolbar 時候需要先隱藏掉系統原先的導航欄,網上很多人都說給Activity設定一個NoActionBar的Theme。但個人覺得有點小題大做了,所以這裡我直接在BaseActivity中呼叫 supportRequestWindowFeature(Window.FEATURE_NO_TITLE) 去掉了預設的導航欄(注意,我的BaseActivity是繼承了AppCompatActivity的,如果是繼承Activity就應該呼叫 requestWindowFeature(Window.FEATURE_NO_TITLE) );
  2. 如果你想修改標題和子標題的字型大小、顏色等,可以呼叫 setTitleTextColorsetTitleTextAppearancesetSubtitleTextColorsetSubtitleTextAppearance 這些API;
  3. 自定義的View位於 titlesubtitleactionmenu 之間,這意味著,如果 titlesubtitle 都在,且 actionmenu選項 太多的時候,留給自定義View的空間就越小;
  4. 導航圖示和 app logo 的區別在哪?如果你只設定 導航圖示 ( or app logo ) 和 titlesubtitle ,會發現 app logotitlesubtitle 的間距比較小,看起來不如 導航圖示 與 它們兩搭配美觀;
  5. Toolbar和其他控制元件一樣,很多屬性設定方法既支援程式碼設定,也支援在xml中設定(這裡也是最最最最最坑爹的地方,如何坑爹法,請接著往下看);

Toolbar 踩坑填坑

坑一:xml佈局檔案中,Toolbar屬性設定無效

剛開始使用Toolbar的時候,我的佈局檔案中是這樣寫的

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

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/color_0176da"
        android:logo="@mipmap/ic_launcher"
        android:navigationIcon="@mipmap/ic_drawer_home"
        android:subtitle="456"
        android:title="123">

        <!--自定義控制元件-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Clock" />
    </android.support.v7.widget.Toolbar>
</LinearLayout>

在真機跑起來之後,看到的結果是下面這樣的。

Android開發:最詳細的 Toolbar 開發實踐總結

Toolbar 屬性設定無效

此時心中真是萬千匹草泥馬在奔騰,除了設定背景色和TextView有效外,說好的 logonavigationIconsubtitletitle 都跑哪去了?在編譯器沒報錯又不見效果的情況下,參考了其他開發者的用法後找到了以下的解決方案,就是在根佈局中加入自定義屬性的名稱空間

xmlns:toolbar="http://schemas.android.com/apk/res-auto"(這裡的toolbar可以換成你想要其他命名,做過自定義控制元件的童鞋相比很熟悉此用法了)

然後把所有用 android:xxx 設定無效的,都用 toolbar:xxx 設定即可生效。最終的佈局程式碼如下:

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

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/color_0176da"
        toolbar:navigationIcon="@mipmap/ic_drawer_home"
        toolbar:logo="@mipmap/ic_launcher"
        toolbar:subtitle="456"
        toolbar:title="123">

        <!--自定義控制元件-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Clock" />
    </android.support.v7.widget.Toolbar>
</LinearLayout>

到此即可解決 xml 中屬性設定失效的問題,為什麼會出現這種問題呢?我猜測是因為這個控制元件是相容版的控制元件,用 android:xxx 設定無效是的這些屬性是在相容包中,不在預設的Android SDK中,所以我們需要額外的引入。至於為什麼IDE不報錯,估計就是bug了吧!

坑二:Action Menu Item 的文字顏色設定無效

系統默設定了ActionMenu每個Item的文字顏色和大小,像ToolbarActivity在Google原生5.1系統下預設效果就是下面這樣的

Android開發:最詳細的 Toolbar 開發實踐總結

Android 5.1 預設的ActionMenu Item的風格

此時,如果我有需求要改變一下item文字顏色,應該怎麼破?我按照網上比較普遍的解決方案,做了如下兩步的修改操作:

在styles.xml中自定義一個Theme,並設定 actionMenuTextColor 屬性(注意:不是 android:actionMenuTextColor )

<style name="Theme.ToolBar.Base" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="actionMenuTextColor">@color/color_red</item>
</style>

在佈局檔案的Toolbar中設定popupTheme(注意:是toolbar:xxx,不是android:xxx)

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/color_0176da"
        toolbar:popupTheme="@style/Theme.ToolBar.Base">

        <!--自定義控制元件-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Clock" />
    </android.support.v7.widget.Toolbar>

執行之後,文字的顏色的並沒有發生任何改變。說好的改變顏色呢…..找來找去,最後再 StackOverflow 找到一個還不錯的解決方案,就是把上面的的 actionMenuTextColor 屬性換成 android:textColorPrimary 即可解決,最終得到下面的執行效果。

Android開發:最詳細的 Toolbar 開發實踐總結

成功修改 actionmenu item 文字的顏色

這種方法也有一個小缺點,如果我把自定義控制元件換成Button,你會發現Button預設的文字顏色也變成了紅色。所以,此處如果有朋友有更好的解決方案,請留言賜教。

如果你想要修改 ActionMenu Item 的文字大小,也可以在theme中設定加上如下設定

<item name="android:textSize">20sp</item>

以上就是目前使用 Toolbar 一些比較折騰的坑,感覺 Google 對 Toolbar 這些坑,還可以進一步優化優化,不然就坑苦了開發者們了。

仿知乎主頁面

為了加深一下 Toolbar 的開發體驗,我們使用 Toolbar 來實現知乎主頁的效果!先來看下知乎主頁的效果

Android開發:最詳細的 Toolbar 開發實踐總結

Android 5.1上知乎主頁效果圖

如果前面的內容你看明白,想擼出這個介面無非是幾分鐘的事情,下面就直接上程式碼,不做贅述了。

ZhiHuActivity介面程式碼

public class ZhiHuActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_zhi_hu);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        toolbar.inflateMenu(R.menu.zhihu_toolbar_menu);

        toolbar.setNavigationIcon(R.mipmap.ic_drawer_home);

        toolbar.setTitle(R.string.home_page);
        toolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
    }
}

zhihu_toolbar_menu.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="@mipmap/ic_search"
        android:title="@string/menu_search"
        app:showAsAction="ifRoom" />

    <item
        android:id="@id/action_notification"
        android:icon="@mipmap/ic_notifications"
        android:title="@string/menu_notifications"
        app:showAsAction="ifRoom" />

    <item
        android:id="@id/action_settings"
        android:orderInCategory="100"
        android:title="@string/menu_settings"
        app:showAsAction="never" />

    <item
        android:id="@id/action_about"
        android:orderInCategory="101"
        android:title="@string/menu_about_us"
        app:showAsAction="never" />
</menu>

activity_zhi_hu.xml 佈局

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

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/color_0176da"
        android:theme="@style/Theme.ToolBar.ZhiHu">

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

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white">

        <ImageView
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_centerInParent="true"
            android:background="@mipmap/ic_zhihu_logo" />
    </RelativeLayout>

</LinearLayout>

styles.xml 中的 Theme.ToolBar.ZhiHu,給 Toolbar 設定android:theme用的

<resources>

    ...
    ...

    <style name="Theme.ToolBar.ZhiHu" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="actionOverflowButtonStyle">@style/ActionButton.Overflow.ZhiHu</item>
    </style>

    <style name="ActionButton.Overflow.ZhiHu" parent="android:style/Widget.Holo.Light.ActionButton.Overflow">
        <item name="android:src">@mipmap/ic_menu_more_overflow</item>
    </style>

</resources>

最終得到下面這樣的效果

Android開發:最詳細的 Toolbar 開發實踐總結

Android5.1上仿知乎主頁面效果

這裡在 Toolbar 設定 android:theme=”@style/Theme.ToolBar.ZhiHu” 主要是為了替換系統右上角三個點的圖示,如果不設定,則會成系統預設主題的樣子。

Android開發:最詳細的 Toolbar 開發實踐總結

不設定Theme的效果

最後,再給知乎的主頁面做個小小的優化,它在 Android 4.4 上執行還是能夠看到一條黑乎乎的通知欄,為此我把 ToolbarTranslucent System Bar 的特性結合起來,最終改進成下面的效果(附上 Android4.4 和 5.1 上的執行效果)。

Android開發:最詳細的 Toolbar 開發實踐總結

Android4.4上改進版的知乎主頁

Android開發:最詳細的 Toolbar 開發實踐總結

Android5.1上改進版的知乎主頁

如果你還不知道 Translucent System Bar 的特性怎麼使用,請檢視我的上一篇文章: Translucent System Bar 的最佳實踐

總結

關於 Toolbar 的使用就介紹到此,本來是懷著很簡單就可以上手的心態來使用,結果發現還是有很多坑需要填。果然還是驗證了一句老話

紙上得來終覺淺,絕知此事要躬行

對於想要更深的瞭解 Toolbar 設計的童鞋,也可以看看這篇 官網文件 (自備梯子)。

同樣,分享即美德,需要原始碼的童鞋,請戳: https://github.com/D-clock/AndroidSystemUiTraining

相關文章