實作上,透過 MaterialAlertDialogBuilder 物件來創建 Dialog,而它的設計邏輯是來自於所謂的 Builder Pattern,而又是這麼剛好(無情業配),我以前也有寫過一篇文章講到,有興趣的大家,歡迎閱讀~
所以在 Builder 模式下,會有許多方法讓我們可以用來客製化想要的屬性,使用上與 Snackbar 非常相似。設置完成後,再透過show()
方法顯示
MaterialAlertDialogBuilder(context)
.setTitle(resources.getString(R.string.title))
.setMessage(resources.getString(R.string.supporting_text))
.setNeutralButton(resources.getString(R.string.cancel)) { dialog, which ->
// Respond to neutral button press
}
.setNegativeButton(resources.getString(R.string.decline)) { dialog, which ->
// Respond to negative button press
}
.setPositiveButton(resources.getString(R.string.accept)) { dialog, which ->
// Respond to positive button press
}
.show()
在 Simple Dialog 實作中,由於設計上,只能有 list item,就透過 setItems()
方法設置,裡面要傳入一個作為選項的 array
val items = arrayOf<String>("Item 1", "Item 2", "Item 3")
MaterialAlertDialogBuilder(requireContext())
.setTitle("Simple Dialog")
.setItems(items) { dialog, which ->
// do something when click any items
}.show()
實作上,有點像是 Simple Dialog 的進階版,但選項的部分是透過 setSingleChoiceItems
將 Dialog 的 item 變成 radioButton 的樣式與達到單選的功能,還要再加入 Netural、Positive Action
val singleItems = arrayOf("Item 1", "Item 2", "Item 3")
val checkedItem = 1
MaterialAlertDialogBuilder(context)
.setTitle(resources.getString(R.string.title))
.setNeutralButton(resources.getString(R.string.cancel)) { dialog, which ->
// Respond to neutral button press
}
.setPositiveButton(resources.getString(R.string.ok)) { dialog, which ->
// Respond to positive button press
}
// Single-choice items (initialized with checked item)
.setSingleChoiceItems(singleItems, checkedItem) { dialog, which ->
// Respond to item chosen
}
.show()
如果在 title 上的文字無法更清楚的表達,可以透過 setIcon
在 title 旁邊添加圖示
而 Multiple Type 實作上與 Single Type 相似,差別在於 item 的設置上,是透過 setMultiChoiceItems
,當中的監聽 lambda 還有 checked 參數讓我們能判斷用戶點選哪一個
val singleItems = arrayOf("Item 1", "Item 2", "Item 3")
val checkedItems = booleanArrayOf(true, false, false, false)
MaterialAlertDialogBuilder(requireContext())
...
setMultiChoiceItems(singleItems, checkedItems) { dialog, which, checked ->
if (checked) Log.d("Multi-choice", "checked item :$which")
}
...
.show()
與其他前面的 Dialog 都不同,不是透過 MaterialAlertDialog 來生成,而是要直接實作一個 Fragment 來 show 出畫面,由於官方文檔沒有直接的範例,反而是要我們自己去看 DialogFragment 的文檔,如果有興趣的話可以先看看,如果懶得看的話,這邊就用我做過的一個專案中其中一個新增 Event 的功能來當範例
第一步就是從 DialogFragment 開始,作為生成 Dialog 的重要物件,我自己先魔改成類似 BaseFragment 的架構來設計。這樣以後有多個 DialogFragment 的時候,就不用重寫這麼多次 inflate 了。
還有一個地方要注意,就是要透過設置 Style 的屬性去啟動全屏模式,否則 Dialog 不會填滿整個畫面,所以在 onCreate 去設置了 STYLE_NORMAL
與我自定義的 theme,這樣就完成了
<style name="FullDialogStyle" parent="Theme.IThomeIRonContest">
<item name="android:windowNoTitle">true</item>
<item name="android:windowAnimationStyle">@style/Animation.Design.BottomSheetDialog</item>
</style>
abstract class BaseDialogFragment<VB : ViewBinding>(private val inflate: (LayoutInflater, ViewGroup?, Boolean) -> VB) :
DialogFragment() {
private var _binding: VB? = null
val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.FullDialogStyle)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = inflate(inflater, container, false)
return binding.root
}
override fun onDestroy() {
_binding = null
super.onDestroy()
}
}
畫面的部分,重點在於 Top bar,這邊只貼一部分的 code,剩下的可以到 Github 看完整的程式碼
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/Animation.Design.BottomSheetDialog"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/fullscreen_dialog_tb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/darkBlue"
android:paddingVertical="5dp"
app:navigationIcon="@drawable/ic_baseline_close_24"
app:title="New Event">
<com.google.android.material.button.MaterialButton
android:id="@+id/fullscreen_save_btn"
android:layout_width="wrap_content"
android:layout_gravity="end"
android:text="SAVE"
android:textColor="@color/white"
android:layout_marginEnd="10dp"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_height="wrap_content"/>
</com.google.android.material.appbar.MaterialToolbar>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<!-- Content -->
...
...
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
DialogFragment 實作的部分我就不貼了,讓各位自由發揮,這邊就單純貼如何呼叫它。先初始化 DialogFragment,再透過當中的方法show()
,裡面有兩個參數要帶入,分別為 manage 與 tag。 manager 在不同層級要帶不同的參數,如果是在 Activity 要使用 supportFragmentManager
,在 Fragment的話是 childFragmentManager
private val fullScreenDialogFragment by lazy { FullScreenDialogFragment() }
fullScreenDialogFragment.show(childFragmentManager, "FullScreen Dialog")
除了 DialogFragment 能讓我們自由設置畫面與元件,其他的 Dialog 都是系統透過主題的預設大小與色彩。可以調整的屬性非常多,這邊我也只能帶大家走馬看花。色彩上,由於當中的組件很多,有 TextButton、TextView、Container 等,所吃的屬性也不同
透過各種主題色,將顏色設計改了一下,然後透過 alertDialogStyle 去改變 Dialog 的形狀。基本上這樣就能改變當中許多元件的設計
<style name="ThemeOverlay.App.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
<item name="colorPrimary">@android:color/holo_orange_light</item>
<item name="colorSecondary">@color/red</item>
<item name="colorSurface">@color/darkBlue</item>
<item name="colorOnSurface">@color/white</item>
<item name="alertDialogStyle">@style/MaterialAlertDialog.App</item>
</style>
<style name="MaterialAlertDialog.App" parent="MaterialAlertDialog.MaterialComponents">
<item name="shapeAppearance">@style/ShapeAppearance.App.MediumComponent</item>
</style>
<style name="ShapeAppearance.App.MediumComponent" parent="ShapeAppearance.MaterialComponents.MediumComponent">
<item name="cornerFamily">cut</item>
<item name="cornerSize">8dp</item>
</style>
如果上面那樣不滿足,想再做到更深入的客製化,就要從裡面的元件著手,包括 title、body 跟代表 action 的 TextButton。也就是說我們要直接去寫這些元件的風格並且統一套用在 Dialog 上面
由於 Container 的顏色一定要透過 colorSurface,所以其他上述所設定的主題色都刪除只留下它。這邊新增了 title、body 的 TextStyle,還有透過 buttonBarButtonStyle 設置每個 action 的 TextButton Style。由於這些元件在基礎上的大小、間距與形狀都被配置好了,如果要特別去改動,要注意可能會出現非預期的狀況,建議只改顏色就好
<style name="ThemeOverlay.App.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
<!-- Dialog background color -->
<item name="colorSurface">@color/darkBlue</item>
<item name="alertDialogStyle">@style/MaterialAlertDialog.App</item>
<item name="buttonBarPositiveButtonStyle">@style/Widget.App.Button.TextButton.Dialog</item>
<item name="buttonBarNegativeButtonStyle">@style/Widget.App.Button.TextButton.Dialog</item>
<item name="buttonBarNeutralButtonStyle">@style/Widget.App.Button.TextButton.Dialog</item>
<item name="materialAlertDialogTitleTextStyle">@style/MaterialAlertDialog.App.Title.Text</item>
<item name="materialAlertDialogBodyTextStyle">@style/MaterialAlertDialog.App.Body.Text</item>
</style>
<style name="MaterialAlertDialog.App" parent="MaterialAlertDialog.MaterialComponents">
<item name="shapeAppearance">@style/ShapeAppearance.App.MediumComponent</item>
</style>
<style name="MaterialAlertDialog.App.Title.Text" parent="MaterialAlertDialog.MaterialComponents.Title.Text">
<item name="android:textColor">@android:color/holo_orange_light</item>
</style>
<style name="MaterialAlertDialog.App.Body.Text" parent="MaterialAlertDialog.MaterialComponents.Body.Text">
<item name="android:textColor">@color/white</item>
</style>
<style name="Widget.App.Button.TextButton.Dialog" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
<item name="android:textColor">@android:color/holo_red_light</item>
<item name="shapeAppearance">@style/ShapeAppearance.App.SmallComponent</item>
</style>
最後再介紹幾個屬性,分別為 buttonBarStyle (能改動 Dialog 下方的 bar)、materialAlertDialogTitlePanelStyle (能改動 Dialog 上方的 bar)。可透過這兩個屬性去改動 Dialog 上下方的 background,讓整個 Dialog 不在只是單一色
<!-- Bottom bar style -->
<item name="buttonBarStyle">@style/Widget.App.Dialog.BottomBar</item>
<!-- Top bar style -->
<item name="materialAlertDialogTitlePanelStyle">@style/TitlePaneStyleCenter</item>
<style name="Widget.App.Dialog.ButtonBar" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
<item name="android:background">@color/white</item>
</style>
<style name="Widget.App.Dialog.Title.Panel" parent="MaterialAlertDialog.MaterialComponents.Title.Panel.CenterStacked">
<item name="android:background">@color/black</item>
</style>
實作完自定義的 style 後,記得別忘了在 theme 裡面去套用
<item name="materialAlertDialogTheme">@style/ThemeOverlay.App.MaterialAlertDialog</item>
Dialogs 實作上除了 Full-screen DialogFragment 之外,都是透過 MaterialDialog 的 Builder 模式來建立,讓我們能一步一步去建立想要的 Dialog,但也僅限物件中所提供的功能,如果想要更加客製化,除了上述提到的修改 style 之後,還能透過setView
的方法注入自定義的 layout,但由於這部分主要不是 Material Design 的範圍,所以就沒特別提到。若是想知道系統原生的 Dialog layout 配置如何,可以在 android studio 中搜尋 mtrl_alert_dialog.xml
若對實作還是有點不懂的,這邊提供我的 Github方便大家參考