概要
SingleChoiceItems
をカスタマイズして、特定の条件のときに任意の要素を disable にできるリストを内包したダイアログを実装したので、公開しました
経緯
案件で「特定の条件のときにリストの要素を disable にしてユーザーが選択できないようにするダイアログ」が必要になりました。
よくある要件に思えたのでググってみたんですが、意外なことにいいサンプルが見つからなかったので、自分で実装してみました。
ソースコード
CustomSingleChoiceItemDialogFragment
SingleChoiceItems
を内包した DialogFragment
です。
以下にソースコードを転記して、要所だけコメントを追記しました。
package com.macneko.customsinglechoiceitemdialog.view import android.app.AlertDialog import android.os.Bundle import androidx.fragment.app.DialogFragment import com.macneko.customsinglechoiceitemdialog.adapter.CustomAdapter class CustomSingleChoiceItemDialogFragment : DialogFragment() { private lateinit var title: String // ダイアログのタイトル private lateinit var entries: List<String> // リストに表示する配列 private var entryIndex = 0 // リストで選択状態にする index。`-1` で選択なし private var disableIndex = -1 // リストで disable 状態にする index。`-1` で disable なし private lateinit var adapter: CustomAdapter // Adapter private lateinit var requestKey: String // 呼び出し元が `setFragmentResultListener` で選択結果を受け取るときの Key override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.run { title = getString(PARAM_TITLE) ?: throw IllegalArgumentException("arg is null") entries = getStringArrayList(PARAM_ENTRIES) ?: throw IllegalArgumentException( "arg is null" ) entryIndex = getInt(PARAM_ENTRY_INDEX) disableIndex = getInt(PARAM_DISABLE_INDEX, -1) requestKey = getString(PARAM_REQUEST_KEY) ?: throw IllegalArgumentException("arg is null") } adapter = CustomAdapter(entries, disableIndex) } override fun onCreateDialog(savedInstanceState: Bundle?) = AlertDialog.Builder(context).apply { setTitle(title) setSingleChoiceItems(adapter, entryIndex) { _, selectedIndex -> val bundle = Bundle().apply { putInt(RESULT_KEY, selectedIndex) // 選択した index を bundle に詰める } parentFragmentManager.setFragmentResult(requestKey, bundle) // `setFragmentResult` で結果を返す dismiss() } setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() } }.create() ?: super.onCreateDialog(savedInstanceState) companion object { private const val PARAM_TITLE = "param_title" private const val PARAM_ENTRIES = "param_entries" private const val PARAM_ENTRY_INDEX = "param_entry_index" private const val PARAM_DISABLE_INDEX = "param_disable_index" private const val PARAM_REQUEST_KEY = "PARAM_REQUEST_KEY" const val RESULT_KEY = "result_key" fun newInstance( title: String, entries: List<String>, entryIndex: Int, disableIndex: Int = -1, requestKey: String ) = CustomSingleChoiceItemDialogFragment().apply { arguments = Bundle().apply { putString(PARAM_TITLE, title) putStringArrayList(PARAM_ENTRIES, ArrayList(entries)) putInt(PARAM_ENTRY_INDEX, entryIndex) putInt(PARAM_DISABLE_INDEX, disableIndex) putString(PARAM_REQUEST_KEY, requestKey) } } } }
CustomAdapter
特定の要素を disable にする BaseAdapter
のサブクラスです。
以下にソースコードを転記して、要所だけコメントを追記しました。
package com.macneko.customsinglechoiceitemdialog.adapter import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.TextView class CustomAdapter( private val items: List<String>, private val disableIndex: Int // コンストラクタで disable にする index を受け取る ) : BaseAdapter() { override fun getCount() = items.size override fun getItem(position: Int) = items[position] override fun getItemId(position: Int): Long = 0 override fun isEnabled(position: Int) = position != disableIndex override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { val view: View val holder: ViewHolder if (convertView == null) { view = LayoutInflater.from(parent?.context) .inflate(android.R.layout.simple_list_item_single_choice, parent, false) // `android.R.layout.simple_list_item_single_choice` は SimpleChoiceItems のレイアウトXML holder = ViewHolder() holder.textView = view.findViewById<View>(android.R.id.text1) as TextView view.tag = holder } else { view = convertView holder = view.tag as ViewHolder } setViewItems(position, holder) return view } private fun setViewItems(position: Int, holder: ViewHolder) { holder.textView?.apply { text = getItem(position) // disableIndex と一致したら `isEnabled = false` になる isEnabled = isEnabled(position) } } private class ViewHolder { var textView: TextView? = null } }
プロジェクトへの導入方法
CustomSingleChoiceItemDialogFragment
とCustomAdapter
をプロジェクトに追加するActivity
などのダイアログを表示するクラスにダイアログを表示するコードを書く
val dialog = CustomSingleChoiceItemDialogFragment.newInstance( "title", // ダイアログのタイトル ["First", "Second", "Third"], // リストに表示する値の配列 0, // リストで選択状態にする index 1, // リストで disable 状態にする index。`-1` で disable なし "REQUEST_KEY_CUSTOM" // setFragmentResultListener で選択結果を受け取るときの Key ) dialog.show(supportFragmentManager, "CustomSingleChoiceItemDialogFragment")
- 2 のクラスの onCreate にダイアログの選択結果を受け取るコードを書く
supportFragmentManager.setFragmentResultListener("REQUEST_KEY_CUSTOM", this) { _, bundle -> Log.d("sample", bundle.getInt(CustomSingleChoiceItemDialogFragment.RESULT_KEY, -1)) // bundle に選択した index が入っているので、取り出してログに出力する }
サンプルアプリケーション
ソースコード
以下のリポジトリで公開しています
https://github.com/macneko-ayu/CustomSingleChoiceItemDialog
デモ動画
画面の説明
合番 | 説明 |
---|---|
1 | 4 をタップして表示されたダイアログで選択した要素を表示します |
2 | disable にする要素を選択するダイアログを表示します |
3 | 2 で disable にした要素の position を表示します |
4 | 2 で選択した要素を disable にしたダイアログを表示します |
実装してみて思ったこと
今回初めて使った SingleChoiceItems
ですが、簡単にリスト選択型のダイアログが作れて便利ですね。