【Kotlin】DialogFragmentでダイアログ表示【Androidアプリ開発】
この記事では、KotlinのDialogFragmentを使って、Androidアプリでダイアログ表示するやり方を解説します。「リストメニュー」「チェックボックス」「ラジオボタン」「ログイン入力」などの、すべてのダイアログの作り方をご紹介します。また、アクティビティへコールバックする方法も解説しました。
はじめに
Androidアプリでダイアログを表示させるには、Dialogクラスを継承したAlertDialogを使います。アクティビティで直接インスタンスを生成するのではなく、DialogFragmentというクラスを経由してインスタンスを生成することになります。DialogFragmentクラスはフラグメントを継承しますので、アクティビティのライフサイクルと同期されます。 フラグメントに慣れてない方は KotlinでFragmentを理解する【Androidアプリ開発】 をお読みになってからDialogFragmentを使うとスムーズです。フラグメントを使ったほうが、DialogFragmentでカプセル化されますし、アクティビティの肥大化が防げて管理しやすくなります。
シンプルなダイアログ
シンプルなダイアログを作るため、DialogFragmentクラスを継承したSimpleDialogFragmentクラスを作成します。onCreateDialogメソッドをオーバーライドし、その中でAlertDialogのインスタンスをビルダーで生成してます。
class SimpleDialogFragment: DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(activity)
builder.setTitle("Here Title")
.setMessage("Here Message")
.setPositiveButton("done") { dialog, id ->
println("dialog:$dialog which:$id")
}
.setNegativeButton("cancel") { dialog, id ->
println("dialog:$dialog which:$id")
}
return builder.create()
}
}
アクティビティからは次のようにしてダイアログフラグメントを呼び出します。
val dialog = SimpleDialogFragment()
dialog.show(supportFragmentManager, "simple")
これ以降のダイアログでも、アクティビティからの呼び出しはすべてこの方法で行います。
リストメニューのダイアログ
さきほどのシンプルなダイアログでは、setPositiveButtonとsetNegativeButtonでボタンの追加を行いました。今回のリストメニューのダイアログでは、setItemsメソッドを使ってリスト配列をセットしリスト表示を行っていきます。またsetMessageを実装してしまうと、リストが表示されませんのでご注意ください。
class ListDialogFragment: DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(activity)
builder.setTitle("Here Title")
// .setMessage("Here Message") // setMessageは使うとリスト表示されないので注意!
.setItems(R.array.language_array) { dialog, which ->
val langs = resources.getStringArray(R.array.language_array)
println(langs[which])
}
return builder.create()
}
}
interface OnClickListener {
/**
* This method will be invoked when a button in the dialog is clicked.
*
* @param dialog the dialog that received the click
* @param which the button that was clicked (ex.
* {@link DialogInterface#BUTTON_POSITIVE}) or the position
* of the item clicked
*/
void onClick(DialogInterface dialog, int which);
}
チェックボックスのダイアログ
チェックボックスタイプのダイアログにはsetMultiChoiceItemsを使用します。setMultiChoiceItemsの第二引数には、あらかじめチェック状態を設定するためbooleanArray型で初期値を渡すことができます。
class CheckboxDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val checkedItems = booleanArrayOf(false, true ,false) // 保存されたデータに置き換えることができる
val mSelectedItems:MutableList<Int> = mutableListOf()
setupSelectedItems(checkedItems, mSelectedItems)
val builder = AlertDialog.Builder(activity)
builder.setTitle("Here Title")
// .setMessage("Here Message") // setMessageは使うとリスト表示されないので注意!
.setMultiChoiceItems(R.array.language_array, checkedItems) { dialog, which, isChecked ->
if (isChecked) {
mSelectedItems.add(which)
} else {
mSelectedItems.remove(which)
}
}
.setPositiveButton("OK") { dialog, id ->
printSelectedStatus(mSelectedItems)
}
.setNegativeButton("Cancel") { dialog, id ->
}
return builder.create()
}
private fun setupSelectedItems(
checkedItems: BooleanArray,
mSelectedItems: MutableList<Int>
) {
var index = 0
checkedItems.forEach {
if (it) {
mSelectedItems.add(index)
}
index++
}
}
private fun printSelectedStatus(mSelectedItems: MutableList<Int>) {
val langs = resources.getStringArray(R.array.language_array)
mSelectedItems.forEach {
println(langs[it])
}
}
}
ラジオボタンのダイアログ
ラジオボタンタイプのダイアログではsetSingleChoiceItemsを使います。setSingleChoiceItemsの第二引数では、デフォルトで選択させたいラジオボタンのインデックスをint型で渡すことができます。
class RadiobuttonDialogFragment: DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(activity)
builder.setTitle("Here Title")
.setSingleChoiceItems(R.array.language_array, 1) { dialog, which: Int ->
println(which)
}
.setPositiveButton("OK") { dialog, id ->
}
.setNegativeButton("Cancel") { dialog, id ->
}
return builder.create()
}
}
【コラム】Dialogとは
ここで閑話休題です。英単語のDialogについて少しお話しましょう。 Dialogとは、Dialogueとも呼び、そのの語源はlogの「話す」から来ているそうです。logとは、ギリシャ語で「話す」や「単語」「会話」を意味します。 また、diaは「間で」の意味がありますので、「dia(間で)」+「log(話す)」となり、Dialogは「対話」や「意見交換」といった意味になります。
英英辞書でDialogを調べると「(a) written conversation in a book or play」の一文がありまして、つまりは、本やゲームでの「書かれた対話」でもあります。
また、logには「log house」にも使われるように「木」という意味もってます。昔は、文字を木に書いていたこともあるので、log自体に「対話(書かれた)」の意味をもつのは自然なことのように思われますね。
アプリでのDialog表示は、ユーザーさんとの対話になりますので、一方的なスパムアラートにならないよう十分配慮しましょうね。
ちなみに、logを語源とする単語は、Dialog以外にもたくさん存在します。catalog(カタログ)、logo(ロゴ)、logic(論理)、apology(謝罪)、prologue(序幕)、epilogue(結びの言葉)、monologue(独白)などなど。
プログラミングで出てくる英語も、語源を知っておくと意図していることが分かりやすくなりますので、お暇なときにでも、英単語の語源を学んでみるのをおすすめします!
カスタムレイアウトのダイアログ(ログイン入力)
ダイアログのレイアウトは、自由にカスタマイズすることもできます。ここでは公式ドキュメントにならって、ログイン入力用のダイアログを実装しました。
まず、ダイアログ内に表示させたいレイアウトを、レイアウトファイルdialog_signin.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="wrap_content"
android:layout_height="wrap_content">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:id="@+id/email"
android:hint="Email"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:id="@+id/password"
android:hint="Password"/>
</LinearLayout>
次に、作成したレイアウトをDialogFragmentクラスの中でinflaterを使ってinflateします。つまりdialog_signin.xmlレイアウトを元にしたViewを生成させます。丁寧にプログラムを追えば、何も難しいことはないはずです。
class SigninDialogFragment:DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(activity)
val inflater = activity!!.layoutInflater
val signinView = inflater.inflate(R.layout.dialog_signin, null)
builder.setView(signinView)
.setTitle("Sign in")
.setPositiveButton("OK") { dialog, id ->
val email = signinView.findViewById<EditText>(R.id.email).text
val password = signinView.findViewById<EditText>(R.id.password).text
println("Email: $email Password:$password")
}
.setNegativeButton("Cancel") { dialog, id ->
}
return builder.create()
}
}
ダイアログからアクティビティへコールバック
最後に、アクティビティへコールバック可能なダイアログを作成してみましょう。
インタフェースを使ってコールバックを実装することで、アクティビティとダイアログを疎結合になりますので管理がしやすくなります。ここでは最初の「シンプルなダイアログ」のプログラムをベースに改良してあります。
class NoticeDialogFragment: DialogFragment() {
public interface NoticeDialogLister {
public fun onDialogPositiveClick(dialog:DialogFragment)
public fun onDialogNegativeClick(dialog:DialogFragment)
}
var mLister:NoticeDialogLister? = null
override fun onAttach(context: Context?) {
super.onAttach(context)
try {
mLister = context as NoticeDialogLister
} catch (e: ClassCastException) {
throw ClassCastException("${context.toString()} must implement NoticeDialogListener")
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(activity)
builder.setTitle("Here Title")
.setMessage("Here Message")
.setPositiveButton("ok") { dialog, id ->
println("dialog:$dialog which:$id")
mLister?.onDialogPositiveClick(this)
}
.setNegativeButton("cancel") { dialog, id ->
println("dialog:$dialog which:$id")
mLister?.onDialogNegativeClick(this)
}
return builder.create()
}
override fun onDestroy() {
println("NoticeDialogFragmentのonDestroyが呼ばれたよ!")
super.onDestroy()
}
override fun onDetach() {
println("NoticeDialogFragmentのonDetachが呼ばれたよ!")
super.onDetach()
mLister = null
}
}
上記のプログラムをすこし詳しく見ていきましょう。
まず、コールバック用のインターフェイスNoticeDialogListerを作り、DialogFragmentを継承したNoticeDialogFragmentクラス内に作成します。
class NoticeDialogFragment: DialogFragment() {
public interface NoticeDialogLister {
public fun onDialogPositiveClick(dialog:DialogFragment)
public fun onDialogNegativeClick(dialog:DialogFragment)
}
...
このインタフェースはダイアログを呼び出すホスト、つまりMainActivityへ実装します。
class MainActivity : AppCompatActivity(), NoticeDialogFragment.NoticeDialogLister {
override fun onDialogPositiveClick(dialog: DialogFragment) {
println("NoticeDialogでOKボタンが押されたよ!")
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
println("NoticeDialogでCancelボタンが押されたよ!")
}
...
...
override fun onAttach(context: Context?) {
super.onAttach(context)
try {
mLister = context as NoticeDialogLister
} catch (e: ClassCastException) {
throw ClassCastException("${context.toString()} must implement NoticeDialogListener")
}
}
...
そしてダイアログのボタンクリックイベント内で、NoticeDialogListerのメソッドを実行すれば、フラグメントからアクティビティへの通知が可能になります。
...
.setPositiveButton("ok") { dialog, id ->
println("dialog:$dialog which:$id")
mLister?.onDialogPositiveClick(this)
}
...
以上でKotlinのDialogFragmentを使って、Androidアプリでダイアログ表示するやり方の説明をおわります。本プロジェクトは、 GitHubリポジトリ で公開してます。
▼ こんな記事も書いてます。