簡要
提示用戶做出決定或輸入更多訊息,不會佔據整個屏幕
避免直接對Dialog做實體化
應使用下列子類別之一:
- AlertDialog
- DatePickerDialog
- TimePickerDialog
注意:Android ProgressDialog已被棄用,因為會阻止用戶與應用進行互動,應遵循進展與活動設計準則,在布局中使用ProgressBar,而非ProgressDialog
可定義對話框的樣式和結構,應使用 DialogFragment 作為對話框
DialogFragment 類提供創建對話框和管理其外觀所需的所有控件,而非調用 Dialog 對像上的方法
使用 DialogFragment 來管理對話框可確保對話框能正確處理各種生命週期事件,如用戶按“返回”按鈕或旋轉屏幕時
下列依序介紹
- 創建DialogFragment
- 建構提醒對話框
- 創建自定義佈局
- 將Activity作為Dialog使用
- DialogFragment事件傳回Activity
- 嵌入式Fragment
1. 創建DialogFragment
根據對話複雜度,進行各種回調方法,包括所有基本的Fragment生命週期
新增以下的程式碼,重新定義對話樣式及架構
class CustomDialogFragment: DialogFragment() { override fun onCreateDialog( savedInstanceState: Bundle? ): Dialog { return activity?.let { val builder = AlertDialog.Builder(it) builder //預設title樣式 //不能和setCustomTitle共存 //.setTitle("title A") //預設內容樣式 //可以和setView共存 //.setMessage("Test Message") .setCustomTitle(TextView(activity).also { it.text = "title" }) .setView(TextView(activity).also { it.text = "content" }) .setPositiveButton("OK") { dialog, id -> } .setNegativeButton("CANCEL") { dialog, id -> } builder.create() } ?: throw IllegalStateException( "Activity cannot be null" ) } }
呼叫方式如下
val customDialogFragment = CustomDialogFragment() customDialogFragment.show(supportFragmentManager,"test")
2. 建構提醒對話框
提醒對話框有三個區塊
- 標題
- 內容區域
- 操作按紐
操作按鈕不應超過3個,根據Material Design解釋如下,參考Material Design:
When there are three or more actions with long text labels in a Material dialog, products may use iOS action sheets or bottom sheets.
以下程式碼,基本框架的範例
private fun initDialog() { val builder: AlertDialog.Builder? = let { AlertDialog.Builder(it) } builder?.apply { setMessage("message") setTitle("title") setPositiveButton("OK" ) { dialog, id -> // User clicked OK button } setNegativeButton("CANCEL" ) { dialog, id -> // User cancelled the dialog } } val dialog: AlertDialog? = builder?.create() dialog?.show() }

添加列表
AlertDialog API提供三種列表
- 傳統的單選列表
- 永久性單選列表(單選按鈕)
- 永久性多選列表(複選框)
在app/src/main/res/values新增array
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="colors_array">
<item>RED</item>
<item>GREEN</item>
<item>BLUE</item>
</array>
</resources>
在DialogFragment新增以下程式碼,就實現了傳統的單選列表
也可以搭配ListAdapter,之後會有個文章介紹,這裡暫時不多介紹
override fun onCreateDialog( savedInstanceState: Bundle? ): Dialog { return activity?.let { val builder = AlertDialog.Builder(it) builder.setTitle("Colors") .setItems(R.array.colors_array ) { dialog, which -> //打印點選index println("which $which") } builder.create() } ?: throw IllegalStateException( "Activity cannot be null" ) }
永久性多選列表(複選框),程式碼如下
//預先已知Item Size //可以保存所有Item狀態 var checkItems = BooleanArray(3) override fun onCreateDialog( savedInstanceState: Bundle? ): Dialog { return activity?.let { //只保存true的狀態,其他皆不保存 val selectedItems = ArrayList() val builder = AlertDialog.Builder(it) builder.setTitle("title") //checkItems如果不需要 //可以設成null .setMultiChoiceItems( R.array.colors_array, checkItems ) { dialog, which, isChecked -> if (isChecked) { selectedItems.add(which) } else if (selectedItems.contains(which)) { selectedItems.remove(which) } } .setPositiveButton("OK" ) { dialog, id -> } .setNegativeButton("CANCEL" ) { dialog, id -> } builder.create() } ?: throw IllegalStateException( "Activity cannot be null" ) }
永久性單選列表(單選按鈕),將上方程式碼,參照下方重點更換
就可以實現功能囉!
.setMultiChoiceItems( R.array.colors_array, checkItems ) { dialog, which, isChecked -> //由上方程式更換成下方程式 .setSingleChoiceItems( R.array.colors_array, //預設選取index 1 ) { dialog, which ->
3. 創建自定義佈局
先建立一個xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="64dp"
android:scaleType="center"
android:background="#FFFFBB33"
android:contentDescription="@string/app_name" />
<EditText
android:id="@+id/username"
android:inputType="textEmailAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="4dp"
android:hint="Username" />
<EditText
android:id="@+id/password"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="16dp"
android:fontFamily="sans-serif"
android:hint="Password"/>
</LinearLayout>
默認情況下,當 EditText 使用 “textPassword" 輸入類型時
字體系列會設置為等寬
因此更改為 “sans-serif",以便兩個文本字段均使用匹配的字體樣式
override fun onCreateDialog( savedInstanceState: Bundle? ): Dialog { return activity?.let { val builder = AlertDialog.Builder(it) val inflater = requireActivity().layoutInflater; builder.setView(inflater.inflate( R.layout.dialog_custom, null )) .setPositiveButton("Sign in" ) { dialog, id -> } .setNegativeButton("Cancel" ) { dialog, id -> getDialog()?.cancel() } builder.create() } ?: throw IllegalStateException( "Activity cannot be null" ) }

4. 將Activity作為Dialog使用
首先先至style裡面新增以下,設定最小視窗大小
備註:如果沒設定運行大小會是最小化,可以自行測試看看
<style name="MainNoActionBarDialog" parent="android:Theme.Holo.Dialog">
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
</style>
再來打開AndroidManifest將Activity新增以下程式碼
<activity android:theme="@style/MainNoActionBarDialog">
接著把Activity繼承FragmentActivity()
在onCreate內在setContentView前,先增以下
//去除Title requestWindowFeature(Window.FEATURE_NO_TITLE)
在幫Activity setContentView Layout

5. DialogFragment事件傳回Activity
建立一個DialogFragment,如下
class CustomDialogFragment2: DialogFragment() { internal lateinit var listener: NoticeDialogListener override fun onCreateDialog( savedInstanceState: Bundle? ): Dialog { return activity?.let { val builder = AlertDialog.Builder(it) builder.setMessage("title") .setPositiveButton("ok" ) { dialog, id -> listener.onDialogPositiveClick(this) } .setNegativeButton("cancel" ) { dialog, id -> listener.onDialogNegativeClick(this) } builder.create() } ?: throw IllegalStateException( "Activity cannot be null" ) } interface NoticeDialogListener { fun onDialogPositiveClick(dialog: DialogFragment) fun onDialogNegativeClick(dialog: DialogFragment) } override fun onAttach(context: Context) { super.onAttach(context) try { listener = context as NoticeDialogListener } catch (e: ClassCastException) { throw ClassCastException((context.toString() + " must implement NoticeDialogListener")) } } }
Activity中,新增下方程式碼
fun showNoticeDialog() { val dialog = CustomDialogFragment2() //NoticeDialogFragment是唯一識別碼 //可以透過findFragmentByTag()找到他 dialog.show( supportFragmentManager, "NoticeDialogFragment" ) } override fun onDialogPositiveClick( dialog: DialogFragment ) { println("onDialogPositiveClick") } override fun onDialogNegativeClick( dialog: DialogFragment ) { println("onDialogNegativeClick") }
按下OK打印 onDialogPositiveClick
按下CANCEL打印 onDialogNegativeClick
6. 嵌入式Fragment
DialogFragment中新增以下程式碼
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = DialogCustomBinding .inflate( layoutInflater, container, false ) return binding.root } override fun onCreateDialog( savedInstanceState: Bundle? ): Dialog { //要用super val dialog = super.onCreateDialog(savedInstanceState) dialog .requestWindowFeature(Window.FEATURE_NO_TITLE) return dialog }
Activity中新增
//監聽按鈕,按下關閉嵌入式Fragment binding.addBtn.setOnClickListener { val fragment = supportFragmentManager .findFragmentByTag("dialog") as? DialogFragment fragment?.dismiss() } //如果是大Layout則show //如果不是則嵌入式Fragment fun showDialog() { val fragmentManager = supportFragmentManager val newFragment = CustomDialogFragment() if (isLargeLayout) { newFragment.show(fragmentManager, "dialog") } else { val transaction = fragmentManager.beginTransaction() transaction.setTransition( FragmentTransaction.TRANSIT_FRAGMENT_OPEN ) transaction .add( android.R.id.content, newFragment, "dialog" ) .addToBackStack(null) .commit() } }
以上內容參考Android 官網
發表迴響