AndroidとかiOSとかモバイル多め。その他技術的なことも書いていきます。

【Android】続・PreferenceFragmentCompatを使った設定画面

以下の記事を昔に書いたが、内容が古くなってきたようなので書き直す。

phicdy.hatenablog.com

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

  • ListPreferenceentryValuesstring-array の各itemが数字だと setDefaultValue() が動かなかったので setValueIndex() を使うことで解決
  • entryValuesinterger-array にすると NullPointerException で落ちてしまう

イベントハンドリング

Preference.OnPreferenceChangeListenerSharedPreferences.OnSharedPreferenceChangeListener の2種類がある。 Preferennce はデフォルトで値を SharedPreferences に保存する。 PreferenceDataStore を使うことで好きな場所に保存することもできる。

Preference.OnPreferenceChangeListener は値が変わる前に呼ばれるのでバリデーションなどに使う。 PreferenceDataStore を使う場合はここでハンドリングして値を保存する。 SharedPreferences.OnSharedPreferenceChangeListenerSharedPreferences のみ使用でき、値が保存された後に呼ばれる。

参考