Androidのダイアログの実装って面倒ですよね。簡単なメッセージとOKボタンを表示したいだけなのに、DialogFragment
をのサブクラスを実装しないといけません。アプリのあちこちでダイアログを使っていると、似たような実装があちこちにできてしまいがちです。
そこで今回は、シンプルな(タイトル・メッセージ・ボタン2つ)ダイアログを、簡単に(いちいちサブクラスを作ることなく)実装できる、汎用的なDialogFragment
を作りました。
概要
クラス名:SimpleDialog
SimpleDialog
クラスは、ダイアログのタイトル、メッセージ、Positive/Negativeボタンのテキストを指定できるダイアログです。
SimpleDialog.Builder
クラスを使ってこれらのパラメータを設定し、SimpleDialog.Builder#create()
関数でSimpleDialog
のインスタンスを作成します。あとは通常のDialogFragment
と同じようにshow()
関数で表示します。
呼び出し側のActivity
またはFragment
がSimpleDialog.ResultListener
を実装していれば、Positive/Negativeボタンを押したときにSimpleDialog.ResultListener#onSimpleDialogResult()
が呼び出されるので、結果を取得することができます。
使い方
では具体的な使い方を説明します。
Activity
では、SimpleDialog.Builder()
でBuilder
クラスを取得し、setXXX()
関数でパラメータを設定していきます。setXXX()
関数は、引数に文字列を直接指定するか、リソースIDを指定することができます。また、必要なパラメータだけ設定すればOKです。例えばsetNegativeText()
を省略した場合、OKボタン一つだけのダイアログになります。
一通りパラメータを設定したら、create()
でSimpleDialog
のインスタンスを取得し、show()
で表示します。show()
はDialogFragment
の関数をそのまま継承しています。
ボタンクリックの結果を受け取るには、Activity
にSimpleDialog.ResultListener
を実装します。Positive/Negativeボタンがクリックされると、onSimpleDialogResult()
が呼び出されます。第一引数のtag
には、show()
の第二引数で指定したtag
が渡されます。一つのActivity
で複数のダイアログを使い分ける場合は、tag
で区別します。第二引数のresult
は、Positive/Negativeどちらのボタンがクリックされたかを示します。
class MyActivity : AppCompatActivity(), SimpleDialog.ResultListener {
private fun showDialog() {
SimpleDialog.Builder()
.setTitle("Title") // setTitle(R.string.dialog_title)のようにリソースIDの指定も可。
.setMessage("Message")
.setPositiveText("OK")
.setNegativeText("CANCEL")
.create()
.show(supportFragmentManager, "SimpleDialog") // FragmentではchildFragmentManagerを指定する。
}
override fun onSimpleDialogResult(tag: String?, result: SimpleDialog.Result) {
if (tag == "SimpleDialog") {
when (result) {
SimpleDialog.Result.POSITIVE -> { /* OK button is clicked */ }
SimpleDialog.Result.NEGATIVE -> { /* CANCEL button is clicked */ }
}
}
}
このように、呼び出し側では一文完結でダイアログを表示することができます。また、特にリスナーの設定などしなくても、インターフェースの実装だけすればボタンクリック時の結果を受け取ることができます。
また上記の例はActivity
で表示していますが、Fragment
からも同様に表示することができます。その場合は、show()
の第一引数をsupportFragmentManager
ではなくchildFragmentManager
にしてください。
実装
次にSimpleDialog
クラスの実装を説明します。全体のソースコードはこのページの最後に載せています。
まずはBuilder
クラスの実装です。Builderクラスの役割は、SimpleDialog
のarguments
に渡すbundle
オブジェクトを作成することです。setXXX()
関数それぞれでbundle
オブジェクトにパラメータを設定しています。そしてcreate()
でSimpleDialog
オブジェクトを作成し、arguments
にbundle
を設定しています。こうすることによってarguments
に各パラメータが保存されるので、画面が回転したりしてダイアログが破棄された場合でも、パラメータを維持することができます。
各set関数は、引数にString
を取るものとInt
を取るものをそれぞれ用意しています。これは、文字列を直接指定することも、リソースIDで指定することもできるようにするためです。
class Builder {
private val bundle = Bundle()
fun setTitle(title: String) = this.apply {
bundle.putString(KEY_TITLE, title)
}
fun setTitle(titleId: Int) = this.apply {
bundle.putInt(KEY_TITLE_ID, titleId)
}
fun setMessage(message: String) = this.apply {
bundle.putString(KEY_MESSAGE, message)
}
fun setMessage(messageId: Int) = this.apply {
bundle.putInt(KEY_MESSAGE_ID, messageId)
}
fun setPositiveText(text: String) = this.apply {
bundle.putString(KEY_POSITIVE_TEXT, text)
}
fun setPositiveText(textId: Int) = this.apply {
bundle.putInt(KEY_POSITIVE_TEXT_ID, textId)
}
fun setNegativeText(text: String) = this.apply {
bundle.putString(KEY_NEGATIVE_TEXT, text)
}
fun setNegativeText(textId: Int) = this.apply {
bundle.putInt(KEY_NEGATIVE_TEXT_ID, textId)
}
fun create() = SimpleDialog().apply {
arguments = bundle
}
}
インターフェースResultListener
には、onSimpleDialogResult()
関数が一つだけ定義してあります。
interface ResultListener {
fun onSimpleDialogResult(tag: String?, result: Result)
}
次はSimpleDialog
本体です。DialogFragment
を継承し、onAttach()
とonCreateDialog()
をoverrideしています。
onAttach()
では、ボタンクリック時のイベント送信先を判定しています。Activity
からDialogFragment#show()
を呼び出した場合、DialogFragment
から見るとcontext
がActivity
になります。Fragment
からDialogFragment#show()
をchildFragmentManager
を指定して呼び出した場合、DialogFragment
から見るとparentFragment
が呼び出し側のFragment
になります。onAttach()
では、これらがResultListener
を実装しているかどうかを見ています。実装していればlistener
として登録しています。このようにonAttach()
を実装しているので、呼び出し側でリスナーの登録が不要になります。また、Activity
でもFragment
でも同じように使うことができるようになります。
class SimpleDialog : DialogFragment() {
private var listener: ResultListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
listener = when {
context is ResultListener -> context
parentFragment is ResultListener -> parentFragment as ResultListener
else -> null
}
}
}
残るはonCreateDialog()
です。
若干長いですが、やっていることはシンプルで、SimpleDialog.Builder
が設定したarguments
から各パラメータを取り出して設定しているだけです。ただ、ここのbuilder
はAlertDialog.Builder
なので、混乱しないようにしてください。
リソースIDが指定されていればリソースIDを、指定されていなければ文字列を設定しています。どちらも指定されていない場合、文字列を指定する方のset関数の引数がnullになり、そのパラメータは表示されないことになります。Positive/Negativeボタンを押したときは、onAttach()
で登録したlisetner
に対してonSimpleDialogResult()
を呼び出しています。
class SimpleDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = activity?.let { AlertDialog.Builder(it) }
?: throw IllegalStateException("Activity is null when onCreateDialog() is called.")
/* Title */
val titleId = arguments?.getInt(KEY_TITLE_ID) ?: 0
if (titleId != 0) {
builder.setTitle(titleId)
} else {
builder.setTitle(arguments?.getString(KEY_TITLE))
}
/* Message */
val messageId = arguments?.getInt(KEY_MESSAGE_ID) ?: 0
if (messageId != 0) {
builder.setMessage(messageId)
} else {
builder.setMessage(arguments?.getString(KEY_MESSAGE))
}
/* Positive Button */
val positiveTextId = arguments?.getInt(KEY_POSITIVE_TEXT_ID) ?: 0
if (positiveTextId != 0) {
builder.setPositiveButton(positiveTextId) { _,_ ->
listener?.onSimpleDialogResult(tag, Result.POSITIVE)
}
} else {
builder.setPositiveButton(arguments?.getString(KEY_POSITIVE_TEXT)) { _,_ ->
listener?.onSimpleDialogResult(tag, Result.POSITIVE)
}
}
/* Negative Button */
val negativeTextId = arguments?.getInt(KEY_NEGATIVE_TEXT_ID) ?: 0
if (negativeTextId != 0) {
builder.setNegativeButton(negativeTextId) { _,_ ->
listener?.onSimpleDialogResult(tag, Result.NEGATIVE)
}
} else {
builder.setNegativeButton(arguments?.getString(KEY_NEGATIVE_TEXT)) { _,_ ->
listener?.onSimpleDialogResult(tag, Result.NEGATIVE)
}
}
return builder.create()
}
}
ソースコード全体
class SimpleDialog : DialogFragment() {
interface ResultListener {
fun onSimpleDialogResult(tag: String?, result: Result)
}
enum class Result {
POSITIVE,
NEGATIVE
}
class Builder {
private val bundle = Bundle()
fun setTitle(title: String) = this.apply {
bundle.putString(KEY_TITLE, title)
}
fun setTitle(titleId: Int) = this.apply {
bundle.putInt(KEY_TITLE_ID, titleId)
}
fun setMessage(message: String) = this.apply {
bundle.putString(KEY_MESSAGE, message)
}
fun setMessage(messageId: Int) = this.apply {
bundle.putInt(KEY_MESSAGE_ID, messageId)
}
fun setPositiveText(text: String) = this.apply {
bundle.putString(KEY_POSITIVE_TEXT, text)
}
fun setPositiveText(textId: Int) = this.apply {
bundle.putInt(KEY_POSITIVE_TEXT_ID, textId)
}
fun setNegativeText(text: String) = this.apply {
bundle.putString(KEY_NEGATIVE_TEXT, text)
}
fun setNegativeText(textId: Int) = this.apply {
bundle.putInt(KEY_NEGATIVE_TEXT_ID, textId)
}
fun create() = SimpleDialog().apply {
arguments = bundle
}
}
companion object {
private const val KEY_TITLE = "KeyTitle"
private const val KEY_TITLE_ID = "KeyTitleId"
private const val KEY_MESSAGE = "KeyMessage"
private const val KEY_MESSAGE_ID = "KeyMessageId"
private const val KEY_POSITIVE_TEXT = "KeyPositiveText"
private const val KEY_POSITIVE_TEXT_ID = "KeyPositiveTextId"
private const val KEY_NEGATIVE_TEXT = "KeyNegativeText"
private const val KEY_NEGATIVE_TEXT_ID = "KeyNegativeTextId"
}
private var listener: ResultListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
listener = when {
context is ResultListener -> context
parentFragment is ResultListener -> parentFragment as ResultListener
else -> null
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = activity?.let { AlertDialog.Builder(it) }
?: throw IllegalStateException("Activity is null when onCreateDialog() is called.")
/* Title */
val titleId = arguments?.getInt(KEY_TITLE_ID) ?: 0
if (titleId != 0) {
builder.setTitle(titleId)
} else {
builder.setTitle(arguments?.getString(KEY_TITLE))
}
/* Message */
val messageId = arguments?.getInt(KEY_MESSAGE_ID) ?: 0
if (messageId != 0) {
builder.setMessage(messageId)
} else {
builder.setMessage(arguments?.getString(KEY_MESSAGE))
}
/* Positive Button */
val positiveTextId = arguments?.getInt(KEY_POSITIVE_TEXT_ID) ?: 0
if (positiveTextId != 0) {
builder.setPositiveButton(positiveTextId) { _,_ ->
listener?.onSimpleDialogResult(tag, Result.POSITIVE)
}
} else {
builder.setPositiveButton(arguments?.getString(KEY_POSITIVE_TEXT)) { _,_ ->
listener?.onSimpleDialogResult(tag, Result.POSITIVE)
}
}
/* Negative Button */
val negativeTextId = arguments?.getInt(KEY_NEGATIVE_TEXT_ID) ?: 0
if (negativeTextId != 0) {
builder.setNegativeButton(negativeTextId) { _,_ ->
listener?.onSimpleDialogResult(tag, Result.NEGATIVE)
}
} else {
builder.setNegativeButton(arguments?.getString(KEY_NEGATIVE_TEXT)) { _,_ ->
listener?.onSimpleDialogResult(tag, Result.NEGATIVE)
}
}
return builder.create()
}
}