DialogFragmentを使ったダイアログの表示【Android・Kotlin】


画像の拡大

この記事では、AndroidStudioでKotlinを使って、DialogFragmentでのダイアログの表示のやり方を解説していく。

ダイアログの基本

Androidでダイアログを表示させるにはDialogクラスを継承したAlertDialogを使う。Activityでインスタンス生成するのではなく、DialogFragmentクラスを経由してインスタンスを生成する。DialogFragmentクラスはFragmentを継承しているのでActivityのライフサイクルと同期することができる。また、DialogFragmentでカプセル化されるのでActivityが肥大化せず、ダイアログを管理しやすくなる。

実際にサンプルを見たほうが分かりやすいので、次ではCancelDoneの2つのボタンを実装したシンプルなダイアログを作っていく。

シンプルなダイアログ


画像の拡大

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()
    }

}

Activityからは次のようにして呼び出すことができる。以降、ダイアログの呼び出しはすべてこの方法で行う。

val dialog = SimpleDialogFragment()
dialog.show(supportFragmentManager, "simple")

リストメニューのダイアログ


画像の拡大

さきほどのシンプルなダイアログではsetPositiveButtonなどでボタンを追加したが、リスト表示では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()
    }

}

setItemsメソッドの第二引数へはクリックされたときの処理をラムダ式を渡すことになっている。このラムダ式はOnClickListenerインタフェースで定義されている。ラムダ式内の第二引数(which)にはクリックされた配列のインデックスが渡される。

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_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レイアウトを元にした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()
    }
}

ダイアログからアクティビティへコールバックする


画像の拡大

最後にアクティビティへコールバック可能なダイアログを作成する。インタフェースを使ってコールバックを実装することでActivityとダイアログを疎結合にすることができ、キレイなコードで管理することができる。ここではシンプルなダイアログのプログラムを元に改造した。

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ボタンが押されたよ!")
    }
	...

onAttachMainActivity(ここではContextになっているが)をNoticeDialogListerへキャストしている。ホストのActivityNoticeDialogListerインタフェースが実装されていなければ例外がスローされるようになっている。これで実装忘れを防げるのだ。

	...
    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)
            }
...

このコールバックの実装パターンは、iOS開発では頻繁に行われるデリゲートでのコールバックとよく似ている。Objective-C/SwiftのprotocolがJava/Kotlinのinterfaceに対応しているのだ。またiOSでは強参照を避けるためにweakを指定するが、AndroidでのFragmentを通した実装ではその必要がないようである。確かなことは言えないが、ダイアログを終了するとDialogFragmentonDestroyonDetachが呼ばれることを確認しているので問題ないと思う。

本プロジェクトは、下記GitHubリポジトリへ公開してあるのでよかったら参考に。

デスクワークの負担を減らすアイテム

作業効率をアップさせるには姿勢が大事です!実際に使ってみてどれもオススメなのでよかったら参考に!

Bestandノートパソコンスタンド 11 '' -16 '' Macbook Air Pro/富士通と互換性のある放熱性に優れたアルミニウム合金PCスタンド-シルバー
Bestandノートパソコンスタンド 11 '' -16 '' Macbook Air Pro/富士通と互換性のある放熱性に優れたアルミニウム合金PCスタンド-シルバー

ノートパソコンの高さをあげると、長時間のデスクワークでも作業者の負担を軽減する事が可能です。 アルミニウム製品、純正品、高品質、高級感があ ります。 対応機種:MacBook、MacBook Air、MacBook Pro、富士通と対応11"-16"ノートパソコン スタンド.

Amazon
AKRacing ゲーミングチェア Pro-X V2 GREY
AKRacing ゲーミングチェア Pro-X V2 GREY

AKRacingハイエンドゲーミングチェア。大きめの座面でゆったりとした座り心地。張地を経年劣化に強い高耐久仕様のPUレザーに変更したアップグレードモデル 寸法:背もたれ幅55cm/奥行55cm/高さ95cm、座面下高さ:32~39cm、座面厚さ:13cm、重量:25kg

Amazon
Kensington ExpertMouse ワイヤレストラックボール K72359JP
Kensington ExpertMouse ワイヤレストラックボール K72359JP

接続方式/Bluetooth、2.4GHz USB付属品/レシーバー、パームレスト、日本語取扱説明書 サイズ(本体):W130×D157×H65mm 対応OS:2.4GHzUSB:Windows10/8.1/8/7、MacOS10.8以降/Bluetooth4.0LE:Windows10/8.1、MacOS10.8以降

AmazonRakuten

Anrdoidアプリ開発にオススメの書籍

こちらの本は、図や絵が多くてわかりやすく、Androidアプリ開発初心者でも理解しやすい内容。基本的なことはしっかりおさえられるので、この手の本は一冊読んでおくと良と思う。

基本からしっかり身につくAndroidアプリ開発入門
基本からしっかり身につくAndroidアプリ開発入門

圧倒的な多数のユーザーが使っているヤフーのアプリ。その制作の最前線にいる黒帯エンジニアが、ユーザーが使いやすいアプリの大切な基本をしっかりと解説します。

KindleAmazonRakuten

Kotlinの実践的な使い方が学べる内容の本で、プログラミング技術をもっと磨きたい人向け。センスの良いプログラミングがたくさん紹介されていて、個人的には「目からうろこ」の連続だった本。

Kotlinプログラミング
Kotlinプログラミング

Kotlinは、Javaとの相互運用を可能にし、Android OSでGoogleがフルサポートする静的型プログラミング言語です。この言語は、Javaだけでは十全ではない(Javaだけでは実装に手間がかかりすぎる)、軽量かつ豊かな表現形式や、他言語ではすでに実装されている最新の機能を盛り込んでいます。

KindleAmazonRakuten

【おまけ】英語雑学


画像の拡大

Dialog/Dialogueの語源は log(話す) から来ている。logはギリシャ語で「話す」や「単語」「会話」を意味する。

dia(間で) + log(話す) であるのでつまりは「対話」「意見交換」といった意味となる。

英英辞書では (a) written conversation in a book or play と書かれているように、本やゲームにおいての書かれた対話となる。

また log には log house のように、の意味もある。

文字を木に書いていた歴史からもlogが対話(書かれた)の意味をもつのは、自然なことではないだろうか。

The mayor tried to maintain a dialogue with the citizens.
He's not very good at writing dialogue.

logを語源とする単語は他にもたくさん存在する。

catalog(カタログ)/logo(ロゴ)/logic(論理)/apology(謝罪)/prologue(序幕)/epilogue(結びの言葉)/monologue(独白)

アプリのDialogもユーザーとの対話なので、こちらの一方的なスパムアラートにならないよう、十分配慮したいところだ。

Amazonでお得に購入するなら、Amazonギフト券がオススメ!

\Amazonギフトがお得/

コンビニ・ATM・ネットバンキングで¥5,000以上チャージすると、プライム会員は最大2.5%ポイント、通常会員は最大2%ポイントがもらえます!
Amazonギフト券

\この記事をシェアする/