ためしにDialogFragmentを使いやすくするようなラッパーを作ってみました。
DialogFragmentの基本的な動作についてはこちらから。
DialogFragmentでは、ただそれを継承したクラスのプロパティに設定したい項目を設定しても、スマホを縦横に動かしたときに再生成されて消えてしまう。そのため少なくともonCreate時にデータを渡してあげないと行けないという面倒なことがあるというのが基本的な動作のおさらい。
またDialogFragmentではコンストラクタに引数を設定できない仕様になっているので、コンストラクタで引数を渡すことができない。なのでBuilderパターンを利用してインスタンスを生成し、そこの引数で処理を振り分ける必要がある。
このように普通にプロパティを設定してデータを受け渡すだけではスマホを縦横移動させた時にメンバ変数が消えてしまう。強引な解決方法としては、DialogFragmentを継承したクラスにcompanion objectを設定して、いわゆるstatic変数を設定することだが、これだと同じクラスを複数で使うときに同じ挙動になってしまうので意味がないので、static変数を使うのはあまりいい方法ではない。
なら新しいクラスを作り、そこに設定させる情報を集約してBundleを利用して渡してしまえばいいのでは?ということで参考に作成してみたのが以下のソース。
詳しい流れはこのような感じ。
TestDialogクラスはDialogFragmentを継承しているクラスなので、このクラスのonCreateDialogメソッドでAlertDialogを生成すればいろいろな問題を勝手に解決してくれる。
TestDialogには内部クラスDataStoreがあり、このクラスにAlertDialogで設定する内容を保存してTestDialogに戻してあげることで設定した内容を反映してくれる。このDataStoreクラスはBundleで渡されるのでスマホの縦横移動時にも対応してくれる。
具体的な使用方法はこのような感じ。
TestDialog.DataSore()はcompanion objectなのでこれを呼び出すとDataStoreクラスのインスタンスを生成してくれる。そのインスタンスにはAlertDialogの設定に関する設定内容やコールバックなどが含まれているのでそれを設定してあげる。
最後にTestDialog.Builder()をして引数に前述のDataStoreのインスタンスを渡してあげることでDataStoreに設定した内容に従ってダイアログを生成してくれる。
ここで設定できる内容はごく一部だが、下記のようにメソッドを拡張することで、より使い安いダイアログが作成できる。
なおAlertDialogにカスタムレイアウトを適用したいと思うけど、そのカスタムレイアウトのイベントを取得する方法がDialogFragmentにはない。そのため、ありかじめレイアウトのxmlファイルをviewにinflateしておき、そこからUI部品のインスタンスを取得しておき、そのインスタンスに関して操作してあげる必要がある。
AlertDialogにはsetView(R.layout.test_layout)のようにリソースから設定できるが、このようなやり方だとshow()するまでUI部品にアクセスすることができない。そのため、事前にLayoutInflaterでviewのインスタンスを生成しておき、そしてsetView(View)をしたほうがいい。
DialogFragmentの基本的な動作についてはこちらから。
DialogFragmentでは、ただそれを継承したクラスのプロパティに設定したい項目を設定しても、スマホを縦横に動かしたときに再生成されて消えてしまう。そのため少なくともonCreate時にデータを渡してあげないと行けないという面倒なことがあるというのが基本的な動作のおさらい。
またDialogFragmentではコンストラクタに引数を設定できない仕様になっているので、コンストラクタで引数を渡すことができない。なのでBuilderパターンを利用してインスタンスを生成し、そこの引数で処理を振り分ける必要がある。
このように普通にプロパティを設定してデータを受け渡すだけではスマホを縦横移動させた時にメンバ変数が消えてしまう。強引な解決方法としては、DialogFragmentを継承したクラスにcompanion objectを設定して、いわゆるstatic変数を設定することだが、これだと同じクラスを複数で使うときに同じ挙動になってしまうので意味がないので、static変数を使うのはあまりいい方法ではない。
なら新しいクラスを作り、そこに設定させる情報を集約してBundleを利用して渡してしまえばいいのでは?ということで参考に作成してみたのが以下のソース。
詳しい流れはこのような感じ。
TestDialogクラスはDialogFragmentを継承しているクラスなので、このクラスのonCreateDialogメソッドでAlertDialogを生成すればいろいろな問題を勝手に解決してくれる。
TestDialogには内部クラスDataStoreがあり、このクラスにAlertDialogで設定する内容を保存してTestDialogに戻してあげることで設定した内容を反映してくれる。このDataStoreクラスはBundleで渡されるのでスマホの縦横移動時にも対応してくれる。
具体的な使用方法はこのような感じ。
TestDialog.DataSore()はcompanion objectなのでこれを呼び出すとDataStoreクラスのインスタンスを生成してくれる。そのインスタンスにはAlertDialogの設定に関する設定内容やコールバックなどが含まれているのでそれを設定してあげる。
最後にTestDialog.Builder()をして引数に前述のDataStoreのインスタンスを渡してあげることでDataStoreに設定した内容に従ってダイアログを生成してくれる。
ここで設定できる内容はごく一部だが、下記のようにメソッドを拡張することで、より使い安いダイアログが作成できる。
なおAlertDialogにカスタムレイアウトを適用したいと思うけど、そのカスタムレイアウトのイベントを取得する方法がDialogFragmentにはない。そのため、ありかじめレイアウトのxmlファイルをviewにinflateしておき、そこからUI部品のインスタンスを取得しておき、そのインスタンスに関して操作してあげる必要がある。
AlertDialogにはsetView(R.layout.test_layout)のようにリソースから設定できるが、このようなやり方だとshow()するまでUI部品にアクセスすることができない。そのため、事前にLayoutInflaterでviewのインスタンスを生成しておき、そしてsetView(View)をしたほうがいい。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//まずはDataStoreのインスタンスを生成して、そこに設定したい内容を設定する
var dataStore: TestDialog.DataStore = TestDialog.DataStore().also {
//タイトルを設定する場合
it.setTitle("ぬるぬる")
//カスタムレイアウトを設定して、カスタムレイアウトに対するコールバックも設定できる
//ここでは別設定のxmlファイルを読み込んで、そこのbtnDialog01というボタンのインスタンスを取得
it.setView(R.layout.test_dialog_layout) { view ->
//viewにはカスタムレイアウトのviewが入っているのでfindViewByIdでIDからインスタンスを取得できる
view.findViewById<Button>(R.id.btnDialog01).setOnClickListener {
Log.v("nullpo", "レイアウトのボタンクリック")
}
}
//posotiveButtonのテキストとコールバックも設定できる
it.setPositiveButton("ボタン名") { dialog, witch ->
Log.v("nullpo", "positiveButtonクリック")
}
}
var dialog: TestDialog = TestDialog.Builder(dataStore)
btnOpenDialog.setOnClickListener {
//ボタンクリックでダイアログを表示 supportFragmentManagerなのに注意が必要
dialog.show(supportFragmentManager, "dialog")
}
}
}
class TestDialog() : DialogFragment() {
private var mDataStore: DataStore by Delegates.notNull<DataStore>()
companion object {
//Dialogの設定する情報が入っているDataStoreクラスのインスタンスを受け取る
fun Builder(dataStore: DataStore): TestDialog {
//DataStoreクラスをBundleでやりとりする
val bundle: Bundle = Bundle().also { it.putSerializable("DataStore", dataStore) }
val testDialog: TestDialog = TestDialog().also { it.arguments = bundle }
return testDialog
}
//DataStoreクラスのインスタンスを生成するBuilderパターン
fun DataStore(): DataStore = DataStore()
}
//ここで設定されたメンバ変数はスマホ縦横移動時にも記憶される
//なのでダイアログ情報を設定したクラスを取得してメンバ変数に保存しておく
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDataStore = arguments?.getSerializable("DataStore") as DataStore
}
//ここでAlertDialogに関する様々な設定を行う
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
//Builderの引数がnot nullなので!!をつける
val dialogBuilder = AlertDialog.Builder(activity!!)
//レイアウトファイルの設定があったらレイアウトのxmlを読んで、そのviewのインスタンスを所得するためにonAttachLayoutCallbackを呼ぶ
mDataStore.mLayoutResId?.let {
var view: View = LayoutInflater.from(activity).inflate(it, null)
mDataStore.mOnAttachLayoutCallBack?.invoke(view)
dialogBuilder.setView(view)
}
//タイトルとメッセージを設定
//変数がnullだったら設定しない
mDataStore.mTitle?.let { dialogBuilder.setTitle(it) }
mDataStore.mMessage?.let { dialogBuilder.setMessage(it) }
//item関連
mDataStore.mItemsText?.let { dialogBuilder.setItems(mDataStore.mItemsText, mDataStore.mItemsCallBack) }
mDataStore.mSingleChoiceText?.let { dialogBuilder.setSingleChoiceItems(mDataStore.mSingleChoiceText, mDataStore.mSingleCheckedItem!!, mDataStore.mSingleChoiceCallBack) }
mDataStore.mMultiChoiceText?.let { dialogBuilder.setMultiChoiceItems(mDataStore.mMultiChoiceText, mDataStore.mMultiChoiceItem, mDataStore.mMultiChoiceCallBack) }
//ボタン関連
mDataStore.mPositiveButtonText?.let { dialogBuilder.setPositiveButton(mDataStore.mPositiveButtonText, mDataStore.mPositiveButtonCallBack) }
mDataStore.mNegativeButtonText?.let { dialogBuilder.setNegativeButton(mDataStore.mNegativeButtonText, mDataStore.mNegativeButtonCallBack) }
mDataStore.mNeutralButtonText?.let { dialogBuilder.setNeutralButton(mDataStore.mNeutralButtonText, mDataStore.mNegativeButtonCallBack) }
//イベント関連
mDataStore.mOnCalcelCallBack?.let { dialogBuilder.setOnCancelListener(it) }
return dialogBuilder.create()
}
//AlertDialogに設定する内容を保存するクラス
//Bundleで受け渡しができるようにSerializableを継承しておく
class DataStore : Serializable {
//タイトルとメッセージ
//基本的にnullを設定しておく。nullじゃないときだけAlertDialogに設定するため
internal var mTitle: CharSequence? = null
internal var mMessage: CharSequence? = null
//アイテム
internal var mItemsText: Array<CharSequence>? = null
internal var mItemsCallBack: ((DialogInterface, Int) -> Unit)? = null
//RadioButton
internal var mSingleChoiceText: Array<CharSequence>? = null
internal var mSingleCheckedItem: Int? = null
internal var mSingleChoiceCallBack: ((DialogInterface, Int) -> Unit)? = null
//Checkbox
internal var mMultiChoiceText: Array<CharSequence>? = null
internal var mMultiChoiceItem: BooleanArray? = null
internal var mMultiChoiceCallBack: ((DialogInterface, Int, Boolean) -> Unit)? = null
//レイアウトファイル
internal var mLayoutResId: Int? = null
internal var mOnAttachLayoutCallBack: ((View) -> Unit)? = null
//イベント
internal var mOnCalcelCallBack: ((DialogInterface) -> Unit)? = null
//各種ボタン
internal var mPositiveButtonText: CharSequence? = null
internal var mPositiveButtonCallBack: ((DialogInterface, Int) -> Unit)? = null
internal var mNegativeButtonText: CharSequence? = null
internal var mNegativeButtonCallBack: ((DialogInterface, Int) -> Unit)? = null
internal var mNeutralButtonText: CharSequence? = null
internal var mNeutralButtonCallBack: ((DialogInterface, Int) -> Unit)? = null
//レイアウトxmlファイルを読んで、そのUI部品に対してイベントを設定できるようにするためコールバックする
fun setView(layoutResId: Int, listener: (View) -> Unit) {
mLayoutResId = layoutResId
mOnAttachLayoutCallBack = listener
}
fun setTitle(title: CharSequence) {
mTitle = title
}
fun setMessage(message: CharSequence) {
mMessage = message
}
fun setItems(items: Array<CharSequence>, listener: ((DialogInterface, Int) -> Unit)) {
mItemsText = items
mItemsCallBack = listener
}
fun setSingleChoiceItems(items: Array<CharSequence>, checkItem: Int?, listener: ((DialogInterface, Int) -> Unit)) {
mSingleChoiceText = items
mSingleCheckedItem = checkItem
mSingleChoiceCallBack = listener
}
fun setMultiChoiceItems(items: Array<CharSequence>, checkItem: BooleanArray, listener: ((DialogInterface, Int, Boolean) -> Unit)) {
mMultiChoiceText = items
mMultiChoiceItem = checkItem
mMultiChoiceCallBack = listener
}
fun setPositiveButton(message: CharSequence, listener: (DialogInterface, Int) -> Unit) {
mPositiveButtonText = message
mPositiveButtonCallBack = listener
}
fun setNegativeButton(message: CharSequence, listener: (DialogInterface, Int) -> Unit) {
mNegativeButtonText = message
mNegativeButtonCallBack = listener
}
fun setNeutralButton(messsage: CharSequence, listener: (DialogInterface, Int) -> Unit) {
mNeutralButtonText = messsage
mNeutralButtonCallBack = listener
}
fun setOnCancelListener(listener: (DialogInterface) -> Unit) {
mOnCalcelCallBack = listener
}
}
}