Androidで色々なDialogを出してみる

# Androidで色々なDialogって何があるの？
AndroidでDialogをだすとなると、AlertDialogが一般的ですが他にも色々あります。
参考URL
本家Android Developpers：[ダイアログ](https://developer.android.com/guide/topics/ui/dialogs?hl=ja)
# AlertDialog
一般的なこんなやつです。アクションボタンに3つの選択肢があります。
| アクションボタン | 意味 |
|:-:|:-:|
| setPositiveButton  | アクションを受け入れて続行する場合に使用します（「OK」アクション）。  |
| setNegativeButton  | アクションをキャンセルする場合に使用します。  |
| setNeutralButton  | どちらでもない。後で、とか。  |

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/f16a672b-9a8a-b8b1-9a11-55610fb5cafb.png">

```kotlin
AlertDialog.Builder(this)
    .setTitle("タイトル")
    .setMessage("メッセージ")
    .setPositiveButton("OK") { dialog,  id ->
        Toast.makeText(applicationContext, "OK押しましたね", Toast.LENGTH_LONG).show()
    }
    .setNegativeButton("Cancel") {dialog, id ->
        Toast.makeText(applicationContext, "Cancel押しましたね", Toast.LENGTH_LONG).show()
    }
    .setNeutralButton("Late") {dialog, id ->
        Toast.makeText(applicationContext, "Late押しましたね", Toast.LENGTH_LONG).show()
    }
    .show()
```
# 基本的なDialog
DialogFragmentを継承します。アクションボタンは、AlertDialog同様3つの選択肢があります。
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/68b247db-b612-8506-fca7-bed69339dec4.png">
どのアクションボタンを押したのかをToastに表示するようにしてみました。
```kotlin
class BasicDialogFragment : DialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let {
            val builder = AlertDialog.Builder(it)
            builder.setMessage("Start Game?")
                // アクションを受け入
                .setPositiveButton("start") { dialog, id ->
                    which(dialog, id)
                }
                // アクションをキャンセル
                .setNegativeButton("cancel") { dialog, id ->
                    which(dialog, id)
                }
                // どっちでもない
                .setNeutralButton("N/A") { dialog, id ->
                    which(dialog, id)
                }
            builder.create()
        } ?: throw java.lang.IllegalArgumentException("Activity cannot be null")
    }

    fun which(daialog: DialogInterface, id: Int) {
        when (id) {
            DialogInterface.BUTTON_POSITIVE ->
                Toast.makeText(context, "start押しましたね", Toast.LENGTH_LONG).show()
            DialogInterface.BUTTON_NEGATIVE ->
                Toast.makeText(context, "cancel押しましたね", Toast.LENGTH_LONG).show()
            DialogInterface.BUTTON_NEUTRAL ->
                Toast.makeText(context, "N/A押しましたね", Toast.LENGTH_LONG).show()
            else -> {}
        }
    }
}
```
# リスト形式のDialog
これもDialogFragmentを継承します。リストの中のどれか1個を選択したら完了です。
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/e2703c70-9e8f-38a4-ca88-482369e71995.png">
これもどれを選んだのかをわかるようにしてみました。
```kotlin
class ListDialogFragment : DialogFragment() {
    var selectId: Int = -1
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

        return activity?.let {
            val builder = AlertDialog.Builder(it)
            builder.setTitle("色を選んでください")
                .setItems(arrayOf("赤", "青", "黄")) { dialog, id ->
                    selectId = id
                    which(dialog, id)
                }
                builder.create()
        } ?: throw java.lang.IllegalArgumentException("Activity cannot be null")
    }

    fun which(daialog: DialogInterface, id: Int) {
        when (id) {
            0 -> Toast.makeText(context, "赤を押しましたね", Toast.LENGTH_LONG).show()
            1 -> Toast.makeText(context, "青を押しましたね", Toast.LENGTH_LONG).show()
            2 -> Toast.makeText(context, "黄を押しましたね", Toast.LENGTH_LONG).show()
            else -> {}
        }
    }
}
```
# チェックボックス形式のDialog
これもDialogFragmentを継承します。チェックボックスなので複数選択可能です。アクションボタンはsetPositiveButtonと、setNegativeButtonだけです。(setNeutralButtonはない)
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/f89e3d69-1665-ed87-3278-440a06ea0c59.png">
これも同様にどれが選択されたのかわかるようにしてみました。
```kotlin
class CheckBoxDialogFragment : DialogFragment() {
    val selectedItems = ArrayList<Int>()
    val toppingList = arrayOf("オニオン", "レタス", "トマト")
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let {
            val builder = AlertDialog.Builder(it)
            builder.setTitle("トッピング選んでください")
                .setMultiChoiceItems(toppingList, null) { dialog, which, isChecked ->
                    if (isChecked) {
                        selectedItems.add(which)
                    } else if (selectedItems.contains(which)) {
                        selectedItems.remove(which)
                    }
                }
                .setPositiveButton("OK") { dialog, id ->
                    which(dialog, id)
                }
                .setNegativeButton("Cancel") { dialog, id ->
                    which(dialog, id)
                }
            builder.create()
        } ?: throw java.lang.IllegalArgumentException("Activity cannot be null")
    }

    fun which(daialog: DialogInterface, id: Int) {
        when (id) {
            DialogInterface.BUTTON_POSITIVE -> {
                val sb = StringBuilder()
                selectedItems.forEach { it ->
                    sb.append(toppingList[it] + ",")
                }
                Toast.makeText(context, "OKを押しましたね。${sb.toString()}", Toast.LENGTH_LONG).show()
            }
            DialogInterface.BUTTON_NEGATIVE ->
                Toast.makeText(context, "Cancelを押しましたね", Toast.LENGTH_LONG).show()
            else -> {}
        }
    }
}
```
# カスタム形式のDialog
最後にカスタム形式のDialogです。これは、上の他のDialogとは違ってlayout xmlを持っています。layoutで好きなようにカスタマイズ可能です。公式のサンプルに倣ってログインの時につかうようなダイアログを作ってみました。
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/0a3aff23-e236-e1d8-6daa-c526fdd05c0c.png">
これもDialogFragmentを継承します。
```kotlin
class CustomLayoutDialogFragment : DialogFragment() {
    private lateinit var binding: DialogSigninBinding
    lateinit var userName: EditText
    lateinit var password: EditText

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

        return activity?.let {
            val builder = AlertDialog.Builder(it)
            binding = DialogSigninBinding.inflate(requireActivity().layoutInflater)
            userName = binding.username
            password = binding.password
            builder.setView(binding.root)
                .setPositiveButton("Login") { dialog, id ->
                    which(dialog, id)
                }
                .setNegativeButton("Cancel") { dialog, id ->
                    getDialog()?.cancel()
                    which(dialog, id)
                }
                builder.create()
        } ?: throw java.lang.IllegalArgumentException("Activity cannot be null")
    }

    fun which(daialog: DialogInterface, id: Int) {
        when (id) {
            DialogInterface.BUTTON_POSITIVE ->
                Toast.makeText(context, "Loginを押しましたね。${userName.text.toString()} ${password.text.toString()}", Toast.LENGTH_LONG).show()
            DialogInterface.BUTTON_NEGATIVE ->
                Toast.makeText(context, "Cancelを押しましたね", Toast.LENGTH_LONG).show()
            else -> {}
        }
    }
}
```
layout xmlは普通にレイアウトエディタで編集可能です。
```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="64dp"
        android:background="#FFFFBB33"
        android:contentDescription="login"
        android:scaleType="center"
        android:src="@android:drawable/ic_lock_idle_lock"/>

    <EditText
        android:id="@+id/username"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_marginLeft="4dp"
        android:layout_marginTop="16dp"
        android:layout_marginRight="4dp"
        android:layout_marginBottom="4dp"
        android:hint="ユーザネーム"
        android:inputType="textEmailAddress" />

    <EditText
        android:id="@+id/password"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_marginLeft="4dp"
        android:layout_marginTop="4dp"
        android:layout_marginRight="4dp"
        android:layout_marginBottom="16dp"
        android:fontFamily="sans-serif"
        android:hint="パスワード"
        android:inputType="textPassword" />
</LinearLayout>
```
# MainActivityからDialogFragmentを継承しているDialogを呼ぶ
MainActivityからDialogFragmentを継承しているDialogを呼ぶ場合はshowメソッドを引数を付けて呼びます
```kotlin
BasicDialogFragment().show(supportFragmentManager, "Basic Dialog")
（中略）
ListDialogFragment().show(supportFragmentManager, "List Dialog")
（中略）
CheckBoxDialogFragment().show(supportFragmentManager, "CheckBox Dialog")
（中略）
CustomLayoutDialogFragment().show(supportFragmentManager,"Custom Layout")
```
第1引数のsupportFragmentManagerは継承しているFragmentActivity#getSupportFragmentManager()を呼んでいます。第2引数のtagは何に使われるのか、よくわかりません。javaDocによると「FragmentTransaction.addによる、このフラグメントのタグ。」と書かれているのでbackStackに使われるのかもしれません。
# MainActivityに通知してみる
これだけじゃ物足りないので、DialogFragmentを継承しているものはMainActivityから呼ばれる形式なので、何が選ばれたのかMainActivityに返すようにしてみました。ます、各DialogFragmentを継承しているクラスの中でリスナを定義します。
```kotlin
internal lateinit var listener: NoticeDialogListener
```
onAttachメソッドをoverrideします
```kotlin
    override fun onAttach(context: Context) {
        super.onAttach(context)
        try {
            listener = context as NoticeDialogListener
        } catch (e: java.lang.ClassCastException) {
            throw java.lang.ClassCastException( (context.toString() + "must implimnet NoticeDialogListener"))
        }
    }
```
各、アクションボタンのメソッドの中でリスナのメソッドを呼びます。ListDialogFragmentの場合、
```kotlin
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

        return activity?.let {
            val builder = AlertDialog.Builder(it)
            builder.setTitle("色を選んでください")
                .setItems(arrayOf("赤", "青", "黄")) { dialog, id ->
                    selectId = id
                    listener.onDialogPositiveClick(this)
                    which(dialog, id)
                }
                builder.create()
        } ?: throw java.lang.IllegalArgumentException("Activity cannot be null")
    }
```
MainActivityの方は、FragmentActivityを継承し、NoticeDialogListenerを実装します。
```kotlin
class MainActivity : FragmentActivity(), NoticeDialogListener {
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater).apply {
            setContentView(this.root)
        }
        （・・・中略・・・）
    }
}
```
onDialogPositiveClickメソッド、onDialogNegativeClickメソッド、onDialogNeutralClickメソッドをoverrideします。
onDialogPositiveClickはどのDialogFragmentからリスナが呼ばれたか、when文で分岐するようにしました。
```kotlin
    override fun onDialogPositiveClick(dialog: DialogFragment) {
        Log.d("MainActivity", "onDialogPositiveClick")
        when (dialog) {
            // リスト形式の場合
            is ListDialogFragment -> {
                when (dialog.selectId) {
                    0 -> binding.textView.text = "赤を押しましたね"
                    1 -> binding.textView.text = "青を押しましたね"
                    2 -> binding.textView.text = "黄を押しましたね"
                    else -> {}
                }
            }
            // チェックボックス形式の場合
            is CheckBoxDialogFragment -> {
                val sb = StringBuilder()
                dialog.selectedItems.forEach { it ->
                    sb.append(dialog.toppingList[it] + "\r\n")
                }
                binding.textView.text = sb.toString()
            }
            // カスタムレイアウトの場合
            is CustomLayoutDialogFragment -> {
                binding.textView.text =
                    String.format("%s\n\r%s", dialog.userName.text.toString(), dialog.password.text.toString())
            }
            else -> {}
        }
    }
    override fun onDialogNegativeClick(dialog: DialogFragment) {
        Log.d("MainActivity", "onDialogNegativeClick")
    }
    override fun onDialogNeutralClick(dialog: DialogFragment) {
        Log.d("MainActivity", "onDialogNeutralClick")
    }
```

完成形は[gitHub](https://github.com/cozyk100/AndroidStudioProjectsPublic/tree/main/DailogFragment)に置きました。