官方所定義的型態有兩種 : Regular、Contextual top app bar,但我個人認為應該是三種,那第三種就是 Prominent top app bar,只不過它在設置的過程中,算是 Regular top app bar 的變形,但設計的方式卻不太相同,下面會再提到
是我們最常見的 top app bar,有著 title、navigation icon、menu icon 等等元件配置,作為主畫面的頂層應用欄位,所對應的功能對用戶來說應該是最常見與重要的
使用上,可以透過下列屬性,來設置 title、navigation icon、menu icon
in layout
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:menu="@menu/top_bar"
app:navigationIcon="@drawable/ic_baseline_menu_24"
app:title="Page title" />
in menu
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/favorite"
android:icon="@drawable/ic_favorite_24dp"
android:title="@string/favorite"
android:contentDescription="@string/content_description_favorite"
app:showAsAction="ifRoom" />
<item
android:id="@+id/search"
android:icon="@drawable/ic_search_24dp"
android:title="@string/search"
android:contentDescription="@string/content_description_search"
app:showAsAction="ifRoom" />
<item
android:id="@+id/more"
android:title="@string/more"
android:contentDescription="@string/content_description_more"
app:showAsAction="never" />
</menu>
in code
設置點擊事件的話,若想讓每個 menu icon 都有不同的事件觸發,要記得去寫判斷式
topAppBar.setNavigationOnClickListener {
// Handle navigation icon press
}
topAppBar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.favorite -> {
// Handle favorite icon press
true
}
R.id.search -> {
// Handle search icon press
true
}
R.id.more -> {
// Handle more item (inside overflow menu) press
true
}
else -> false
}
}
如果情境上想讓我們的 top bar 隨著畫面的滑動收起,就必須用 CoordinatorLayout、AppBarLayout 去包裹這個 top bar,還要設定一些屬性,就能成功設置 Scrolling behavior
若是對這兩個佈局元件不太理解,可以到這邊的官方文件去閱讀一下,礙於篇幅就不多做解析
這邊附上圖解讓大家比較清楚理解之間的層級
in layout
<androidx.coordinatorlayout.widget.CoordinatorLayout
...>
<com.google.android.material.appbar.AppBarLayout
...
app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar
...
app:layout_scrollFlags="scroll|enterAlways|snap"
/>
</com.google.android.material.appbar.AppBarLayout>
...
</androidx.coordinatorlayout.widget.CoordinatorLayout>
上面可以看到設置了兩個屬性,app:liftOnScroll
、app:layout_scrollFlags
app:liftOnScroll
: 滾動時,會增加 View 的高度並讓 Scrolling View 在其後面滾動
app:layout_scrollFlags
: 設置滾動行為的 flag,分別有 scroll、noScroll、enterAlways、enterAlwaysCollapsed、exitUntilCollapsed、snap、snapMargins
這邊我實作了一個範本供大家參考,細節部分都可以到我的 GitHub 上查閱
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|snap|enterAlways|enterAlwaysCollapsed"
app:menu="@menu/top_bar"
app:navigationIcon="@drawable/ic_baseline_menu_24"
</com.google.android.material.appbar.AppBarLayout>
實作上,與 Regular bar 大同小異,差別在於 bar 的 height 較大,還有一個新的 layout 要設定CollapsingToolbarLayout
: 是一種可摺疊的佈局設計,可以讓當中的View 隨著滑動而摺疊變化
<androidx.coordinatorlayout.widget.CoordinatorLayout
...>
<com.google.android.material.appbar.AppBarLayout
...
android:layout_height="128dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:expandedTitleMarginStart="72dp"
app:expandedTitleMarginBottom="28dp"
<com.google.android.material.appbar.MaterialToolbar
...
android:elevation="0dp"
/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
...
</androidx.coordinatorlayout.widget.CoordinatorLayout>
而有一點要注意的是,將 Toolbar elevation 設為 0dp ,是因為要讓整個 Toolbar 與 AppLayout 融為一體,所以不應設置高度,否則會出現如圖下的狀況,Toolbar 會太過突出,與背景在不同的層次上
in theme
在將 image 加入的 top bar 之前,我們要先把 status bar 的顏色改為半透明,這樣才能完整的呈現 image
透過改寫 theme 的 android:windowTranslucentStatus
,設為 true 會將其變為半透明
<style name="Theme.App" parent="Theme.MaterialComponents.*.NoActionBar">
<item name="android:windowTranslucentStatus">true</item>
</style>
in code
若不想因為這樣將整個 App status bar 都設為半透明,可在當前頁面 onCreate
時去設定
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.window?.statusBarColor = resources.getColor(
com.google.android.material.R.color.mtrl_btn_transparent_bg_color,
null
)
}
in layout
將 ImageView 元件放入到CollapsingToolbarLayout
中,並將 toolbar 的背景顏色改為 transparent (透明),這樣 toolbar 才不會阻擋到 imageView
<androidx.coordinatorlayout.widget.CoordinatorLayout
...
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
...
android:layout_height="152dp"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
...
android:fitsSystemWindows="true">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/media"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
/>
<com.google.android.material.appbar.MaterialToolbar
...
android:background="@android:color/transparent"
/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
...
</androidx.coordinatorlayout.widget.CoordinatorLayout>
android:fitsSystemWindows
: 這個屬性用於告知父節點要為 system windows 保留一些 padding,避免與手機的 windows 畫面相撞在一起
在 Prominent top app 的滑動行為上,由於 top bar 放入 ImageView 整體高度會占滿畫面的四分之一左右,所以在會希望若主畫面下方還有內容並往下滑動查看時,能把 Prominent 縮小為 Regular bar,甚至是收起,才能讓 top bar 不至於阻擋到用戶能操作的畫面
in layout
<androidx.coordinatorlayout.widget.CoordinatorLayout
...>
<com.google.android.material.appbar.AppBarLayout
...>
<com.google.android.material.appbar.CollapsingToolbarLayout
...
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:contentScrim="?attr/colorPrimary"
app:statusBarScrim="?attr/colorPrimaryVariant">
...
<com.google.android.material.appbar.MaterialToolbar
...
app:layout_collapseMode="pin"
/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
...
</androidx.coordinatorlayout.widget.CoordinatorLayout>
在官方的範例中,我們又看到了幾個新的屬性要設置,這邊簡單解說一下app:contentScrim
: 設置 CollapsingToolbarLayout 下滑收起時的顏色app:statusScrim
: 設置 CollapsingToolbarLayout 下滑時改變 statusBar 的顏色app:layout_collapseMode
: Toolbar 對應 collapse 的行為,有兩個模式可選pin、parallax
若是想改變 icon 顏色,是沒有屬性去直接修改的,因為它是直接引用 menu drawable 的,所以應該去直接更換圖檔的顏色,不能透過 tint or theme 去做修改
為所選項目提供操作。將 top bar 轉為 Contextual action bar,在執行操作或關閉之前保持活動狀態
應用上,它不是我們可自行去加入的元件,是要透過 ActionMode Callback 呼叫出來,整體的設計已經固定的,能改動的部分,可以透過 theme、style 與 menu 去客製化
由於無法像一般元件一樣,直接在 XML 去透過屬性設定,就只能透過 theme、style,寫出一個 ActionMode 風格檔來應用windowActionModeOverlay
: 是為了讓 actionMode 能去覆蓋 Toolbar ,否則 toolbar 會往下排列。actionModeCloseDrawable
: 設置最左方的 navigation icon 圖示
<style name="Theme.App" parent="Theme.MaterialComponents.*.NoActionBar">
...
<item name="windowActionModeOverlay">true</item>
<item name="actionModeStyle">@style/Widget.App.ActionMode</item>
<item name="actionModeCloseDrawable">@drawable/ic_close_24dp</item>
<item name="actionBarTheme">@style/ThemeOverlay.MaterialComponents.Dark.ActionBar</item>
</style>
在 style 中,我們能透過 ActionMode 的 textStyle、background 來改變字體與顏色
<style name="Widget.App.ActionMode" parent="Widget.AppCompat.ActionMode">
<item name="titleTextStyle">?attr/textAppearanceHeadline6</item>
<item name="subtitleTextStyle">?attr/textAppearanceSubtitle1</item>
<item name="background">@color/material_grey_900</item>
</style>
在 menu 中,與 toolbar 一樣,能自定義我們想要的 menu icon
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/share"
android:icon="@drawable/ic_share_24dp"
android:title="@string/share"
app:showAsAction="ifRoom" />
<item
android:id="@+id/delete"
android:icon="@drawable/ic_delete_24dp"
android:title="@string/delete"
app:showAsAction="ifRoom" />
<item
android:id="@+id/more"
android:title="@string/more"
app:showAsAction="never" />
</menu>
呼叫 Contextual action bar 必須透過 actionMode。所以在執行之前,必須先去覆寫 callback 的東西onCreateActionMode
: 設置 menu layoutonPrepareActionMode
: 如果 menu 或 action 被刷新的話,回傳 ture,這邊回傳 false 因為沒有要變動刷新onActionItemClicked
: 設置 menu icon 的點擊事件onDestroyActionMode
: 結束 actionMode 會呼叫此方法,如果是有配合 navigation 切換畫面的話,這邊可使用 navigate UP,返回前一個畫面
val callback = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
menuInflater.inflate(R.menu.contextual_action_bar, menu)
// in not in activity
activity?.menuInflater?.inflate(R.menu.contextual_action_bar, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return false
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return when (item?.itemId) {
R.id.share -> {
// Handle share icon press
true
}
R.id.delete -> {
// Handle delete icon press
true
}
R.id.more -> {
// Handle more item (inside overflow menu) press
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
}
}
// startSupport 會直接執行設定好的 actionMode.Callback
// in Activity
val actionMode = startActionMode(callback)
// in Fragment
val actionMode = requireActivity().startActionMode(callback)
actionMode?.title = "1 selected"
屬性方面,由於 Contextual 是透過 Callback 生成,在設置上選擇沒有一般的 Regular bar 還多,基本上都是透過 style、theme 來設定。大概看過就好,或是有需要的特定屬性的話,可以來這邊找看看
學習完上述各種屬性設定,就可以依照剛剛所介紹的過 attributes 去做修改。這邊就用我自己魔改的範例
Regular bar,修改了字體與背景色
<style name="Widget.App.Toolbar" parent="Widget.MaterialComponents.Toolbar.Primary">
<item name="materialThemeOverlay">@style/ThemeOverlay.App.Toolbar</item>
<item name="titleTextAppearance">@style/TextAppearance.MaterialComponents.Headline6</item>
<item name="subtitleTextAppearance">@style/TextAppearance.App.Subtitle1</item>
</style>
<style name="ThemeOverlay.App.Toolbar" parent="">
<item name="colorPrimary">@color/darkBlue</item>
<item name="colorPrimaryVariant">@color/lightBlue</item>
<item name="colorOnPrimary">@color/white</item>
</style>
Contextual bar,就如同上述剛剛提到的那樣,這邊在貼一次,改動了字體與背景色
<style name="Theme.App" parent="Theme.MaterialComponents.*.NoActionBar">
...
<item name="windowActionModeOverlay">true</item>
<item name="actionModeStyle">@style/Widget.App.ActionMode</item>
<item name="actionModeCloseDrawable">@drawable/ic_close_24dp</item>
<item name="actionBarTheme">@style/ThemeOverlay.MaterialComponents.Dark.ActionBar</item>
</style>
<style name="Widget.App.ActionMode" parent="Widget.AppCompat.ActionMode">
<item name="titleTextStyle">?attr/textAppearanceHeadline6</item>
<item name="subtitleTextStyle">?attr/textAppearanceSubtitle1</item>
<item name="background">?attr/colorSurface</item>
</style>
Top bar 是包裹著 icon、title 的組件,若是不使用它也能做到類似的效果,利用 LinearLayout、TextView、ImageIcon 等等就能組成相同的效果,但這過程要耗費大量時間,除非是設計師的要求 Material Design 無法滿足。而 Contextual action bar 只是簡單的帶過,若是想設計的更加客製化,還會遇到更多坑,若是有興趣的可以挖官方文檔
若對實作還是有點不懂的,這邊提供我的 Github 方便大家參考