iT邦幫忙

2022 iThome 鐵人賽

DAY 19
1
Mobile Development

Kotlin 實踐 Material Design 懶人包系列 第 19

Day19 延續前實戰新增Bottom Sheets、Full-screen dialogs實作

  • 分享至 

  • xImage
  •  

延續之前實作的Day14 延續前實戰新增 FAB + Bottom App Bar 實作後,這次將實現Day15~Day18文章介紹MDC的 Bottom sheetsDialogsDividerMenus的實作

介紹的內容:

https://ithelp.ithome.com.tw/upload/images/20221003/201444693GFRc11aRm.png

  • 使用 IDE版本 : Android Studio Chipmunk | 2021.2.1 Canary 7
  • 使用 MDC Android Components
  • 實作介紹 (提供完整程式碼)
    • Add Bottom Sheets、Divider
      1. 新增BottomSheet的layout
      2. 新增實作Bottom sheets的class
      3. 自定義Theme屬性
    • Add Full-screen dialogs、Divider、Text field Menus
      1. BottomAppBar新增開啟Full-screen dialogs入口item
      2. 新增Full-screen dialogs的layout
      3. 新增實作Full-screen dialogs的class

Add Bottom Sheets、Divider

https://ithelp.ithome.com.tw/upload/images/20221003/20144469u2La4hQeNJ.png

  1. 新增BottomSheet的layout:model_bottom_sheet_content
    1. 新增 MaterialDivider
  2. 新增BottomSheetDialogFragment的class:ModalBottomSheet
    1. Bottom sheets 關閉點擊事件
  3. 自定義BottomSheet的Theme屬性
    1. 設定不提供滑動功能 <item name="behavior_draggable">false</item>
    2. 背景設白色 <item name="backgroundTint">@color/white</item>

1. 新增BottomSheet的layout:model_bottom_sheet_content

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/modelBottomSheet"
    style="?attr/bottomSheetDialogTheme"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

    <!-- Bottom sheet contents. -->
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:text="篩選"
        android:textSize="16dp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="20dp"
        style="@style/Widget.Material3.Button.IconButton"
        app:iconTint="@color/black"
        app:icon="@drawable/ic_close"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toTopOf="@id/divider1"/>

    <com.google.android.material.divider.MaterialDivider
        android:id="@+id/divider1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:dividerColor="@color/gray"
        app:dividerInsetEnd="20dp"
        app:dividerInsetStart="20dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textView1" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginHorizontal="20dp"
        android:layout_marginVertical="20dp"
        android:text="類型"
        android:textSize="16dp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textView1" />

    <!--CheckBox-->
    <TextView
        android:id="@+id/tv_check_box"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginVertical="20dp"
        android:text="CheckBox"
        android:textSize="16dp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="@id/textView2"
        app:layout_constraintTop_toBottomOf="@id/textView2"/>

    <CheckBox
        android:id="@+id/checkBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="20dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toTopOf="@id/divider2" />

    <com.google.android.material.divider.MaterialDivider
        android:id="@+id/divider2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:dividerColor="@color/gray"
        app:dividerInsetEnd="20dp"
        app:dividerInsetStart="20dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_check_box"/>

    <!--Switch-->
    <TextView
        android:id="@+id/tv_switch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginVertical="20dp"
        android:text="Switch"
        android:textSize="16dp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="@id/textView2"
        app:layout_constraintTop_toBottomOf="@id/divider2"/>

    <com.google.android.material.divider.MaterialDivider
        android:id="@+id/divider3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:dividerColor="@color/gray"
        app:dividerInsetEnd="20dp"
        app:dividerInsetStart="20dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_switch"/>

    <!--Radio button-->
    <TextView
        android:id="@+id/tv_radio_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginVertical="20dp"
        android:text="Radio button"
        android:textSize="16dp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="@id/textView2"
        app:layout_constraintTop_toBottomOf="@id/divider3"/>

    <com.google.android.material.divider.MaterialDivider
        android:id="@+id/divider4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:dividerColor="@color/gray"
        app:dividerInsetEnd="20dp"
        app:dividerInsetStart="20dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_radio_button"/>

    <!--Progress-->
    <TextView
        android:id="@+id/tv_progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginVertical="20dp"
        android:text="Radio button"
        android:textSize="16dp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="@id/textView2"
        app:layout_constraintTop_toBottomOf="@id/divider4"/>

    <com.google.android.material.divider.MaterialDivider
        android:id="@+id/divider5"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:dividerColor="@color/gray"
        app:dividerInsetEnd="20dp"
        app:dividerInsetStart="20dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_progress"/>

</androidx.constraintlayout.widget.ConstraintLayout>

2. 新增BottomSheetDialogFragment的class:ModalBottomSheet

  • 可以自定義 Bottom sheets 的關閉點擊
class ModalBottomSheet : BottomSheetDialogFragment() {

    companion object {
        const val TAG = "ModalBottomSheet"
    }

    private var _binding: ModelBottomSheetContentBinding? = null
    private val binding get() = _binding

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = ModelBottomSheetContentBinding.inflate(inflater, container, false)
        return binding?.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 自定義關閉
        binding?.btnClose?.setOnClickListener{
            dismiss()
        }
    }

}

3. 自定義BottomSheet的Theme屬性

  • layout需設定自定義的style="?attr/bottomSheetDialogTheme"
<style name="Theme.MDCSubject" parent="Theme.Material3.Light.NoActionBar">
        ....
        <!-- Customize your theme here. -->
        <item name="bottomSheetDialogTheme">@style/ModalBottomSheetDialog</item>
    </style>

		 <!-- Customize -->
    <style name="ModalBottomSheet" parent="Widget.Material3.BottomSheet.Modal">
        <item name="behavior_draggable">false</item>
        <item name="backgroundTint">@color/white</item>
    </style>

    <style name="ModalBottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
        <item name="bottomSheetStyle">@style/ModalBottomSheet</item>
    </style>

Add Full-screen dialogs、Divider、Text field Menus

  1. 開啟Full-screen dialogs入口 fragment_product_cards.xml
    1. 延續之前的範例BottomAppBar的menu新增用戶選項
  2. 新增Full-screen dialogs的layout full_screen_dialog_fragment.xml
    1. 這裡使用許多Text Field,所以可以看看之前介紹的Text Field一些用法幫助快速理解使用
  3. 新增實作Full-screen dialogs的class FullScreenDialogFragment.kt
  4. 呼叫顯示Full-screen dialogs的class 方法 ProductCardsFragment.kt

1. 開啟Full-screen dialogs入口 fragment_product_cards.xml

https://ithelp.ithome.com.tw/upload/images/20221003/20144469RU2z05dLb1.png

<item
        android:id="@+id/account"
        android:icon="@drawable/ic_account"
        android:title="用戶"
        app:showAsAction="ifRoom" />

2. 新增Full-screen dialogs的layout full_screen_dialog_fragment.xml

https://ithelp.ithome.com.tw/upload/images/20221003/20144469tGNOVXiyjp.png

  1. Header
    1. icon - 關閉
    2. Tittle - Full-screen dialog title
    3. TextButton - Save
      1. style="@style/Widget.Material3.Button.TextButton"
  2. 基本資料
    1. icon - 用戶的大頭貼

    2. Filled text field - 稱呼

      1. style="@style/Widget.Material3.TextInputLayout.FilledBox"
      2. 相關設定
          android:hint="稱呼"
          android:textColorHint="@color/dark_gray"
          app:boxBackgroundColor="@color/white"
          app:endIconMode="clear_text"
      
    3. Outlined text field - 信箱

      1. 相關設定
          android:hint="信箱"
          android:textColorHint="@color/dark_gray"
          app:endIconMode="clear_text"
      
    4. Outlined text field - 帳號

      1. 相關設定
          android:hint="帳號"
          android:textColorHint="@color/dark_gray"
          app:endIconMode="clear_text"
      
    5. Outlined text field - 密碼

      1. 相關設定
          android:hint="密碼"
          android:textColorHint="@color/dark_gray"
          app:endIconMode="password_toggle"
      
    6. Text field的選單ExposedDropdownMenu

      1. OutlinedBox.ExposedDropdownMenu - 國家
        1. style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
      2. OutlinedBox.ExposedDropdownMenu - 城市
        1. style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"

full_screen_dialog_fragment.xml

<androidx.constraintlayout.widget.ConstraintLayout 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:background="@color/white">

    <!--Header -->
    <!--Icon-->
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginTop="16dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_close" />

    <!--Headline -->
    <TextView
        android:id="@id/tvTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:text="Full-screen dialog title"
        android:textSize="20dp"
        app:layout_constraintStart_toEndOf="@+id/imageView"
        app:layout_constraintTop_toTopOf="@id/imageView" />

    <!--Text button -->
    <Button
        android:id="@+id/button"
        style="@style/Widget.Material3.Button.TextButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Save"
        app:layout_constraintBottom_toBottomOf="@id/tvTitle"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/tvTitle" />

    <!--Divider (optional)-->
    <com.google.android.material.divider.MaterialDivider
        android:id="@+id/divider1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:dividerColor="@color/gray"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/button" />

    <!-- icon - 用戶的大頭貼 -->
    <ImageView
        android:id="@+id/imageUser"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_margin="20dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/divider1"
        app:srcCompat="@drawable/ic_account" />

    <!--Filled text field - 稱呼-->
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputName"
        style="@style/Widget.Material3.TextInputLayout.FilledBox"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="20dp"
        android:layout_marginTop="10dp"
        android:hint="稱呼"
        android:textColorHint="@color/dark_gray"
        app:boxBackgroundColor="@color/white"
        app:endIconMode="clear_text"
        app:layout_constraintBottom_toBottomOf="@id/imageUser"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/imageUser"
        app:layout_constraintTop_toTopOf="@id/imageUser">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </com.google.android.material.textfield.TextInputLayout>

    <!--Outlined text field - 信箱-->
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputEmail"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="20dp"
        android:layout_marginTop="10dp"
        android:hint="信箱"
        android:textColorHint="@color/dark_gray"
        app:endIconMode="clear_text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/imageUser">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </com.google.android.material.textfield.TextInputLayout>

    <!--Outlined text field - 帳號-->
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputAccountNumber"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="20dp"
        android:layout_marginTop="10dp"
        android:hint="帳號"
        android:textColorHint="@color/dark_gray"
        app:endIconMode="clear_text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textInputEmail"
        app:startIconDrawable="@drawable/ic_person">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </com.google.android.material.textfield.TextInputLayout>

    <!--Outlined text field - 密碼-->
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputPassword"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="20dp"
        android:layout_marginTop="10dp"
        android:hint="密碼"
        android:textColorHint="@color/dark_gray"
        app:endIconMode="password_toggle"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textInputAccountNumber"
        app:startIconDrawable="@drawable/ic_lock">

        <com.google.android.material.textfield.TextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.divider.MaterialDivider
        android:id="@+id/divider2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        app:dividerColor="@color/gray"
        app:dividerInsetEnd="20dp"
        app:dividerInsetStart="20dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textInputPassword" />

    <TextView
        android:id="@+id/tvTheme"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        android:text="居住"
        android:textSize="18dp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/divider2" />

    <!--OutlinedBox.ExposedDropdownMenu - 國家-->
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputCountry"
        style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="20dp"
        android:layout_marginTop="10dp"
        android:hint="國家"
        app:layout_constraintEnd_toStartOf="@+id/textInputCity"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tvTheme">

        <AutoCompleteTextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="none" />
    </com.google.android.material.textfield.TextInputLayout>

    <!--OutlinedBox.ExposedDropdownMenu - 城市-->
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputCity"
        style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="20dp"
        android:layout_marginTop="10dp"
        android:hint="城市"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/textInputCountry"
        app:layout_constraintTop_toBottomOf="@id/tvTheme">

        <AutoCompleteTextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="none" />
    </com.google.android.material.textfield.TextInputLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

3. 新增實作Full-screen dialogs的class FullScreenDialogFragment.kt

class FullScreenDialogFragment : DialogFragment() {

    private var _binding: FullScreenDialogFragmentBinding? = null
    private val binding get() = _binding

    override fun onCreate(savedInstanceState: Bundle?) {
        val dialog = super.onCreateDialog(savedInstanceState)
        val window = dialog.window
window?.requestFeature(Window.FEATURE_NO_TITLE)
        window?.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT)
        setStyle(STYLE_NORMAL, android.R.style.Theme_Light_NoTitleBar_Fullscreen)
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FullScreenDialogFragmentBinding.inflate(layoutInflater, container, false)
        return binding?.root
}

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        initView()
    }

		private fun initView() {
					// 關閉dialog
	        binding?.btnClose?.setOnClickListener {
	            dialog?.dismiss()
	        }
	
					// 實作儲存
	        binding?.btnSave?.setOnClickListener {
	            Toast.makeText(context, "實作儲存", Toast.LENGTH_LONG).show()
	        }
	
	
					// 範例國家Menu
	        val exampleCountryItem = arrayOf("Item 1", "Item 2", "Item 3", "Item 4")
	        (binding?.textInputCountry?.editText as? MaterialAutoCompleteTextView)?.setSimpleItems(exampleCountryItem)
	
					// 範例城市Menu
	        val exampleCityItems = arrayOf("Item 1", "Item 2", "Item 3", "Item 4")
	        (binding?.textInputCity?.editText as? MaterialAutoCompleteTextView)?.setSimpleItems(exampleCityItems)
	
    }
}

4. 呼叫顯示Full-screen dialogs的class 方法

BottomAppBar的OnMenuItemClick點擊剛剛新增的item

private inner class OnMenuItemClick : androidx.appcompat.widget.Toolbar.OnMenuItemClickListener {
        override fun onMenuItemClick(item: MenuItem?): Boolean {
            return when (item?.itemId) {
                R.id.mainPage -> {
                    ......
                    true
                }
                R.id.collect -> {
					......
					true
                }
                R.id.account -> {
                    // 使用者資料點擊開啟Full-screen dialogs
                    if(activity != null) {
                        requireActivity().supportFragmentManager.let {
                            FullScreenDialogFragment().show(it, "")
                        }
                    }
                    true
                }
                else -> false
            }
        }
    }

以上是這次的實作內容 歡迎下載程式碼參考
感謝您看到這邊 /images/emoticon/emoticon29.gif

參考資料:
Day15 使用M3的Bottom sheets
Day16 使用M3的Dialogs
Day17 使用M3的Divider
Day18 使用M3的Menus


上一篇
Day18 使用M3的Menus
下一篇
Day20 使用M3的Navigation drawer
系列文
Kotlin 實踐 Material Design 懶人包30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言