iT邦幫忙

2022 iThome 鐵人賽

DAY 15
1

BottomSheets 分成兩種

  1. Standard bottom sheet
  2. Modal bottom sheet
    https://ithelp.ithome.com.tw/upload/images/20220929/201444699ruCrvSfOI.png

M3新增內容

  • 顏色:新的顏色對映和與動態顏色的相容性
  • 形狀:底部板材的頂角半徑為28dp
  • 佈局:新的最大寬度為640dp和可選的拖動手柄

實作上探討

1. Standard bottom sheet

StandardBottomSheet 是與主 UI 區域共存,並允許同時查看兩個區域並且可以互相交互,可以垂直拖動或向下滑動來完全關閉。

影片中點擊按鈕簡單的置換BottomSheet內的文字,同時有顯示了處於折疊(有圓角)和展開(沒有圓角)狀態的BottomSheet

https://ithelp.ithome.com.tw/upload/images/20220929/20144469XI3xol9PDC.png

為什麼外層需是CoordinatorLayout而不是其他的佈局layout,從程式碼中可看到BottomSheetBehavior是繼承CoordinatorLayout。
https://ithelp.ithome.com.tw/upload/images/20220927/20144469zEPKcZwqiZ.png

 <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/standard_bottom_sheet"
            style="@style/Widget.Material3.BottomSheet"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

            <!-- Drag handle for accessibility -->
            <com.google.android.material.bottomsheet.BottomSheetDragHandleView
                android:id="@+id/drag_handle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

            <!-- Bottom sheet contents. -->
            <TextView
                android:id="@+id/tvBottomContents"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="Title" />

        </LinearLayout>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>

完整程式碼

  • LinearLayout排版
  • CoordinatorLayout的子層新增設定Behavior attributes app:layout_behavior指定 BottomSheet類型
    app:layout_behavior=“com.google.android.material.bottomsheet.BottomSheetBehavior”
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="20dp"
        android:text="Text" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Enabled" />

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/standard_bottom_sheet"
            style="@style/Widget.Material3.BottomSheet"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

            <!-- Drag handle for accessibility -->
            <com.google.android.material.bottomsheet.BottomSheetDragHandleView
                android:id="@+id/drag_handle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

            <!-- Bottom sheet contents. -->
            <TextView
                android:id="@+id/tvBottomContents"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="Title" />

        </LinearLayout>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</LinearLayout>

2. Modal bottom sheet

展開狀態時的背景呈現灰色,同時阻止與畫面其餘元件的交互點擊,可以垂直拖動或向下滑動來關閉,或是點擊對灰色部分也可以關閉BottomSheet。

https://ithelp.ithome.com.tw/upload/images/20220929/20144469WXNaw230kY.png

使用BottomSheetDialogFragment實作
BottomSheetDialogFragment and overwrite onCreateView後inflater要顯示的layout (modal_bottom_sheet_content.xml)。

// ModalBottomSheet 繼承 BottomSheetDialogFragment
class ModalBottomSheet : BottomSheetDialogFragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? = inflater.inflate(R.layout.modal_bottom_sheet_content, container, false)

    companion object {
        const val TAG = "ModalBottomSheet"
    }
}

modal_bottom_sheet_content.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

    <!-- Drag handle for accessibility -->
    <com.google.android.material.bottomsheet.BottomSheetDragHandleView
        android:id="@+id/drag_handle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <!-- Bottom sheet contents. -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="20dp"
        android:text="Title" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="20dp"
        android:text="Title" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="20dp"
        android:text="Title" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="20dp"
        android:text="Title" />


</LinearLayout>

BottomSheetDialogFragment是AppCompatFragment的一個子類,所以需要在繼承AppCompatActivity得頁面上show ModalBottomSheet。

// MainActivity 透過 ModalBottomSheet show 來呼叫顯示
class MainActivity : AppCompatActivity() {
    ...
    val modalBottomSheet = ModalBottomSheet()
    modalBottomSheet.show(supportFragmentManager, ModalBottomSheet.TAG)
    ...
}

如果需求BottomSheetDialogFragment要可以控制取消、或是關閉。
從原碼中註解說明ModalBottomSheet是DialogFragment中的版本,所以會需要繼承BottomSheetDialogFragment。
https://ithelp.ithome.com.tw/upload/images/20220927/20144469IxyqRHlkcH.png
可以使用DialogFragment有提供的方法,皆可以透過override onCancel(DialogInterface) onDismiss(DialogInterface),就不用在寫setOnCancelListener or setOnDismissListener

Anatomy and key properties

https://ithelp.ithome.com.tw/upload/images/20220927/20144469xT0nrxn6Es.png

  1. Sheet
  2. Content
  3. Scrim (in modal bottom sheets)

Sheet attributes 的xml Attribute的設定、可以呼叫Method方法、預設的Default值
https://ithelp.ithome.com.tw/upload/images/20220927/20144469f63rcYjfPP.png
Behavior attributes 的xml Attribute的設定、可以呼叫Method方法、預設的Default值
https://ithelp.ithome.com.tw/upload/images/20220927/2014446960Bsm5RIe5.png

範例StandardBottomSheet設定behavior

使用viewBinding取得layout的resources的Id,當然也可以用其他方式取到layout的resources的Id

val standardBottomSheet = binding.standardBottomSheet
val standardBottomSheetBehavior = BottomSheetBehavior.from(standardBottomSheet)

// 一開始顯示的折疊時的高度
standardBottomSheetBehavior.**peekHeight** = 350 

// true 向下滑動時是可以隱藏
// false 向下滑動時不可以隱藏
// setHideable會提示換成isHideable,**實測預設是True下滑可以隱藏**
standardBottomSheetBehavior.**setHideable**(false) 

// true 展開一次後隱藏時是否應跳過折疊狀態。同時setHideable(true)是可隱藏的,否則將此設置為true無效。
// false 下滑時可以先回到折疊狀態
standardBottomSheetBehavior.**skipCollapsed** = true

// true 展開的高度是由其內容的高度決定 
// false 是分兩個階段展開(外層layout的一半高度,外層layout的全高)
// setFitToContents 會提示換成 isFitToContents
standardBottomSheetBehavior.**isFitToContents** = false 

// true 是可以上下拖動折疊或展開
// false 不行上下拖動折疊或展開,固定位置
standardBottomSheetBehavior.**isDraggable** = true 

// 當isFitToContents 為 false 時使用
// 調整「外層layout的一半高度的高度」,會隨著這個比率的降低而變小,而隨著它的增加而變高
// 必須小於 1.0 (不包含1.0)
standardBottomSheetBehavior.halfExpandedRatio = 0.2f

// 當isFitToContents 為 false 時使用
// 調整「外層layout的全高」主UI上最靠近的元件要間隔多少
standardBottomSheetBehavior.expandedOffset = 100

儲存配置更改時的行為
https://ithelp.ithome.com.tw/upload/images/20220927/20144469bSb6tgWRkM.png

  • SAVE_PEEK_HEIGHT: app:behavior_peekHeight  折疊時的高度 儲存下來
  • SAVE_HIDEABLE: app:behavior_hideable 隱藏儲存下來
  • SAVE_SKIP_COLLAPSED: app:behavior_skipCollapsed 儲存下來
  • SAVE_FIT_TO_CONTENTS: app:behavior_fitToContents 儲存下來
  • SAVE_ALL: 上述所有屬性都被保留下來
  • SAVE_NONE: 沒有保留任何屬性。 使用預設值

使用者點擊滑動狀態

// 設定方式
standardBottomSheetBehavior.saveFlags = BottomSheetBehavior.SAVE_ALL

// 使用在監聽BottomSheetCallback狀態變更時用
behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
            
            override fun onStateChanged(bottomSheet: View, newState: Int) {
                when(newState){
                    BottomSheetBehavior.STATE_EXPANDED->{}
                    BottomSheetBehavior.STATE_COLLAPSED->{}
                    BottomSheetBehavior.STATE_DRAGGING->{}
                    BottomSheetBehavior.STATE_SETTLING->{}
                    BottomSheetBehavior.STATE_HIDDEN->{}
                }
            }

            override fun onSlide(bottomSheet: View, slideOffset: Float) {

            }

        })
  • STATE_COLLAPSED:已折疊的高度,有足夠的高度讓使用者互動。
  • STATE_EXPANDED:展開到最大高度。
  • STATE_HALF_EXPANDED:是半展開的(僅在行為_fitToContents設定為false時才適用。
  • STATE_HIDDEN:底部被隱藏,只能以寫程式方式重新顯示。
  • STATE_DRAGGING:使用者正在主動上下拖動。
  • STATE_SETTLING:拖動/滑動手勢後,檢視從脫離手指自由滑動到最終停下的這一小段時間

/images/emoticon/emoticon29.gif
參考資料:Material Design Component Bottom Sheets


上一篇
Day14 延續前實戰新增 FAB + Bottom App Bar 實作
下一篇
Day16 使用M3的Dialogs
系列文
Kotlin 實踐 Material Design 懶人包30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言