iT邦幫忙

2022 iThome 鐵人賽

DAY 7
0

大綱

  • Type
  • Regular top app bar
    • Simple Using
    • Applying scrolling behavior
    • Prominent top app bar
      • Adding a image
      • Applying scrolling behavior
    • Anatomy
    • Key attributes
    • Style
  • Contextual action bar
    • Using
    • Anatomy
    • Key attributes
    • Style
  • Custom Style

Type

官方所定義的型態有兩種 : Regular、Contextual top app bar,但我個人認為應該是三種,那第三種就是 Prominent top app bar,只不過它在設置的過程中,算是 Regular top app bar 的變形,但設計的方式卻不太相同,下面會再提到
https://ithelp.ithome.com.tw/upload/images/20220922/20151680lMiHxI6hbi.png

  • Prominent top app bar

https://ithelp.ithome.com.tw/upload/images/20220922/20151680TX1zJeRTto.png

Regular top app bar

是我們最常見的 top app bar,有著 title、navigation icon、menu icon 等等元件配置,作為主畫面的頂層應用欄位,所對應的功能對用戶來說應該是最常見與重要的

Simple Using

使用上,可以透過下列屬性,來設置 title、navigation icon、menu icon
https://ithelp.ithome.com.tw/upload/images/20220922/20151680tjjDrr466f.png
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
    }
}

Applying scrolling behavior

如果情境上想讓我們的 top bar 隨著畫面的滑動收起,就必須用 CoordinatorLayout、AppBarLayout 去包裹這個 top bar,還要設定一些屬性,就能成功設置 Scrolling behavior

若是對這兩個佈局元件不太理解,可以到這邊的官方文件去閱讀一下,礙於篇幅就不多做解析

這邊附上圖解讓大家比較清楚理解之間的層級
https://ithelp.ithome.com.tw/upload/images/20220922/20151680Pg0OBjKWnu.png

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:liftOnScrollapp:layout_scrollFlags

app:liftOnScroll : 滾動時,會增加 View 的高度並讓 Scrolling View 在其後面滾動

app:layout_scrollFlags : 設置滾動行為的 flag,分別有 scroll、noScroll、enterAlways、enterAlwaysCollapsed、exitUntilCollapsed、snap、snapMargins

  • scroll: 會跟隨滾動事件一起發生移動而滾出或滾進螢幕。而這邊有兩點要注意
    1. 如果在當前這個 Appbar layout 有兩個以上的元件,若是在上方的元件沒有設置 scroll flag ,則就算下方的元件有設置,也會一併失效
    2. 若要使用其他滾動的 flag,必須要先設置這個 scroll flag
  • noScroll : 不會跟隨滾動事件一起發生移動而滾出或滾進螢幕
  • enterAlways : 就是向下滾動時優先順序問題,scroll 首先滑動的會是列表,列表的資料全部滾動完,才開始滾動 toolbar 。而** scroll | enterAlways 讓滑動的首先是 top bar** ,然後再去滑動其他的 view
  • enterAlwaysCollapsed : 是 enterAlways 的附加 flag,這裡涉及到 top bar 的最小高度,向下滾動時,top bar 先向下滾動最小高度值,然後 Scrolling View 開始滾動,到達邊界時,top bar 再向下滾動,直至顯示完全。如果向上滾動,則會顯示出最小高度,直到滾動到最上方時完全顯現。在用戶體感上,做出一種向下滑動就隱藏,向上滑動就出現的交互效果
  • exitUntilCollapsed : 這裡也涉及到最小高度。發生向上滾動事件時,top bar 向上滾動直至最小高度,然後Scrolling View 開始滾動。也就是,top bar 不會完全退出螢幕
  • snap : 滾動比例的吸附效果,動畫上會呈現一種彈跳感。也就是說 top bar 不會存在區域性顯示的情況,只要滾動到 top bar 的部分高度,當用戶鬆開手指時,top bar 要麼向上全部滾出螢幕,要麼向下全部滾進螢幕
  • snapMargins : 是必須配合 sna 一起使用的額外的 flag。代表這個 View 將會被 snap 到它的頂部外邊距和它的底部外邊距的位置,而不是這個 View 自身的上下邊緣

這邊我實作了一個範本供大家參考,細節部分都可以到我的 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>

Prominent top app bar

實作上,與 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 會太過突出,與背景在不同的層次上

https://ithelp.ithome.com.tw/upload/images/20220922/20151680zLl0mX3Ulw.png

Adding a image

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 畫面相撞在一起

Applying scrolling behavior

在 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

  • pin : 當 CollapsingToolbarLayout 完全收縮時,讓設置的 View 元件 保留在畫面上
  • parallax : 滑動畫面時,CollapsingToolBarLayou 中的 View 也會同時滾動,並且漸漸地消失,達成一種視差的效果

Anatomy

https://ithelp.ithome.com.tw/upload/images/20220922/20151680EfbZEjjQzp.png

  1. Container
  2. Navigation icon (optional)
  3. Title (optional)
  4. Action items (optional)
  5. Overflow menu (optional)

Key attributes

Container

https://ithelp.ithome.com.tw/upload/images/20220922/20151680c4oV7D2ECR.png

Navigation icon

https://ithelp.ithome.com.tw/upload/images/20220922/20151680wWyXYYGrAu.png

Title

https://ithelp.ithome.com.tw/upload/images/20220922/20151680GkSFINPvWr.png

Action items

若是想改變 icon 顏色,是沒有屬性去直接修改的,因為它是直接引用 menu drawable 的,所以應該去直接更換圖檔的顏色,不能透過 tint or theme 去做修改
https://ithelp.ithome.com.tw/upload/images/20220922/20151680nHF2toeIZQ.png

Overflow menu

https://ithelp.ithome.com.tw/upload/images/20220922/20151680FgrEcgEKHV.png

Scrolling behavior

https://ithelp.ithome.com.tw/upload/images/20220922/20151680QAKKg5lIma.png

Style

AppBarLayout

https://ithelp.ithome.com.tw/upload/images/20220922/20151680ovBVx5a7Nk.png

MaterialToolbar

https://ithelp.ithome.com.tw/upload/images/20220922/20151680O6wKYCxDNo.png

CollapsingToolbarLayout

https://ithelp.ithome.com.tw/upload/images/20220922/20151680r6ngbN6GwK.png


Contextual action bar

為所選項目提供操作。將 top bar 轉為 Contextual action bar,在執行操作或關閉之前保持活動狀態

Using

應用上,它不是我們可自行去加入的元件,是要透過 ActionMode Callback 呼叫出來,整體的設計已經固定的,能改動的部分,可以透過 theme、style 與 menu 去客製化

In theme

由於無法像一般元件一樣,直接在 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>

in 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>

In menu

在 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>

In code

呼叫 Contextual action bar 必須透過 actionMode。所以在執行之前,必須先去覆寫 callback 的東西
onCreateActionMode : 設置 menu layout
onPrepareActionMode : 如果 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"

Anatomy

https://ithelp.ithome.com.tw/upload/images/20220922/20151680ibLlOrHrs7.png

  1. Close button (instead of a navigation icon)
  2. Contextual title
  3. Contextual actions
  4. Overflow menu (optional)
  5. Container (not shown)

Key attributes

屬性方面,由於 Contextual 是透過 Callback 生成,在設置上選擇沒有一般的 Regular bar 還多,基本上都是透過 style、theme 來設定。大概看過就好,或是有需要的特定屬性的話,可以來這邊找看看

Close button

https://ithelp.ithome.com.tw/upload/images/20220922/20151680QYRgeE71nF.png

Contextual title

https://ithelp.ithome.com.tw/upload/images/20220922/20151680tcI9b8YJQD.png

Contextual actions

https://ithelp.ithome.com.tw/upload/images/20220922/20151680ejdX8qvj70.png

Overflow menu

https://ithelp.ithome.com.tw/upload/images/20220922/20151680k7rA4ObqcV.png

Container attributes

https://ithelp.ithome.com.tw/upload/images/20220922/20151680pz3EyP4nFt.png

Style

https://ithelp.ithome.com.tw/upload/images/20220922/20151680bHzYuKHCCD.png


Custom Style

學習完上述各種屬性設定,就可以依照剛剛所介紹的過 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 方便大家參考


上一篇
Day 6 - App bar : Top ( Design )
下一篇
Day 8 - Slider ( Design )
系列文
從 Google Material Design Components 來了解與實作 Android 的 UI/UX 元件設計30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言