Android Preference 保存应用设置
阅读原文时间:2021年04月20日阅读:1

Preference 保存应用设置

个性化是Android的一大特点,根据用户习惯不同app应提供修改设置的页面。因此app需要面对大量需要持久化的变量。引入 Preference 库来实现这一需求

导入

原有的Preference包已被弃用,建议使用AndroidX下的Preference

app.build.gradle中添加以下依赖

dependencies {
    // ...
    implementation 'androidx.preference:preference:1.1.0'
}

编写配置界面

定义界面

首先在res新建xml资源文件,在里面新建一个以<PreferenceScreen>开头的xml文件

<!-- preferences.xml -->

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <SwitchPreferenceCompat
        app:key="notifications"
        app:icon="@drawable/ic_android_black_24dp"
        app:title="开关" />

    <Preference
        app:key="feedback"
        app:title="发送反馈"
        app:summary="上传技术Issues或提供建议"/>

    <PreferenceCategory
        app:key="category_0"
        app:summary="分组标题"
        app:title="分组0">

        <SwitchPreferenceCompat
            app:key="switch_0"
            app:summaryOff="关闭时提示"
            app:summaryOn="开启时提示"
            app:summary="全局提示优先级低于上面两条"
            app:title="开关0"/>

        <EditTextPreference
            app:key="edit_0"
            app:summary="输入框,输入内容"
            app:title="输入0"/>

        <Preference
            app:key="sub_page"
            app:title="点击进入二级设置"
            app:summary="自动加入返回栈"
            app:fragment="com.cloud_hermits.cameraxtest.fragment.SubSettingsFragment"/>
    </PreferenceCategory>
</PreferenceScreen>

如上所示的文件,提供了如下界面

其层级关系可以参考ViewViewGroup。在这里,PreferenceCategory之于ViewGroupPreference之于View


  • 主节点,必须以其为根节点。请使用 xmlns:app="http://schemas.android.com/apk/res-auto"

  • 容器,当设置项有很多时,可以用这个容器来划分区域(图中粉色文字和分割线),同样可以设置标题、图标和描述

  • 生成一个设置项,可以理解为是个按钮。必须为其设置属性key。因为所有设置项都继承自它,所以实际上所有的设置项也都必须设置key。其中,app:key为其标识符,icon为该设置项的图标。app:title设置标题,app:summary设置其描述。

  • 生成一个开关设置项。可以通过app:summaryOffapp:summaryOn分别设置其在开启和关闭状态下的描述。优先级高于单纯的app:Summary

  • 生成一个输入框设置项。点击该项会弹出一个输入弹窗。

创建Fragment

Preference需要通过Fragment来实现展示。创建一个继承自PreferenceFragmentCompat的类(文件或public static修饰),并在其回调中导入刚刚编写的xml文件

public class SettingsFragment extends PreferenceFragmentCompat{
    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        setPreferencesFromResource(R.xml.preferences, rootKey);
    }
}


class SettingsFragment: PreferenceFragmentCompat(){
    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?){
        setPreferencesFromResource(R.xml.preferences, rootKey)
    }
}

接下来,像添加普通Fragment一样将其加入Activity即可

多界面层级

若设置选项多到使用<PreferenceCategory>依旧过于复杂的地步,可以考虑分成多个界面进行展示

<PreferenceScreen>
    <!-- ... -->

    <Preference
        app:key="sub_page"
        app:title="点击进入二级设置"
        app:summary="自动加入返回栈"
        app:fragment="com.cloud_hermits.cameraxtest.fragment.SubSettingsFragment"/>
</PreferenceScreen>

点击该Preference后,将会跳转至app:fragment属性指向的Fragment(完整包名),并自动添加进返回栈。也就是说无需额外代码,即可实现用户使用返回键跳转到上级设置页面。

当然也可以在代码内通过Preference#setFragment(String fragment)实现跳转

当用户点击关联Fragment的设置项时,会调用接口方法PreferenceFragmentCompat#OnPreferenceStartFragmentCallback#onPreferenceStatFragment(PreferenceFragmentCompate, Preference)。建议托管在Activity中实现,以便在配置页面跳转时更新工具栏标题或其它工作.

自定义设置

获取Preference实例

通过在xml文件中定义的app:key属性,可以在Fragment中通过方法来获取实例

@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey){
    setPreferencesFromResource(R.xml.preferences, rootKey);

    //获取Key为 switch_0 的设置项
    SwitchPreferenceCompat switch0 = findPreference("switch_0");
}


override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?){
    setPreferenceFromResource(R.xml.preferences, rootKey)
    var switch0: SwitchPreferenceCompat? = findPreference("switch_0")
}

可用性与可见性

有时候有些设置项只有在特定条件下才开放设置。可以通过属性app:isPreferenceVisibleapp:enable来进行可见性和禁用状态的设置也可以在代码中通过setVisible(boolean)\ setEnable来进行设置

动态描述

获取到Preference实例后,可以为其设置SummaryProvider进行动态描述的设置。Android提供了两个简单的实现以满足普通需求

ListPreference.SimpleSummary.SimpleSummaryProviderEditTextPreference.SimpleSummaryProvider,可以通过Reference的属性app:useSimpleSummaryProvider设置,或通过代码使用。其会通过summary描述展示已经设置的值,若该值为空,则显示“Not set”。

代码设置:

listPreference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
editTextPreference.setSummaryProvider(EditTextPreference.SimpleSummaryProvider.getInstance());


listPreference.summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
editTextPreference.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()

当然,也可以自己实现SummaryProvider<T extent Preference>并将其设置进Preference

跳转

可以在<Preference>中嵌套<intent>标签来实现跳转。

<Preference
        app:key=”activity”
        app:title="Launch activity">
    <intent
            android:targetPackage="com.example"
            android:targetClass="com.example.ExampleActivity"/>
</Preference>

上述代码会跳转至ExampleActivity页面,或者也可以直接在Preference使用setIntent(Intent)
<intent>标签同样可以嵌套<extra>标签,以实现隐式跳转

保存的值

默认情况下,Preference使用SharedPreferences进行保存,保存在PreferenceManager.getDefaultSharedPreferences()中。检索方式如下

//官方示例 java
SharedPreferences sharedPreferences =
        PreferenceManager.getDefaultSharedPreferences(this /* Activity context */);
String name = sharedPreferences.getString(“signature”, "");


//官方示例 kotlin
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this /* Activity context */)
val name = sharedPreferences.getString("signature", "")

当然也可以通过实现并拓展PreferenceDataStore类来实现自定义数据存储(应注意ANR问题)。通过Preference#setPreferenceDataStore(PreferenceDataStore)PreferenceManager#setPreferenceDataStore(PreferenceDataStore)设置个别/全局的数据存储方式。
若调用尚未实现的方法,会导致抛出UnsupportedOperationException异常。