うきっとラボ~中卒から始めるプログラミング~

中卒のポンコツ太郎が立派なプログラマになるまでの道のり

【Android Studio】ダイアログを使ってみるぞい

f:id:ukiuki0518:20191219212647j:plain
ダイアログはコレだ!!

はじめに

アプリを作っていく中で、ダイアログを使用する機会がありました。
実装しながら調べていると
なかなか応用の利く便利な仕組みになっているようなので、
調べた内容を自分の考察や応用も含めてまとめていこうと思います(^-^)

ダイアログを使うメリット

まず、
ダイアログを使用するメリットについて、自分なりに考えてみました。


メリットを見つけるために、ダイアログの特徴を考えてみます(∩´∀`)∩

まず、ダイアログの使い方として、
アクティビティでユーザーが行った操作の確認
というのが一般的で、イメージが浮かびやすい使い方なのではないでしょうか。


もちろん確認画面として使用できることも特徴ではあると思うのですが、
やり方によっては、入力画面として使用するといった使い方も可能です。
これを踏まえるとダイアログの特徴は、

遷移ではなく最前面に追加表示させる小さな画面

ということになります。

例えば入力内容の確認のためだけに画面を遷移させる場合、
画面が毎度切り替わることでユーザに”操作画面の多さ”を感じさせてしまい
ユーザビリティが低下してしまいます。

ダイアログを使用することで、元の画面を見せた状態で小さな画面がポップアップするため、実際の操作は変わらなくとも”操作画面の多さ”をあまり感じさせないようになっています。



また、この仕組みって「フラグメント」と似ていますよね。
なぜでしょう?

実はandroidのダイアログは「フラグメント」の一種だからです。
そのため
「フラグメントとしてのメリットも持ちつつ、ダイアログとしての機能を簡単に実装できるメソッドがたくさん用意されている」

ことがダイアログの大きなメリットではないかな、と思います。



さぁ、では実際に実装していきましょう!


確認ダイアログを実装してみる

【実装内容】
入力テキストを表示欄に表示させるかを確認。
”はい”の場合:入力された文字列を表示欄に表示する
”いいえ”の場合:キャンセルし、それをトーストで通知する


使用するレイアウト

f:id:ukiuki0518:20191219122407j:plain
入力欄と表示欄が各1つ

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="end"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:text="ダイアログの実装"
        android:textSize="25sp"
        android:textStyle="bold" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/colorPrimary"
        android:padding="5dp"
        android:text="入力内容の表示"
        android:textColor="#ffffff"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tvOutput"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:maxLines="1"
        android:textSize="20sp" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/colorPrimary"
        android:padding="5dp"
        android:text="文字を入力"
        android:textColor="#ffffff"
        android:textSize="20sp" />

    <EditText
        android:id="@+id/etInput"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="ここに入力"
        android:inputType="text"
        android:maxLines="1" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onConfirmButtonClick"
        android:text="確認" />

</LinearLayout>


①ダイアログを表すクラスを作成する

まず、
ダイアログとして使用する
ダイアログフラグメントクラスを作成していきます。


ダイアログとして使用するためには、
DialogFragmentクラスを継承しておく必要があります。
コンストラクタの引数には何も渡しません。

class ConfirmDialogFragment : DialogFragment(){
}


②内部にリスナインタフェースを定義する

今回の例のように、
ダイアログのボタン選択によって呼び出し元のアクティビティでの動きを変える場合、
そのような処理を記述しなければいけませんΣ(゚Д゚)


これを実現する方法はいくつかありますが、今回は
ボタンが押された時の処理をアクティビティクラス側に記述する
方法を使っていきます。この方法のメリットは、アクティビティ側のデータをダイアログ側へ持って行かなくてもいいことです。


具体的な処理をアクティビティ側に委ねることでダイアログクラス内の記述が減り、
また、機能が分離されることで他の場面でも使いまわすことが容易になりますね(/・ω・)/


そのためにまず、
「具体的な処理の枠組み」となるインタフェースを定義します(^^)/


また、
リスナインタフェースを実装したアクティビティのインスタンス
ダイアログフラグメント側で保持するための変数も定義
しています。(^-^)

class ConfirmDialogFragment : DialogFragment(){
    /* ConfirmDialogListenerを実装したアクティビティのインスタンスを、
     * ConfirmDialogListener型にキャストしたものがここに入る */
    private lateinit var listener: ConfirmDialogListener

    interface ConfirmDialogListener{
        //「はい」ボタンが押された時用のメソッド
        fun onDialogPositiveClick()
        //「いいえ」ボタンが押された時用のメソッド
        fun onDialogNegativeClick()
    }
}


③アクティビティクラスに、リスナインタフェースを実装

アクティビティクラスにリスナインタフェースを実装することで、
各ボタンが押された時の処理をこちら側に記述することができます。


今回は例として

〈「はい」が選択された場合〉
入力欄の文字列を、表示欄に表示させる

〈「いいえ」が選択された場合〉
キャンセルされたことをトースト表示する

という内容を実装しました。

class MainActivity : AppCompatActivity(), ConfirmDialogFragment.ConfirmDialogListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        // 省略
    }

    // "はい"が押された時
    override fun onDialogPositiveClick() {
        val inputStr = etInput.text.toString()
        tvOutput.text = inputStr
    }

    // "いいえ"が押された時
    override fun onDialogNegativeClick() {
        Toast.makeText(this, "キャンセルされました",Toast.LENGTH_SHORT).show()
    }
}


④ダイアログフラグメント側で、初期化処理を行う

onAttach()メソッドをオーバーライドして、
listener変数を初期化します(/・ω・)/


その際、try-catchを使用して、
アクティビティクラスがConfirmDialogListener

実装していればキャストして保持」、

実装していなければ例外再スロー

という処理にしています(*^^)

class ConfirmDialogFragment : DialogFragment(){
    /*
     *  省略
     */ 
    
    override fun onAttach(context: Context) {
        super.onAttach(context)
        try {
            listener = context as ConfirmDialogListener
        }catch (e: ClassCastException){
            throw ClassCastException("$context must be implement ConfirmDialogListener")
        }
    }
}


⑤ダイアログをセットアップする

やっとダイアログの設定を行っていきます(;´∀`)


AlertDialog.Builderを使って、タイトルやメッセージの設定を行います。


また、set~Button()でボタンの設定も行っています。

書き方をかなり簡略化していますが、
コメントアウトした部分と同じ意味になります。

class ConfirmDialogFragment : DialogFragment(){
    private lateinit var listener: ConfirmDialogListener
    /*
     * 省略
     */
    
    override fun onAttach(context: Context) {
        /*
         * 省略
         */
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let{
            val builder = AlertDialog.Builder(it)
            builder.setTitle("入力内容の確認")
            builder.setMessage("入力内容を表示しますか?")
            builder.setPositiveButton("はい"){ _, _ ->
                listener.onDialogPositiveClick()
            }
            /*
            builder.setPositiveButton(
                "はい",
                DialogInterface.OnClickListener{dialog, which ->
                    listener.onDialogPositiveClick()
                })
            */
            builder.setNegativeButton("いいえ"){ _, _ ->
                listener.onDialogNegativeClick()
            }
            builder.create()
        } ?: throw IllegalStateException("Activity cannot be null")
    }
}


⑥ダイアログを表示させる処理を記述する

アクティビティ側で、ダイアログを表示させる処理を記述していきます。


とっても簡単です。
適切なタイミングでダイアログクラスをインスタンス化し、show()を呼び出すだけです。


show()の第1引数には「supportFragmentManager」を指定します。

第2引数は、表示させるダイアログフラグメントクラスを表す文字列です。

class MainActivity : AppCompatActivity(), ConfirmDialogFragment.ConfirmDialogListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        // 省略
    }

    fun onConfirmButtonClick(view: View) {
        val dialog = ConfirmDialogFragment()
        dialog.show(supportFragmentManager, "ConfirmDialogFragment")
    }

    override fun onDialogPositiveClick() {
        // 省略
    }

    override fun onDialogNegativeClick() {
        // 省略
    }
}

完成

[f:id:ukiuki0518:20191219152457g:plain:h:400]
確認ダイアログができた

できました(^_^)v
今回は「確認ダイアログ」として実装しましたが、前述のとおり
「入力用ダイアログ」としても使うことができます。

また、それについてはまとめて記事にしたいと思います<(_ _)>