【Android】続・PreferenceFragmentCompatを使った設定画面
以下の記事を昔に書いたが、内容が古くなってきたようなので書き直す。
Androidの設定画面について
- AndroidではAPI1から設定画面を生成してくれる
PreferenceActivity
が用意されている - Android 3.0で各設定画面をFragmentに分けるための
PreferenceFragment
が追加された - Android 4.0では
PreferenceActivity
が拡張され、PreferenceFragment
と組み合わせることでハンドセットでは1画面、タブレットでは2画面の設定画面を自動的に作成できる - API 28で
PreferenceFragment
はdeprecatedになりPreferenceFragmentCompat
が推奨となった。PreferenceActivity
も使う必要はなく、AppCompatActivity
を使用する。
準備
PreferenceFragmentCompat
を使用するためにはサポートライブラリが必要。AndroidX版もあるがひとまず28.0.0で書く。
def supportLibVer = '28.0.0' implementation "com.android.support:appcompat-v7:$supportLibVer" implementation "com.android.support:preference-v7:$supportLibVer"
テレビ用には preference-leanback-v17
があるがここでは説明しない。
設定項目
クラス | 説明 |
---|---|
v7.preference.CheckBoxPreference | オン/オフの設定項目 |
v14.preference.SwitchPreference | オン/オフの設定項目 |
v7.preference.ListPreference | リストの中から1つ選ぶ設定項目 |
v14.preference.MultiSelectListPreference | リストの中から複数を選ぶ設定項目 |
v7.preference.EditTextPreference | 入力からの設定 |
設定画面
Activity
class SettingsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) supportFragmentManager .beginTransaction() .replace(android.R.id.content, SettingsFragment()) .commit() } }
PreferenceFragmentCompat
- onCreatePreferences() で
addPreferencesFromResource()
から設定画面を定義したXMLを読み込む - 必要であれば各設定項目の初期値等を設定する
- XMLで設定した
Preference
を取得するにはandroid:key
を使い、PreferenceFragmentCompat#findPreference()
で行う
class SettingFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.setting_fragment) } }
ヘッダー
<preference-header>
によるヘッダー作成から<Preference android:fragment="">
で指定するようになった。設定するだけでは遷移してくれず、 Activity
側で PreferenceFragmentCompat.OnPreferenceStartFragmentCallback
を実装して onPreferenceStartFragment()
でハンドリングしてあげる必要がある。
旧
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android"> <!-- These settings headers are only used on tablets. --> <header android:fragment="com.phicdy.preferencesample.GeneralPreferenceFragment" android:title="@string/pref_header_general" android:icon="@drawable/ic_info_black_24dp"/> <header android:fragment="com.phicdy.preferencesample.NotificationPreferenceFragment" android:title="@string/pref_header_notifications" android:icon="@drawable/ic_notifications_black_24dp"/> <header android:fragment="com.phicdy.preferencesample.DataSyncPreferenceFragment" android:title="@string/pref_header_data_sync" android:icon="@drawable/ic_sync_black_24dp"/> </preference-headers>
新
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <!-- These settings Preferences are only used on tablets. --> <Preference android:key="aaa" android:fragment="com.phicdy.preferencesample.GeneralPreferenceFragment" android:title="@string/pref_header_general" android:icon="@drawable/ic_info_black_24dp"/> <Preference android:key="bbb" android:fragment="com.phicdy.preferencesample.NotificationPreferenceFragment" android:title="@string/pref_header_notifications" android:icon="@drawable/ic_notifications_black_24dp"/> <Preference android:key="ccc" android:fragment="com.phicdy.preferencesample.DataSyncPreferenceFragment" android:title="@string/pref_header_data_sync" android:icon="@drawable/ic_sync_black_24dp"/> </PreferenceScreen>
設定画面でそんなに需要があるのかわからないが、切替時に処理を入れたり、アニメーションを入れることができる。
class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { // AndroidX版だとNon-Null override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference?): Boolean { // AndroidX版だとval fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment, args) で書ける val fragment = when (pref?.fragment) { GeneralPreferenceFragment::class.java.name -> GeneralPreferenceFragment() NotificationPreferenceFragment::class.java.name -> NotificationPreferenceFragment() DataSyncPreferenceFragment::class.java.name -> DataSyncPreferenceFragment() else -> throw InvalidParameterException("Invalid fragment, fragment name is " + pref?.fragment) } fragment.setTargetFragment(caller, 0) supportFragmentManager.beginTransaction() .replace(android.R.id.content, fragment) .addToBackStack(null) .commit() return true } }
PreferenceFragmentCompatから読み込む設定XML
PreferenceFragmentCompat
から読み込むXMLのトップはPreferenceScreen
でなければならない- 各
Preference
にはandroid:key
を設定する。これが各Preference
を特定するIDとなる。 - カテゴリを設定する場合は
PreferenceCategory
を使う。android:key
は必須ではない。 ListPreference
の場合、選択候補に表示されるリスト(android:entries
)と実際の値のリスト(android:entryValues
)を設定する必要がある
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > <PreferenceCategory android:title="Category Title" > <ListPreference android:title="Title" android:key="@string/key_list_preference" android:entries="@array/entry_sample" android:entryValues="@array/entry_sample_values"/> </PreferenceCategory> </PreferenceScreen>
その他の属性は以下を参照
http://developer.android.com/reference/android/preference/Preference.html
array.xml
ListPreference
で使う値を定義する
<resources> <array name="entry_sample"> <item>@string/entry1</item> <item>@string/entry2</item> </array> <string-array name="entry_sample_values"> <item>1</item> <item>0</item> </string-array> </resources>
初期値設定
XMLで初期値を設定する場合はandroid:defaultValueに設定する。SharedPreference
から保存した値を読み込んでそれに応じて初期値を設定する場合など、コードの中で初期値を設定を行う場合は Preference
によって設定方法が異なる
SwitchPreference
setChecked()
で行う
val switchPref = findPreference(getString(R.string.key_internal_browser)) as SwitchPreference switchPref.setChecked(true)
ListPreference
ListPreference
のentryValues
のstring-array
の各itemが数字だとsetDefaultValue()
が動かなかったのでsetValueIndex()
を使うことで解決entryValues
をinterger-array
にするとNullPointerException
で落ちてしまう
イベントハンドリング
Preference.OnPreferenceChangeListener
と SharedPreferences.OnSharedPreferenceChangeListener
の2種類がある。 Preferennce
はデフォルトで値を SharedPreferences
に保存する。 PreferenceDataStore
を使うことで好きな場所に保存することもできる。
Preference.OnPreferenceChangeListener
は値が変わる前に呼ばれるのでバリデーションなどに使う。 PreferenceDataStore
を使う場合はここでハンドリングして値を保存する。
SharedPreferences.OnSharedPreferenceChangeListener
は SharedPreferences
のみ使用でき、値が保存された後に呼ばれる。