Androidのダイアログを作ろう - サンプルコードで学ぶAlertDialogの使い方の基本からカスタマイズ

Androidでの通知の仕組み「Dialog」の代表格であるAlertDialogには、ユーザーに選択を促す場合以外に、自由度の高い画面を作れる機能があります。この記事では、サンプルコードとともにAlertDialog.Builderクラスを使って用途別にダイアログを構成する情報を設定する方法、カスタマイズを行う方法について説明します。

Androidのダイアログを作ろう - サンプルコードで学ぶAlertDialogの使い方の基本からカスタマイズ

PCでもモバイルでもWebのサービスでも、ユーザーに重要な情報を伝えたいときや選択肢から選ばせたい場合などに、「ダイアログ」というUIが使われます。Androidでは、ユーザーに対する通知の仕組みとして「Snackbar」「Toast」「Dialog」などが用意されています。

コンポーネント 主な用途
Snackbar ユーザーの行動を阻害せず、自動的に消える(メールを送信しました、など)
Toast Snackbarが使えない場合(画面が閉じたあとにメッセージを表示する、など)
Dialog ユーザーに強制的に行動させる(規約の同意、などユーザーになんらかのアクションを求める)

SnackbarやToastはシンプルなAPIを持っているのですが、Dialogの代表格であるAlertDialogには、ユーザーに選択を促す場合以外にも自由度の高い画面を作ることのできる機能があります。この記事では、サンプルコードとともにAlertDialog.Builderクラスを使って用途別にダイアログを構成する情報を設定する方法、さまざまなカスタマイズを行う方法について説明します。

本記事では、macOS Catalina(10.15.2)/Android Studio 3.5.3/Kotlin 1.3.61/BuildToolsVersion 29.0.2で動作確認を行っています。

AlertDialogの簡易的な使い方

それでは早速、AlertDialogを使って簡単なダイアログを表示してみましょう。

ダイアログといえば次のような画面をイメージするのではないでしょうか。

1
▲基本的なコンポーネントを指定したAlertDialog

AlertDialogは、それ自身を直接インスタンス化して使うこともできますが、多くの項目を設定できるため、Builderクラスを使って値を設定するやり方が一般的です。

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      setSupportActionBar(toolbar)

      setupViews()
  }

  private fun setupViews() {
    // TextViewがタップされたときにダイアログを表示
    showAlertDialogTextView.setOnClickListener {
      // BuilderからAlertDialogを作成
      val dialog = AlertDialog.Builder(this)
        .setTitle(R.string.title) // タイトル
        .setMessage(R.string.message) // メッセージ
        .setPositiveButton(R.string.ok) { dialog, which -> // OK
            Toast.makeText(context, "OKがタップされた", Toast.LENGTH_SHORT).show()
        }
        .create()
      // AlertDialogを表示
      dialog.show()
    }
}

上記のように、タイトル、メッセージ、そしてOKボタンを表示させるダイアログは、Builderクラスのそれぞれのsetterメソッドを使用して値を設定していきます。titleやmessageのように文字列を設定するメソッドについては、文字列リソースのIDを指定しますが、通常の文字列(String)を指定することもできます。文字列リソースはリソースフォルダ以下のXMLファイルに定義します。

<resources>
  <string name="title">タイトル</string>
  <string name="message">メッセージ</string>
  <string name="ok">OK</string>

  <!-- 省略 -->
</resources>

またボタンについては、表示する文字列(文字列リソース)とタップしたときの処理(OnClickListener)を設定します。

この例ではAlertDialogの作成(create)と表示(show)を別々に行っていますが、Builderのshowメソッドを使うことで、生成と表示をまとめて実行できます。

ダイアログを構成する3つの要素

さて、ダイアログは3つの要素から成り立っています。「タイトル」「コンテンツ領域」「アクションボタン」です。ダイアログの使いみちとして、ユーザーに追加情報の入力を促す(コンテンツ領域)、ユーザーへ意思決定を促す(アクションボタン)などがありますが、目的に応じて何をどのように表示させるか、ユーザーの使い勝手をよく考えてデザインするようにしてください。

要素 概要
タイトル ダイアログのタイトル。コンテンツ領域がリストの場合や複雑な内容を表示している場合に指定する。省略可
コンテンツ領域 単純な文字列、リスト、カスタムビューなどが表示される
アクションボタン ユーザーからのアクションを受け取るためのボタンが表示される。最大3つ

DialogFragmentをダイアログのコンテナとして使用する

AlertDialogでは、単独でshow()メソッドを呼び出してやれば、ダイアログを表示できます。これは動作確認を行う上ではとても楽ちんなのですが、画面の回転と合わせてダイアログも回転させたい場合などには、Activityとの間にDialogFragmentを導入する必要があります。

FragmentはActivityに複数配置できる、いわば「断片的なActivity」で、UIと振る舞いを記述できます。Activityは、startActivityメソッドで表示を開始しますが、具体的に画面を作成する(onCreate)、画面に表示される(onResume)、一時停止状態になる(onPause)、破棄される(onDestroy)などのメソッドをオーバーライドすることで、それぞれのタイミングで行いたい処理を記述します。Fragmentも同様のメソッドを持っており、Activityと連動してそれぞれのタイミングで必要な処理を実装します。ダイアログを表示するときに、これらのタイミングに合わせてダイアログの表示を管理する場合には、DialogFragmentを用いることになります。

では、具体的な例も見てみましょう。

// DialogFragmentを継承したクラス
class SimpleAlertDialogFragment : DialogFragment() {

  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
    // AlertDialogの作成
    AlertDialog.Builder(requireContext())
      .setTitle(R.string.title)
      .setMessage(R.string.message)
      .setIcon(android.R.drawable.btn_star_big_on)
      .setPositiveButton(R.string.ok) { dialog, which ->
          // ボタンがタップされたときの処理
          Toast.makeText(context, "OKがタップされた", Toast.LENGTH_SHORT).show()
      }
      .create()
}

DialogFragmentをダイアログのコンテナとして利用する場合には、DialogFragment#onCreateDialog()メソッドでAlertDialog.Builderによって作成したDialog(AlertDialog)のインスタンスを返すようにするだけです。上のサンプルでも、DialogFragmentを継承したクラスを作成しています。

DialogFragmentはAndroidXに含まれるもの、またはサポートライブラリに含まれるものを使用してください。標準ライブラリに含まれるandroid.app.DialogFragmentは現在は非推奨となっています。

DialogFragmentを継承したクラスを使ってダイアログを表示するには、DialogFragment#show()メソッドに、FragmentActivity#getSupportFragmentManager()で取得したFragmentManagerとタグを渡します。

class MainActivity : AppCompatActivity() {

  // 省略
  private fun setupViews() {
    showAlertDialogTextView.setOnClickListener {
      // DialogFragment#show()メソッドでダイアログを表示
      SimpleAlertDialogFragment().apply {
        show(supportFragmentManager, "SimpleAlertDialogFragment")
      }
    }
    // 省略
  }
}

アクションボタン

AlertDialogでは、アクションボタンとして最大で3つまで表示することができます。それらは肯定(Positive)、否定(Negative)、中立(Neutral)の3つで、ダイアログの役割である「ユーザーによる意思決定」を取得するために活用されています。

2
▲3つのボタンを表示

ボタンを表示させ、それぞれのボタンがタップされた時に何かの処理を行うには、次のように記述します。

class ButtonsAlertDialogFragment : DialogFragment() {

  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
    AlertDialog.Builder(requireContext())
      .setTitle(R.string.title)
      .setMessage(R.string.message) // あなたは毎朝朝食を食べますか?
      .setPositiveButton(R.string.yes) { dialog, which ->
        // Positiveボタンがタップされたときに実行される処理
        Toast.makeText(context, "「はい」がタップされた", Toast.LENGTH_SHORT).show()
      }
      .setNegativeButton(R.string.no) { dialog, which ->
        // Negativeボタンがタップされたときに実行される処理
        Toast.makeText(context, "「いいえ」がタップされた", Toast.LENGTH_SHORT).show()
      }
      .setNeutralButton(R.string.no_answer) { dialog, which ->
        // Neutralボタンがタップされたときに実行される処理
        Toast.makeText(context, "「無回答」がタップされた", Toast.LENGTH_SHORT).show()
      }
      .create()
}

各ボタンを設定するメソッドの2番目の引数には、DialogInterface.OnClickListenerの実装インスタンスが渡されます。ここでは、Kotlinの記法で簡略化していますが、これを簡略化せずに記述すると次のようになります。

AlertDialog.Builder(context)
    // 省略
    .setPositiveButton(R.string.yes, object : DialogInterface.OnClickListener {
      override fun onClick(dialog: DialogInterface?, which: Int) {
        Toast.makeText(context, "「はい」がタップされた", Toast.LENGTH_SHORT).show()
      }
    })

ボタンは表示させたいが何も特別な処理は記述しないという場合には、setXxxButtonの2番めの引数(OnClickListenerのインスタンス)としてnullを渡します。

リストの表示

AndroidのライブラリにはDatePickerDialogという日付を選択するためのダイアログが標準で用意されていますが、その他にも、何らかのリストから1つ(または複数)の値を選択するというのは、よくあるダイアログの使いみちです。AlertDialogではこれを実現するための仕組みが標準で備わっています。

後述するカスタムビューを使えばどのような形式でも扱うことは可能ですが、定型的なリストであれば、以下のようなソースを利用するのが簡単です。

種別 概要
Arraysリソース XMLで定義される配列リソース
文字列型の配列 CharSequence型の配列
ListAdapter ListView(内部で利用している)のリスト管理用アダプタ
Cursor SQLiteなどのデータベース・カーソルオブジェクト

▲リストとして表示可能なリソース種別

シンプルなリスト表示

最初に例として文字列の配列を表示し、どれかをタップしたらダイアログを閉じる方法について紹介します。

3
▲文字型の配列を指定して一覧から選択させる

やり方はシンプルで、文字列の配列とOnClickListenerをsetItemsに渡します。配列の項目が選択された場合には、whichには配列のインデックス値が渡されますので、それを用いてどれが選択されたかを判定できます。

class ItemsAlertDialogFragment : DialogFragment() {

  companion object {
    // リストに表示する値を配列として定義
    val GENDERS = arrayOf("男性", "女性", "無回答")
  }

  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
    AlertDialog.Builder(requireContext())
      .setTitle(R.string.title) // 性別を選択してください
      .setItems(GENDERS) { dialog, which ->
        // GENDERSのリストのどれかが選択されたときに実行される処理
        // whichはGENDERS配列の選択されたインデックス
        Toast.makeText(context, "${GENDERS[which]} が選択されました", Toast.LENGTH_SHORT).show()
      }
      .setNegativeButton(R.string.close, null)
      .create()
}

リストの表示(1つだけ選択)

次に、項目をラジオボタンで選択状態にし、アクションボタンで選択したものを確定させるリストを表示する方法について紹介します。

4
▲配列から1つだけ選択可能なリストを表示

先ほどのsetItemsと似ていますが、setSingleChoiseItemsを使います。第1引数、第3引数はsetItemsと同じですが、第2引数としてint型の値を受け取ります。これはダイアログを開いた際に特定の項目を選択状態としたい場合に利用します。デフォルトで何も選択状態にしない場合には-1を指定します。

class SingleChoiceAlertDialogFragment : DialogFragment() {

  companion object {
    val GENDERS = arrayOf("男性", "女性", "無回答")
  }

  // 選択された値を保持しておくための変数
  private var chosenGender: String? = null

  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    chosenGender = null

    return AlertDialog.Builder(requireContext())
      .setTitle(R.string.title)
      .setSingleChoiceItems(GENDERS, -1) { dialog, which ->
        // GENDERSのリストのどれかが選択されたときに実行される処理
        // whichはGENDERS配列の選択されたインデックス
        Toast.makeText(context, "${GENDERS[which]} が選択されました", Toast.LENGTH_SHORT).show()
        // 選択された値をchosenGenderに設定
        chosenGender = GENDERS[which]
      }
      .setPositiveButton(R.string.ok) { dialog, which ->
        // Positiveボタンがタップされたときに実行される処理
        if (chosenGender == null) {
          // 何も選択されていない場合
          Toast.makeText(context, "性別は選択されていません", Toast.LENGTH_SHORT).show()
        } else {
          // 何らかの値が選択されている場合
          Toast.makeText(context, "選択された性別は $chosenGender です", Toast.LENGTH_SHORT).show()
        }
      }
      .setNegativeButton(R.string.close, null)
      .create()
  }
}

リストの表示(複数選択)

リスト表示の最後に紹介するのは、項目を複数選択可能にするためのメソッドです。画面上では項目はチェックボックスで表されています。

5
▲配列から複数選択可能なリストを表示

名前から想像のつくとおり、setMultiChoiceItemsを使います。第1引数は他と同様ですが、第2引数としてboolean型の配列を、第3引数には OnMultiChoiceClickListenerを受け取ります。最初からチェックマークを付けておくには第2引数に対応するインデックスの値をtrueにしておいた配列を渡します。nullが渡された場合にはすべて未チェックマークの状態になります。

class MultiChoiceAlertDialogFragment : DialogFragment() {

  companion object {
    val DRINKS = arrayOf("水", "牛乳", "オレンジジュース", "コーヒー", "紅茶", "その他")
  }

  // 配列の要素が選択されたかどうかを保持するためのBoolean配列
  private var chosenDrinks: Array<Boolean> = emptyArray()

  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    // すべてfalseとして初期化
    chosenDrinks = (1..DRINKS.size).map { false }.toTypedArray()

    return AlertDialog.Builder(requireContext())
      .setTitle(R.string.title) // 朝食に飲む飲み物をを選択してください(複数選択可)
      .setMultiChoiceItems(DRINKS, null) { dialog, which, isChecked ->
        if (isChecked) {
          // whichで指定されたアイテムは選択された
          Toast.makeText(context, "${DRINKS[which]} が選択されました", Toast.LENGTH_SHORT).show()
        } else {
          // whichで指定されたアイテムは選択解除された
          Toast.makeText(context, "${DRINKS[which]} が選択解除されました", Toast.LENGTH_SHORT).show()
        }
        // タップした値が選択されたかどうかを設定
        chosenDrinks[which] = isChecked
      }
      .setPositiveButton(R.string.ok) { dialog, which ->
        // mapIndexedによりisChecked=trueの場合の値をDRINKSから取得
        val drinks = chosenDrinks.mapIndexed { index, isChecked ->
          when (isChecked) {
            true -> DRINKS[index]
            else -> null
          }
        }
          .filterNotNull() // isChecked=falseの場合にはnullなのでここで除外
          .joinToString(",") // 選択された値をカンマで連結

        if (drinks.isEmpty()) {
          // 空文字列の場合には選択された値がない
          Toast.makeText(context, "飲み物は選択されていません", Toast.LENGTH_SHORT).show()
        } else {
          // カンマ区切りで選択された値を表示
          Toast.makeText(context, "選択された飲み物は、$drinks です", Toast.LENGTH_SHORT).show()
        }
      }
      .setNegativeButton(R.string.close, null)
      .create()
  }
}

ダイアログを閉じる

エンジニアHubに会員登録すると
続きをお読みいただけます(無料)。
登録のメリット
  • すべての過去記事を読める
  • 過去のウェビナー動画を
    視聴できる
  • 企業やエージェントから
    スカウトが届く