Android 對話框Dialogs

簡要

提示用戶做出決定或輸入更多訊息,不會佔據整個屏幕

避免直接對Dialog做實體化

應使用下列子類別之一:

  1. AlertDialog
  2. DatePickerDialog
  3. TimePickerDialog

注意:Android ProgressDialog已被棄用,因為會阻止用戶與應用進行互動,應遵循進展與活動設計準則,在布局中使用ProgressBar,而非ProgressDialog

可定義對話框的樣式和結構,應使用 DialogFragment 作為對話框

DialogFragment 類提供創建對話框和管理其外觀所需的所有控件,而調用 Dialog 對像上的方法

使用 DialogFragment 來管理對話框可確保對話框能正確處理各種生命週期事件,如用戶按“返回”按鈕或旋轉屏幕時

下列依序介紹

  1. 創建DialogFragment
  2. 建構提醒對話框
  3. 創建自定義佈局
  4. 將Activity作為Dialog使用
  5. DialogFragment事件傳回Activity
  6. 嵌入式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. 建構提醒對話框

提醒對話框有三個區塊

  1. 標題
  2. 內容區域
  3. 操作按紐

操作按鈕不應超過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 官網


訂閱Codeilin的旅程,若有最新消息會通知。

廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

透過 WordPress.com 建置的網站.

向上 ↑

%d 位部落客按了讚: