"Simple" is "Best"

開発時に詰まったところや 調べた結果日本語での情報が無かったり古かったりした場合に自分用のメモとして高校生(?)が更新していくブログ

Dialogと仲良くなる その1

先日、RecyclerViewと仲良くなった(気がする)

次はDialogと仲良くなってみる

参考サイト

ひたすら公式

Dialogs - Android Developers

Dialogs - Material Design

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>

完成図 f:id:bigbuddha:20181007044439p:plain

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 f:id:bigbuddha:20181007044442p:plain

ただ文字を表示するだけ、正直使いどころはない

でも、基本を学ぶにはシンプルでちょうどいい

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)に文字列をセットする

f:id:bigbuddha:20181007044442p:plain

これはデザイン的な話になるが

GoogleはTitle部分に曖昧な文言を設定することを推奨していない

例えば”Are you sure?”である

なるべく読み取れる意味を絞れってはなしですな

Button Dialog

公式的には”Alert Dialog”らしい

右下にボタンがついてるDialog

f:id:bigbuddha:20181007044435p:plain

ココらへんから使いどころがでてくるかな

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がでてくるというもの

f:id:bigbuddha:20181007044428p:plain

たまに”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

f:id:bigbuddha:20181007044431p:plain

タップした内容を取得できる

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

f:id:bigbuddha:20181007044428p:plain

RadioBox Dialog

公式的には”Confirmation Dialog”

リファレンスを見ると、setSingleChoiceItems()を利用しているので

Single Choice Dialogっていうのが一番正しい名前なのかね

f:id:bigbuddha:20181007044418p:plain

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を設定すると標準で無選択になる

f:id:bigbuddha:20181007044414p:plain

CheckBox Dialog

本記事最後のDialog、公式的には”Confirmation Dialog”

さっきのがsetSingleChoiceItems()だったのに対し、これはsetMultiChoiceItems()を使う

う〜ん、わかりやすい

f:id:bigbuddha:20181007044425p:plain

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()に出会うとは、、、(やぎにぃに感謝)

f:id:bigbuddha:20181007044422p:plain

さぁ、これで全てが出揃った

個人的にはDialogととりあえずは仲良くなれたように思える

あとはFull-ScreenとCustomとPicker、、、

ま、のんびりやっていきます

今回のコードのまとめはこちらから見ることができます

意味わかんねぇよとか、ここエラーでたんですけどとかあったらTwitterでメンション投げてください

@OJI_1941です(ごめんなさいDM閉じてるんで最初はメンションでお願いします)

ありがとうございました