Material Design 控件之 Toolbar 非完全解析

一、Toolbar 出現(xiàn)的背景

Toolbar 是 Android5.0 中新引入的一個控件,其出現(xiàn)的目的就是為了取代 ActionBar。
Actionbar 在 Android3.0 推出的目的就是為了在 UI 界面中引入一個全局導航的功能,取代 Android3.0 之前的標題欄。并且在剛推出不久就發(fā)布了兼容到 API 7 的兼容包,但是很多應用都沒有使用 Actionbar。
原因是 Actionbar 的界定很模糊,因為 Android 系統(tǒng)中把界面分成兩大部分,一部分是 System UI,主要由 Statusbar 和 Android4.0 以后出現(xiàn)的由虛擬按鍵構(gòu)成的導航欄為主,對于系統(tǒng) UI 來說,Android 不允許應用開發(fā)者對其進行完全控制,在 Android5.0 之后甚至不讓開發(fā)者控制狀態(tài)欄;另一部分就是應用 UI,對于這部分 UI,開發(fā)者擁有完全控制權(quán)。
而 Actionbar 在顯示上應該算是應用 UI 的一部分,但是開發(fā)者又不能對其進行完全控制,因為它畢竟是由系統(tǒng)創(chuàng)建并對其進行相關(guān)參數(shù)的初始化。所以在實際開發(fā)中,很多開發(fā)者都是用布局生成一個模擬的 Actionbar 來代替系統(tǒng)的 Actionbar,基于這一點,Android 在5.0后推出一個新的控件 Toolbar 來取代 ActionBar。

二、Toolbar 的使用

Toolbar

1)導包

Toolbar 位于 android.widget 包下,在 Android5.0 + 版本可以直接使用,如果需要兼容 Android5.0 以下的版本,那么需要使用位于支持包 android.support.v7.widget 中的 Toobar,可以支持到 API7。
直接在 Module 的 build.gradle 文件中添加依賴

dependencies {
    compile 'com.android.support:appcompat-v7:25.3.1'
}

2)設置 Theme

要想使用 Toolbar 代替 Actionbar,必須使用 Theme.AppCompat 主題中沒有 Actionbar 的主題,把 Actionbar 隱藏掉,否則會造成沖突,NoActionbar 的主題對兩個 Window 屬性進行了重寫:

<style name="Theme.AppCompat.Light.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
</style>

經(jīng)過實際測試,發(fā)現(xiàn)其實只需要設置 <item name="windowNoTitle">true</item> 即可

3)xml 文件中添加

不同于 Actionbar ,系統(tǒng)會自動根據(jù)設置的 Theme 創(chuàng)建不同樣式的 Actionbar 添加到界面中,Toolbar 需要像普通控件一樣在布局文件中添加:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"/>
</LinearLayout>

但是運行之后會發(fā)現(xiàn)界面中 Actionbar 位置只有一個 Toobar 的android:background 屬性所指定顏色的背景,其它的什么都沒有,其實這個時候 Toolbar 已經(jīng)出現(xiàn)了,只是因為我們除了 background 外沒有給 Toolbar 設置任何屬性,ActionBar 的屬性是系統(tǒng)自動初始化的,Toolbar 需要我們進行設置。
給 Toolbar 設置屬性有三種方式,可以結(jié)合使用:

1、調(diào)用 setSupportActionbar/setActionbar 方法和 ·getSupportActionbar/getActionbar 把 Toolbar 作為 Actionbar 處理
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }
}

通過 findViewById 方法獲取到 xml 文件中添加的 Toolbar 控件,然后調(diào)用 setSupportActionbar 方法,如果只支持 5.0 以上系統(tǒng)調(diào)用 setActionbar 方法,這個方法是把 Toolbar 當作 Actionbar 來處理,把系統(tǒng)給 Actionbar 設置的相關(guān)初始參數(shù)設置給 Toolbar,之前關(guān)于 Actionbar 的大部分操作都會應用在 Toolbar 上。
setSupportActionbar 是 AppCompatActivity 中的方法,所以使用這種方式的 Activity 必須繼承 AppCompatActivity。
這里的參數(shù)是 Toolbar 而并非 Actionbar,簡單看一下源碼:

AppCompatActivity#setSupportActionbar
public void setSupportActionBar(@Nullable Toolbar toolbar) {
    getDelegate().setSupportActionBar(toolbar);
}

只是簡單地調(diào)用了 AppCompatDelegate 的 setSupportActionbar 方法:

AppCompatDelegateImplV7#setSupportActionbar

AppCompatDelagate 類是個抽象類,它的實現(xiàn)類 AppCompatDelegateImplV7 實現(xiàn)了 setSupportActionbar 方法:

@Override
public void setSupportActionBar(Toolbar toolbar) {
    if (!(mOriginalWindowCallback instanceof Activity)) {
        return;
    }

    final ActionBar ab = getSupportActionBar();
    if (ab instanceof WindowDecorActionBar) {
        throw new IllegalStateException("This Activity already has an action bar supplied " +
                "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
                "windowActionBar to false in your theme to use a Toolbar instead.");
    }

    ToolbarActionBar tbab = new ToolbarActionBar(toolbar, ((Activity) mContext).getTitle(),
            mAppCompatWindowCallback);
    setSupportActionBar(tbab);
    mWindow.setCallback(tbab.getWrappedWindowCallback());
    tbab.invalidateOptionsMenu();
}

這段方法的邏輯非常簡單,首先會判斷當前的窗口回調(diào)是否是一個
Activity 對象,因為只有 Activity 才能夠支持創(chuàng)建 ActionBar,不是的話就返回,然后就會嘗試去獲取當前的 ActionBar,如果發(fā)現(xiàn)當前 Activity 中已經(jīng)有了 ActionBar 就會拋出一個異常,這就是我們要設置 Toolbar 來替代 ActionBar 那么就必須去掉原有的
ActionBar 的原因,當然如果只是將 Toolbar 作為一個普通的控件,就不是必須的了。
然后將傳入的 Toolbar 對象作為入?yún)?gòu)造了一個 ToolbarActionBar 對象,隨后馬上調(diào)用了 setSupportActionBar 方法并將這個
ToolbarActionBar 對象傳入,來看 ToolbarActionBar 類:

ToolbarActionBar
public class ToolbarActionBar extends ActionBar {
    // ......省略一些代碼......

    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback callback) {
        mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
        mWindowCallback = new ToolbarCallbackWrapper(callback);
        mDecorToolbar.setWindowCallback(mWindowCallback);
        toolbar.setOnMenuItemClickListener(mMenuClicker);
        mDecorToolbar.setWindowTitle(title);
    }

    // ......省略大量代碼......
}

在 ToolbarActionBar 的構(gòu)造方法中,又將 toolbar 作為入?yún)?gòu)造了一個 ToolbarWidgetWrapper 對象,從類名里就可以看出,這個
ToolbarWidgetWrapper 是個包裝類,它持有 Toolbar 的引用,其實用心觀察 ToolbarActionBar 這個類你會發(fā)現(xiàn)他是一個代理類,其中大部分方法都是間接由 mDecorToolbar 這個對象調(diào)用,mDecorToolbar 對象的實際類型就是剛才我們所說的
ToolbarWidgetWrapper,而引用類型則是 DecorToolbar,ToolbarActionBar 這個類以委托代理的方式將自身的功能交由
mDecorToolbar 實現(xiàn):

public class ToolbarActionBar extends ActionBar {
    // ......省略一些代碼......

    @Override
    public void setIcon(int resId) {
        mDecorToolbar.setIcon(resId);
    }

    @Override
    public void setIcon(Drawable icon) {
        mDecorToolbar.setIcon(icon);
    }

    @Override
    public void setLogo(int resId) {
        mDecorToolbar.setLogo(resId);
    }

    @Override
    public void setLogo(Drawable logo) {
        mDecorToolbar.setLogo(logo);
    }

    // ......省略一些代碼......
}

mDecorToolbar 的方法中對持有的 Toolbar 成員變量進行相關(guān)設置。
ToolbarActionBar 里面有一些方法是空的,那么 Actionbar 的相關(guān)屬性就沒有被關(guān)聯(lián)到 Toolbar 中,其中就包括 android:background'屬性,如果我們不給 Toolbar 設置該屬性的話, Toolbar 就會默認顯示 Window 窗口的背景色,即 Activity 所使用的主題中 android:windowBackground 屬性指定的顏色。

也可以調(diào)用 getSupportActionbar 方法獲取被當做 Actionbar 處理的 Toolbar 對象,并調(diào)用 Actionbar 的相關(guān)方法對 Toolbar 進行操作,這個時候 Toolbar 就是一個 Actionbar。

2、調(diào)用 Toolbar 的相關(guān)方法

Toolbar 自身也對外提供了一系列的方法對 Toolbar 進行相關(guān)設置,這個時候不需要調(diào)用 setSupportActionbar 方法,Activity 也就不必非要繼承 AppCompatActivity,而且即使不隱藏 Actionbar,也不會造成沖突報錯,但是 Actionbar 會占用 Toolbar 的位置,將 Toolbar 擠到下面去,所以作為導航欄使用的話,一般還是要隱藏掉 Actionbar。

//設置導航圖標
setNavigationIcon(@DrawableRes int resId)
setNavigationIcon(@Nullable Drawable icon)
setNavigationOnClickListener(OnClickListener listener)
//設置 Logo
setLogo(@DrawableRes int resId)
setLogo(Drawable drawable)
//設置標題
setTitle(@StringRes int resId)
setTitle(CharSequence title)
//設置標題文本顏色
setTitleTextColor(@ColorInt int color)
//設置標題外觀,包括字體顏色、大小、樣式等
setTitleTextAppearance(Context context, @StyleRes int resId)
//設置標題邊距像素
setTitleMargin(int start, int top, int end, int bottom)
setTitleMarginStart(int margin)
setTitleMarginTop(int margin)
setTitleMarginEnd(int margin)
setTitleMarginBottom(int margin)
//設置副標題
setSubtitle(@StringRes int resId)
setSubtitle(CharSequence subtitle)
//設置副標題文本顏色
setSubtitleTextColor(@ColorInt int color)
//設置副標題外觀,包括字體顏色、大小、樣式等
setSubtitleTextAppearance(Context context, @StyleRes int resId)
//加載菜單
inflateMenu(@MenuRes int resId)
//監(jiān)聽菜單點擊
setOnMenuItemClickListener(OnMenuItemClickListener listener)
//設置彈出菜單主題
setPopupTheme(@StyleRes int resId)
//設置溢出菜單圖標
setOverflowIcon(@Nullable Drawable icon)
3、在 xml 文件中設置屬性

就像普通控件一樣,需要注意的是一些屬性的命名空間并不是 'android',需要在根布局聲明自定義的命名空間,比如這里使用 app

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

這里的屬性是很多的:

        //應用圖標、導航圖標、收縮圖標
        app:logo=""
        app:logoDescription=""
        app:navigationIcon=""
        app:navigationContentDescription=""
        app:collapseIcon=""
        app:collapseContentDescription=""
        //樣式
        app:theme=""
        //彈窗樣式
        app:popupTheme=""
        //標題樣式
        app:title=""
        app:titleTextAppearance=""
        app:titleTextColor=""
        app:titleMargin=""
        app:titleMargins=""
        app:titleMarginStart=""
        app:titleMarginTop=""
        app:titleMarginBottom=""
        app:titleMarginEnd=""
        //副標題樣式
        app:subtitle=""
        app:subtitleTextAppearance=""
        app:subtitleTextColor=""
        //按鈕
        app:buttonGravity=""
        app:maxButtonHeight=""
        //一些邊距
         app:contentInsetEnd=""
        app:contentInsetLeft=""
        app:contentInsetRight=""
        app:contentInsetStart=""
        app:contentInsetEndWithActions=""
        app:contentInsetStartWithNavigation=""
        app:paddingEnd=""
        app:paddingStart=""
app:popupTheme 和 app:theme

app:popupTheme 用來設置溢出菜單的樣式,通常繼承一個 ThemeOverlay.AppCompat 主題,當然并不是必須的,然后重寫相關(guān)屬性:

<style name="PopupTheme" parent="ThemeOverlay.AppCompat.Light">
        <item name="android:colorBackground">@color/colorAccent</item>
        <item name="android:textColor">@color/colorPrimary</item>
        <item name="android:textSize">22sp</item>
    </style>
  • android:colorBackground 屬性設置的是溢出菜單的背景顏色;
  • android:textColor 屬性設置的是溢出菜單的字體顏色;
  • android:textSize 屬性設置的是溢出菜單的字體大??;

而如果想要修改 Toolbar 上的菜單字體顏色和大小則需要使用 android:popupTheme 屬性來設置主題,同樣繼承一個系統(tǒng)主題,然后重寫相關(guān)屬性:

<style name="PopupTheme" parent="ThemeOverlay.AppCompat.Light">
        <item name="android:textSize">22sp</item>
        <item name="actionMenuTextColor">#00FF00</item>
    </style>
  • android:textSize 設置菜單字體大小,如果沒有在 android:popupTheme 的主題中修改字體大小,這個屬性同樣會作用于溢出菜單;
  • actionMenuTextColor 設置菜單的顏色,注意這個是不帶命名空間的;

如果在 android:theme 的主題中重寫了 android:colorBackgroundandroid:textColor 屬性同樣可以修改溢出菜單的樣式。

自定義View

Toolbar 繼承自 ViewGroup,我們可以直接在 Toolbar 中添加其它控件,添加的控件會顯示在 Title、SubTitle 和 ActionMenu 之間,如果設置自定義 View 的 android:layout_width="match_parent 會覆蓋掉 Title 和 SubTitle。

參考: Toolbar:上位的小三
Material Design 之 Toolbar 開發(fā)實踐總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容