Dialogと仲良くなる その1
先日、RecyclerViewと仲良くなった(気がする)
次はDialogと仲良くなってみる
参考サイト
ひたすら公式
Android Developersに載っているコードをほぼまんま写した
公式を見る際に、日本語で見るとKotlinのコードが表示されない
なので、英語が苦手な方は英語と日本語交互に見ながらやるとよいだろう
(なんで日本語のはないんだろう・・・)
Fragment Dialog
公式より、Dialogを使うときはFragmentDialogを使いなさいと言われている
Fragmentか〜って感じではあるが、かのGoogle様直々のお願いなので守らなくてはいけない
どうやらFragmentを使うことにより、サイクルが確保される模様
お断り
Dialogには様々な種類がある
この記事では、全てのDialogを扱うわけではない
扱うDialog
Alert dialog
-> Alert dialogs interrupt users with urgent information, details, or actions.
Simple dialog
-> Simple dialogs display a list of items that take immediate effect when selected.
Confirmation dialog
-> Confirmation dialogs require users to confirm a choice before the dialog is dismissed.
扱わないDialog
Full-screen dialog
-> Full-screen dialogs fill the entire screen, containing actions that require a series of tasks to complete.
Date/Time PickerDialog
-> A dialog with a pre-defined UI that allows the user to select a date or time.
Custom Dialog
-> Dialogに表示するレイアウトをカスタマイズしたもの
Custom DialogとPickerDialogの説明以外は全てここから引用
PickerDialogはリファレンスより
また、今回利用するDialogは全てサポートライブラリのDialogである
コピっててなんかエラーが出るってなったらまずはimport文を見よう
下準備
MainActivtyのレイアウトと、Simple,Confirmationで扱うArrayListの定義は特筆することがないのでコードだけ
あとSnackbarを使うのでADSLも忘れずに
Android Design Support Library
これ入れとくだけでナウくなるからいっぱい好き
implementation 'com.android.support:design:27.1.1'
activity_main.xml
Dialogを呼び出すためのButtonを用意する
今回は5種類ほどDialogを実装する
<LinearLayout android:id="@+id/container" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" xmlns:android="http://schemas.android.com/apk/res/android"> <Button android:id="@+id/bt_basic" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Basic Dialog" android:layout_marginBottom="8dp" android:textAllCaps="false"/> <Button android:id="@+id/bt_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Button Dialog" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:textAllCaps="false"/> <Button android:id="@+id/bt_list" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="List Dialog" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:textAllCaps="false"/> <Button android:id="@+id/bt_check" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="CheckBox Dialog" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:textAllCaps="false"/> <Button android:id="@+id/bt_radio" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="RadioBox Dialog" android:layout_marginTop="8dp" android:textAllCaps="false"/> </LinearLayout>
完成図
values/array.xml
ちゃんとリソースファイルを使う癖を付けなな〜とか思いつついつもサボる
<?xml version="1.0" encoding="utf-8"?> <resources> <array name="items"> <item>Item1</item> <item>Item2</item> <item>Item3</item> <item>Item4</item> <item>Item5</item> </array> </resources>
本題
では、実装していく
今回はDialogs
というパッケージを作って、そこにバコバコ入れていく
Basic Dialog
最も基本的なDialog
ただ文字を表示するだけ、正直使いどころはない
でも、基本を学ぶにはシンプルでちょうどいい
package net.oldbigbuddha.sample.sampledialog.Dialogs import android.app.Dialog import android.os.Bundle import android.support.v4.app.DialogFragment import android.support.v7.app.AlertDialog class BasicDialogFragment : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return activity?.let { val builder = AlertDialog.Builder(it) builder.setTitle("Basic Dialog") .setMessage("This is Basic Alert Dialog") builder.create() } ?: throw IllegalAccessError("Activity cannot be null") } }
本当に最小限の構成
onCreateDialog()
でAlertDialogを生成し、返り値にしてやる
ただそれだけ
Builderを作って、色々設定を加えて、create()
してやれば出来上がり
let{}
is何ってこは”Kotlin スコープ関数”で検索
MainActivityで呼び出し処理を書く
bt_basic.setOnClickListener { BasicDialogFragment().show(supportFragmentManager, "basic") }
setTitle()
で一番上のTitle部分に文字列をセットし
setMessage()
でメイン部分(Supporting text)に文字列をセットする
これはデザイン的な話になるが
GoogleはTitle部分に曖昧な文言を設定することを推奨していない
例えば”Are you sure?”である
なるべく読み取れる意味を絞れってはなしですな
Button Dialog
公式的には”Alert Dialog”らしい
右下にボタンがついてるDialog
ココらへんから使いどころがでてくるかな
PositiveButton(OKやAgreeなど)は必ず右側に置かないといけない(自動で置かれるので気にする必要はない)
package net.oldbigbuddha.sample.sampledialog.Dialogs import android.app.Dialog import android.content.DialogInterface import android.os.Bundle import android.support.v4.app.DialogFragment import android.support.v7.app.AlertDialog class ButtonDialogFragment: DialogFragment() { lateinit var onClickPositive: DialogInterface.OnClickListener lateinit var onClickNegative: DialogInterface.OnClickListener override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return activity?.let { val builder = AlertDialog.Builder(it) builder.setTitle("Button Dialog") .setMessage("This is Button Dialog") .setPositiveButton("OK", onClickPositive) .setNegativeButton("cancel", onClickNegative) builder.create() } ?: throw IllegalAccessError("Activity cannot be null") } }
Listenerを利用して、タップされた時の処理を外で書くようにする
setPositiveButton()
で肯定的なアクションを
setNegativeButton()
で否定的なアクションを設定する
第一引数でボタンに表示する文字列を設定する(リソースファイルぅ)
MainActivityで呼び出す
bt_button.setOnClickListener { val fragment = ButtonDialogFragment() fragment.onClickPositive = DialogInterface.OnClickListener { _, _ -> Snackbar.make(container, "OnClick 'OK'", Snackbar.LENGTH_SHORT).show() } fragment.onClickNegative = DialogInterface.OnClickListener {_, _ -> Snackbar.make(container, "OnClick 'cancel'", Snackbar.LENGTH_SHORT).show() } fragment.show(supportFragmentManager, "button") }
まぁ、タップしたらSnackbarがでてくるというもの
たまに”OK”、”Cancel”ときて、第三のButton”Learn more”がでてくるDialogがある
material.io曰く、第三のButtonは本来不要で、他の場所で補うべきだと述べている
Rather than adding a third action, an inline expansion can display more information. If more extensive information is needed, provide it prior to entering the dialog.
List Dialog
公式的には”Simple Dialog”
Listを表示するDialog
タップした内容を取得できる
package net.oldbigbuddha.sample.sampledialog.Dialogs import android.app.Dialog import android.content.DialogInterface import android.os.Bundle import android.support.v4.app.DialogFragment import android.support.v7.app.AlertDialog import android.util.Log import net.oldbigbuddha.sample.sampledialog.R class ListDialogFragment: DialogFragment() { lateinit var onSelectedItem: DialogInterface.OnClickListener override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return activity?.let { val builder = AlertDialog.Builder(it) builder.setTitle("List Dialog") .setItems(R.array.items, onSelectedItem) builder.create() } ?: throw IllegalAccessError("Activity cannot be null") } }
setItems()
で配列とListenerを投げてやる
MainActivityの方で、タップした位置を取得することができる
bt_list.setOnClickListener { val fragment = ListDialogFragment() fragment.onSelectedItem = DialogInterface.OnClickListener {_, index -> resources?.let { val item = it.getStringArray(R.array.items)[index] Snackbar.make(container, "Selected $item", Snackbar.LENGTH_SHORT).show() } } fragment.show(supportFragmentManager, "list") }
(仮引数の名前をindex
としているが、position
のほうがいいかもしれない)
特別変なことはしていない
let関数
の中で自身を指定する場合はit
を使う
それぐらい?
Resource周りは勉強しといたほうが良い(人のこと言えない)
図は、item2を選択した場合のSnackbar
RadioBox Dialog
公式的には”Confirmation Dialog”
リファレンスを見ると、setSingleChoiceItems()
を利用しているので
Single Choice Dialogっていうのが一番正しい名前なのかね
package net.oldbigbuddha.sample.sampledialog.Dialogs import android.app.Dialog import android.content.DialogInterface import android.os.Bundle import android.support.v4.app.DialogFragment import android.support.v7.app.AlertDialog import net.oldbigbuddha.sample.sampledialog.R class RadioBoxDialogFragment: DialogFragment() { lateinit var onSelectedItem: DialogInterface.OnClickListener lateinit var onClickPositive: DialogInterface.OnClickListener var selectedIndex = 0 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return activity?.let { val builder = AlertDialog.Builder(it) builder.setTitle("RadioBox Dialog") .setSingleChoiceItems(R.array.items, 0/* default selected */,onSelectedItem) .setPositiveButton("OK", onClickPositive) builder.create() } ?: throw IllegalAccessError("Activity cannot be null") } }
少し個人的には納得していないコード
setSingleChoiceItems()で設定したListener内で、選択されたItemのindexが取れる(
fragment.selectedIndex = index`)
それをフィールドで保存してやり、MainActivityで呼び出すことで選択されたItemを取れるようにしている
bt_radio.setOnClickListener { val fragment = RadioBoxDialogFragment() fragment.onSelectedItem = DialogInterface.OnClickListener { _, index -> resources?.let { fragment.selectedIndex = index } } fragment.onClickPositive = DialogInterface.OnClickListener{ _, _ -> resources?.let { val item = it.getStringArray(R.array.items)[fragment.selectedIndex] Snackbar.make(container, "Selected $item", Snackbar.LENGTH_SHORT).show() } } fragment.show(supportFragmentManager, "radio") }
ちなみにsetSingleChoiceItems()
の第二引数に-1を設定すると標準で無選択になる
CheckBox Dialog
本記事最後のDialog、公式的には”Confirmation Dialog”
さっきのがsetSingleChoiceItems()
だったのに対し、これはsetMultiChoiceItems()
を使う
う〜ん、わかりやすい
package net.oldbigbuddha.sample.sampledialog.Dialogs import android.app.Dialog import android.content.DialogInterface import android.os.Bundle import android.support.v4.app.DialogFragment import android.support.v7.app.AlertDialog import net.oldbigbuddha.sample.sampledialog.R class CheckBoxDialogFragment: DialogFragment() { lateinit var onClickPositive: DialogInterface.OnClickListener lateinit var onClickNegative: DialogInterface.OnClickListener lateinit var onMultiSelected: DialogInterface.OnMultiChoiceClickListener override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return activity?.let { val builder = AlertDialog.Builder(it) builder.setTitle("CheckBox Dialog") .setMultiChoiceItems(R.array.items, null/* default selected */,onMultiSelected) .setPositiveButton("OK", onClickPositive) .setNegativeButton("cancel", onClickNegative) builder.create() } ?: throw IllegalAccessError("Activity cannot be null") } }
さっきのSingle同様リスナーを渡します
setMultiChoiceItems()
の第二引数はbooleanの配列です
trueのところを選択状態にしてくれます
bt_check.setOnClickListener { val fragment = CheckBoxDialogFragment() val mSelectedItems = ArrayList<Int>() // Where we track the selected items fragment.onMultiSelected = DialogInterface.OnMultiChoiceClickListener { _, index, isChecked -> if (isChecked) { mSelectedItems.add(index) } else if (mSelectedItems.contains(index)) { mSelectedItems.remove( Integer.valueOf(index) ) } } fragment.onClickPositive = DialogInterface.OnClickListener { _, _ -> resources?.apply { val mSelectedItemContents = ArrayList<String>() mSelectedItems.forEach { mSelectedItemContents.add(this.getStringArray(R.array.items)[it]) } Snackbar.make(container, "Selected $mSelectedItemContents", Snackbar.LENGTH_SHORT).show() } } fragment.show(supportFragmentManager, "checkbox") }
う〜ん、長い
そしてキモい(forEach in スコープ関数
ということでthisが使えるapplyで対処した)
まぁ、大体CheckBox
を実装するときと同じですな
タップされたものがFalseならTrueに、TrueならFalseへ
OnMultiChoiceClickListener()
で取れたTrace配列はInt型ですので
中の数値ををIndexとしてItem配列から選択されたItemをとりだします
まさかこんなところでforEach()
に出会うとは、、、(やぎにぃに感謝)
さぁ、これで全てが出揃った
個人的にはDialogととりあえずは仲良くなれたように思える
あとはFull-ScreenとCustomとPicker、、、
ま、のんびりやっていきます
今回のコードのまとめはこちらから見ることができます
意味わかんねぇよとか、ここエラーでたんですけどとかあったらTwitterでメンション投げてください
@OJI_1941です(ごめんなさいDM閉じてるんで最初はメンションでお願いします)
ありがとうございました