十一、使用首选项和保存状态

Android 提供了一个强大而灵活的框架来处理设置,也称为偏好。所谓设置,我们指的是用户为定制他们喜欢的应用而做出并保存的那些特性选择。(在本章中,术语“设置”和“首选项”将互换使用。)例如,如果用户想要经由铃声或振动的通知或者根本不想要,则这是用户保存的偏好;应用会记住这个选择,直到用户改变它。Android 提供了简单的 API,这些 API 隐藏了首选项的管理和持久化。它还提供了预构建的用户界面,您可以使用这些界面让用户进行偏好选择。由于 Android preferences 框架内置了强大的功能,我们还可以使用 preferences 来更通用地存储应用状态,以允许我们的应用从它停止的地方重新开始,如果我们的应用离开并稍后回来的话。作为另一个例子,一个游戏的高分可以存储为首选项,尽管您希望使用自己的 UI 来显示它们。

本章涵盖了如何为您的应用实现您自己的设置屏幕,如何与 Android 系统设置交互,以及如何使用设置来秘密保存应用状态,它还提供了最佳实践指导。你将会发现如何让你的设置在小屏幕和大屏幕上看起来都不错,比如平板电脑。

探索偏好框架

Android 的首选项框架从单个设置选项构建到包含设置选项的屏幕层次结构。设置可以是二进制设置,如开/关、文本输入或数值,也可以是选项列表中的一个选项。Android 使用一个 PreferenceManager 向应用提供设置值。该框架负责进行和保持更改,并在设置更改或即将更改时通知应用。虽然设置保存在文件中,但应用并不直接处理文件。文件被藏了起来,你很快就会看到它们在哪里。

与第 3 章中的视图一样,可以用 XML 或通过编写代码来指定首选项。在本章中,您将使用一个示例应用来演示不同类型的选择。XML 是指定首选项的首选方式,所以应用就是这样编写的。XML 指定了最低级别的设置,以及如何将设置分组到类别和屏幕中。作为参考,本章的示例应用给出了如下设置,如图图 11-1T5 所示。

9781430246800_Fig11-01.jpg

图 11-1 。示例应用首选项 UI 中的主要设置。由于屏幕的高度,它被显示为顶部在左边,底部在右边。请注意两幅图像之间的重叠部分

Android 提供了一个端到端的偏好框架。这意味着框架允许您定义首选项,向用户显示设置,并将用户的选择保存到数据存储中。您可以在 XML 中的 /res/xml/ 下定义您的首选项。为了向用户显示首选项,您需要编写一个 activity 类来扩展一个预定义的 Android 类,名为 Android . preference . preference activity,并使用片段来处理首选项的屏幕。框架负责剩下的事情(显示和持久化)。在您的应用中,您的代码将获得对特定首选项的引用。有了首选项引用,您可以获得首选项的当前值。

为了在用户会话中保存首选项,当前值必须保存在某个位置。Android 框架负责将首选项保存在设备上应用的 /data/data 目录下的 XML 文件中(参见图 11-2 )。

9781430246800_Fig11-02.jpg

图 11-2 。应用保存偏好的路径

注意您将只能在模拟器中检查共享的偏好设置文件。在真实的设备上,由于 Android 的安全性,共享的首选项文件是不可读的(当然,除非你有 root 权限)。

应用的默认首选项文件路径是 /data/data/ 【包名】 /shared_prefs/ 【包名】 _preferences.xml,其中【包名】是应用的包。清单 11-1 显示了本例的 com . Android book . preferences . main _ preferences . XML 数据文件。

清单 11-1 。为我们的示例保存的首选项

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<boolean name="notification_switch" value="true" />
<string name="package_name_preference">com.androidbook.win</string>
<boolean name="potato_selection_pref" value="true" />
<boolean name="show_airline_column_pref" value="true" />
<string name="flight_sort_option">2</string>
<boolean name="alert_email" value="false" />
<set name="pizza_toppings">
<string>pepperoni</string>
<string>cheese</string>
<string>olive</string>
</set>
<string name="alert_email_address">davemac327@gmail.com</string>
</map>

如您所见,值存储在一个映射中,首选项作为数据值的名称。有些值看起来很神秘,与显示给用户的内容不匹配。例如,航班排序选项的值是 2。Android 不会将显示的文本存储为首选项的值;相反,它存储了一个用户看不到的值,你可以独立于用户看到的内容来使用它。您希望能够根据用户的语言自由地更改显示的文本,还希望能够调整显示的文本,同时保持首选项文件中存储的值不变。如果值是一个整数而不是一些显示字符串,您甚至可以对首选项进行更简单的处理。您不必担心的是解析这个数据文件。Android 首选项框架提供了一个很好的 API 来处理首选项,这将在本章后面更详细地描述。

如果您将清单 11-1 中的偏好映射与图 11-1 中的截图进行比较,您会注意到并非所有的偏好都在偏好 XML 数据文件中列出了值。这是因为首选项数据文件不会自动为您存储默认值。您将很快看到如何处理默认值。

既然您已经看到了保存值的位置,那么您需要了解如何定义向用户显示的屏幕,以便他们可以进行选择。在您看到如何将偏好设置收集到屏幕中之前,您将了解您可以使用的不同类型的偏好设置,然后您将看到如何将它们收集到屏幕中。/data/data XML 文件中的每个持久值都来自特定的首选项。因此,让我们来了解一下其中每一项的含义。

了解复选框首选项和开关首选项

最简单的首选项是复选框首选项和开关首选项。这些共享一个公共的父类( TwoStatePreference ),或者打开(值为真)或者关闭(值为假)。对于示例应用,创建了一个带有五个复选框首选项的屏幕,如图 11-3 中的所示。清单 11-2 显示了 CheckBoxPreference 的 XML 外观。

9781430246800_Fig11-03.jpg

图 11-3 。复选框首选项的用户界面

清单 11-2 。使用复选框首选项

<CheckBoxPreference
        android:key="show_airline_column_pref"
        android:title="Airline"
        android:summary="Show Airline column" />

注意我们会在本章末尾给你一个 URL,你可以用它来下载本章的项目。这将允许您将这些项目直接导入到 IDE 中。主示例应用名为 PrefDemo。您应该参考该项目,直到到达保存状态部分。

此示例显示了指定首选项所需的最低要求。关键字是首选项的引用或名称,标题是为首选项显示的标题,摘要是对首选项内容或当前设置状态的描述。回头看看清单 11-1 中保存的值,您会看到一个用于“show_airline_column_pref”(关键字)的 <布尔> 标记,它有一个属性值 true,这表明首选项已被选中。

使用复选框首选项,当用户设置状态时,保存首选项的状态。换句话说,当用户选中或取消选中首选项控件时,它的状态会被立即保存。

除了视觉显示不同之外, SwitchPreference 非常相似。用户看到的不是用户界面中的复选框,而是一个开关,如图图 11-1 中“通知是”旁边所示。

CheckBoxPreference 和 SwitchPreference 的另一个有用的特性是,你可以根据它是否被选中来设置不同的摘要文本。XML 属性是 summaryOn 和 summaryOff 。如果您在 main.xml 文件中查找名为“potato_selection_pref”的复选框 Preference ,您会看到一个这样的例子。

在学习其他首选项类型之前,现在是了解如何访问该首选项以读取其值并执行其他操作的好时机。

在代码中访问首选项值

现在您已经定义了一个首选项,您需要知道如何在代码中访问该首选项,以便可以读取值。清单 11-3 显示了访问 Android 中 SharedPreferences 对象的代码,该对象中存在首选项。这段代码来自 setOptionText() 方法中的【MainActivity.java】文件。

清单 11-3 。访问复选框首选项

    SharedPreferences prefs =
            PreferenceManager.getDefaultSharedPreferences(this);
//  This is the other way to get to the shared preferences:
//  SharedPreferences prefs = getSharedPreferences(
//          "com.androidbook.preferences.main_preferences", 0);
    boolean showAirline = prefs.getBoolean("show_airline_column_pref", false);

使用对首选项的引用,可以直接读取 show_airline_column_pref 首选项的当前值。如清单 11-3 所示,有两种方法可以获得首选项。所示的第一种方法是获取当前上下文的默认首选项。在这种情况下,上下文是我们应用的主活动的上下文。第二种情况是注释掉的,使用包名检索首选项。如果您需要在不同的文件中存储不同的首选项集,您可以使用您想要的任何包名。

一旦有了对首选项的引用,就可以用首选项的键和默认值调用适当的 getter 方法。由于 show_airline_column_pref 是一个 TwoStatePreference ,所以返回值是一个布尔值。show_airline_column_pref 的默认值在这里被硬编码为 false。如果这个偏好还没有被设置,硬编码值( false )将被分配给 showAirline 。但是,这本身并不会将首选项保持为 false 以备将来使用,也不会考虑 XML 规范中为该首选项设置的任何默认值。如果 XML 规范使用资源值来指定默认值,则可以在代码中引用相同的资源来设置默认值,如以下不同首选项所示:

String flight_option = prefs.getString(
        resources.getString(R.string.flight_sort_option),
        resources.getString(R.string.flight_sort_option_default_value));

注意这里首选项的键也使用了一个字符串资源值(r . string . flight _ sort _ option)。这可能是一个明智的选择,因为它减少了打字错误的可能性。如果资源名称输入错误,您很可能会得到一个构建错误。如果您只使用简单的字符串,除了您的首选项不起作用之外,输入错误可能会被忽略。

我们展示了一种在代码中读取首选项默认值的方法。Android 提供了另一种更优雅的方式。在 onCreate() 中,您可以改为执行以下操作:

PreferenceManager.setDefaultValues(this, R.xml.main, false);

然后,在 setOptionText() 中,您应该这样做来读取选项值:

String option = prefs.getString(
    resources.getString(R.string.flight_sort_option), null);

第一个调用将使用 main.xml 来查找默认值,并使用默认值为我们生成首选项 xml 数据文件。如果我们在内存中已经有了一个 SharedPreferences 对象的实例,它也会更新这个实例。然后,第二个调用将为 flight_sort_option 找到一个值,因为我们首先负责加载默认值。

第一次运行这段代码后,如果您查看 shared_prefs 文件夹,您将会看到 preferences XML 文件,即使 preferences 屏幕尚未被调用。您还会看到另一个名为 _ has _ set _ default _ values . XML 的文件。这个文件告诉您的应用,已经使用默认值创建了首选项 XML 文件。的第三个参数 setDefaultValues()—即 false—表示您希望在 preferences XML 文件中设置默认值,前提是以前没有这样做过。Android 通过这个新 XML 文件的存在记住了这些信息。然而,即使你升级你的应用并添加具有新默认值的新设置,Android 也会记住,这意味着这个技巧不会设置那些新默认值。您的最佳选择是始终使用资源作为默认值,并在获取首选项的当前值时始终提供该资源作为默认值。

理解 ListPreference

列表首选项包含每个选项的单选按钮,默认(或当前)选项是预先选定的。期望用户选择一个且仅一个选项。当用户选择一个选项时,对话框会立即关闭,选择会保存在 preferences XML 文件中。图 11-4 显示了的样子。

9781430246800_Fig11-04.jpg

图 11-4 。list preference 的用户界面

清单 11-4 包含一个 XML 片段,表示航班选项偏好设置。这一次,该文件包含对字符串和数组的引用,这是指定这些内容而不是对字符串进行硬编码的更常见方式。如前所述,存储在 /data/data/{package} 目录下的 XML 数据文件中的列表首选项的值与用户在用户界面中看到的不同。密钥的名称与用户看不到的隐藏值一起存储在数据文件中。因此,要让 ListPreference 工作,需要两个数组:显示给用户的值和用作键值的字符串。这是你容易犯错的地方。entries 数组保存向用户显示的字符串,而 entryValues 数组保存将存储在首选项数据 XML 文件中的字符串。

清单 11-4 。在 XML 中指定 ListPreference

<ListPreference
  android:key="@string/flight_sort_option"
  android:title="@string/listTitle"
  android:summary="@string/listSummary"
  android:entries="@array/flight_sort_options"
  android:entryValues="@array/flight_sort_options_values"
  android:dialogTitle="@string/dialogTitle"
  android:defaultValue="@string/flight_sort_option_default_value" />

两个阵列之间的元素在位置上彼此对应。也就是说, entryValues 数组中的第三个元素对应于 entries 数组中的第三个元素。用 0,1,2 等很有诱惑力。,因为 entryValues 但这不是必需的,而且当以后必须修改数组时,这可能会导致问题。如果我们的选项本质上是数字(例如,一个倒数计时器的起始值),那么我们可以使用 60、120、300 等值。只要对开发人员有意义,这些值根本不需要是数字;用户看不到这些值,除非您选择公开它们。用户只能看到第一个字符串数组 flight_sort_options 中的文本。本章的示例应用展示了这两种方式。

这里需要注意的是:因为 preferences XML 数据文件只存储值而不存储文本,所以如果您升级了应用并更改了选项的文本或者向字符串数组添加了项,那么在升级之后,preferences XML 数据文件中存储的任何值都应该与适当的文本对齐。在应用升级期间,会保留首选项 XML 数据文件。如果 preferences XML 数据文件中有 a "1" ,并且这意味着升级前的" # of Stops ",那么它在升级后仍然意味着" # of Stops "。

由于最终用户看不到 entryValues 数组,所以最好在应用中只存储一次。因此,创建一个且只有一个/RES/values/prefvaluearrays . XML 文件来包含这些数组。对于不同的语言或不同的设备配置,每个应用很可能会多次创建条目数组。因此,为您需要的每一种变化制作单独的 predisplayarrays . XML 文件。例如,如果您的应用将使用英语和法语,则英语和法语将有单独的 prefdisplayarrays.xml 文件。您不希望在这些其他文件中包含 entryValues 数组。不过,在 entryValues 和 entries 数组之间必须有相同数量的数组元素。元素必须对齐。当你做出改变时,要注意保持一切都在一条线上。清单 11-5 包含示例的 ListPreference 文件的源。

清单 11-5 。我们示例中的其他 ListPreference 文件

<?xml version="1.0" encoding="utf-8"?>
<!-- This file is /res/values/prefvaluearrays.xml -->
<resources>
<string-array name="flight_sort_options_values">
    <item>0</item>
    <item>1</item>
    <item>2</item>
</string-array>
<string-array name="pizza_toppings_values">
    <item>cheese</item>
    <item>pepperoni</item>
    <item>onion</item>
    <item>mushroom</item>
    <item>olive</item>
    <item>ham</item>
    <item>pineapple</item>
</string-array>
<string-array name="default_pizza_toppings">
    <item>cheese</item>
    <item>pepperoni</item>
</string-array>
</resources>

<?xml version="1.0" encoding="utf-8"?>
<!-- This file is /res/values/prefdisplayarrays.xml -->
<resources>
<string-array name="flight_sort_options">
    <item>Total Cost</item>
    <item># of Stops</item>
    <item>Airline</item>
</string-array>
<string-array name="pizza_toppings">
    <item>Cheese</item>
    <item>Pepperoni</item>
    <item>Onions</item>
    <item>Portobello Mushrooms</item>
    <item>Black Olives</item>
    <item>Smoked Ham</item>
    <item>Pineapple</item>
</string-array>
</resources>

另外,不要忘记 XML 源文件中指定的默认值必须与 prefvaluearrays.xml 的数组中的 entryValue 相匹配。

对于 ListPreference,首选项的值是一个字符串。如果你使用数字串(例如,0,1,1138)作为的 entryValues ,你可以把它们转换成整数或者你在代码中需要的任何东西,就像在 flight_sort_options_values 数组中使用的那样。

您的代码可能想要显示来自首选项的条目数组的用户友好文本。这个例子采用了一个捷径,因为数组索引被用于 flight _ sort _ options _ values 中的元素。通过简单地将值转换成一个 int ,你就知道从 flight_sort_options 中读取哪个字符串。如果您为 flight _ sort _ options _ values 使用了其他值集,您将需要确定您偏好的元素的索引,然后使用该索引从 flight_sort_options 中获取您偏好的文本。 ListPreference 的 helper 方法 findindexoffvalue()可以对此有所帮助,它将索引提供给 values 数组,这样您就可以轻松地从 entries 数组中获取相应的显示文本。

现在回到清单 11-4 ,有几个字符串用于标题、摘要等等。名为的字符串 flight _ sort _ option _ default _ value 将默认值设置为 1 ,以表示示例中的“# of Stops”。为每个选项选择一个默认值通常是一个好主意。如果您没有选择默认值,也没有选择任何值,那么返回选项值的方法将返回空值。在这种情况下,您的代码必须处理空值。

了解编辑文本首选项

首选项框架还提供了一个名为 EditTextPreference 的自由格式文本首选项。该首选项允许您捕获原始文本,而不是要求用户做出选择。为了演示这一点,让我们假设您有一个为用户生成 Java 代码的应用。这个应用的首选项设置之一可能是用于生成的类的默认包名。在这里,您希望向用户显示一个文本字段,以便为生成的类设置包名。图 11-5 显示了 UI,清单 11-6 显示了 XML。

9781430246800_Fig11-05.jpg

图 11-5 。使用编辑文本首选项

清单 11-6 。一个 EditTextPreference 的例子

<EditTextPreference
        android:key="package_name_preference"
        android:title="Set Package Name"
        android:summary="Set the package name for generated code"
        android:dialogTitle="Package Name" />

当选择“设置包名”时,会向用户显示一个对话框来输入包名。单击“确定”按钮时,首选项会保存到首选项存储中。

与其他首选项一样,您可以通过调用适当的 getter 方法来获取首选项的值,在本例中是 getString() 。

了解 MultiSelectListPreference

最后,Android 3.0 中引入了一个名为 multiselectlistposition 的偏好设置。这个概念有点类似于 ListPreference ,但是用户不是只能在列表中选择一个项目,而是可以选择几个或者一个都不选。在清单 11-1 中,multiselectlist preference 在 preferences XML 数据文件中存储一个标签,而不是单个值。与 multiselectlist preference 的另一个显著区别是,默认值是一个数组,就像 entryValues 数组一样。也就是说,对于该首选项,默认值数组必须包含零个或多个来自 entryValues 数组的元素。这也可以在本章的示例应用中看到;只需查看 /res/xml 目录中 main.xml 文件的结尾即可。

要获得一个 multiselectlist preference 的当前值,使用 SharedPreferences 的 getStringSet() 方法。要从 entries 数组中检索显示字符串,您需要遍历作为该首选项的值的字符串集,确定字符串的索引,并使用该索引从 entries 数组中访问适当的显示字符串。

更新 AndroidManifest.xml

因为示例应用中有两个活动,所以我们需要在 AndroidManifest.xml 中有两个活动标记。第一个是类别 LAUNCHER 的标准活动。第二个是针对一个 PreferenceActivity 的,所以根据意图的约定设置动作名称,并将类别设置为 PREFERENCE ,如清单 11-7 所示。你可能不希望 PreferenceActivity 出现在我们所有其他应用的 Android 页面上,这就是为什么你不使用 LAUNCHER 的原因。如果要添加其他偏好活动,您需要对 AndroidManifest.xml 进行类似的更改。

清单 11-7 。androidmanifest . XML 中的 PreferenceActivity 条目

        <activity android:name=".MainPreferenceActivity"
                  android:label="@string/prefTitle">
            <intent-filter>
                <action android:name=
 "com.androidbook.preferences.main.intent.action.MainPreferences" />
                <category
                    android:name="android.intent.category.PREFERENCE" />
            </intent-filter>
        </activity>

使用偏好类别

首选项框架支持您将首选项组织成类别。例如,如果您有很多偏好,您可以使用 PreferenceCategory ,它将偏好分组在一个分隔符标签下。图 11-6 显示了这可能是什么样子。注意名为“肉和“蔬菜的分隔符您可以在 /res/xml/main.xml 中找到这些的规范。

9781430246800_Fig11-06.jpg

图 11-6 。使用偏好类别组织偏好

创建具有依赖关系的子首选项

另一种组织首选项的方法是使用首选项依赖关系。这在偏好之间创建了父子关系。例如,您可能有一个打开提醒的偏好设置;如果警报打开,可能会有几个其他与警报相关的首选项可供选择。如果主提醒首选项关闭,则其他首选项不相关,应被禁用。清单 11-8 显示了 XML,而图 11-7 显示了它的样子。

清单 11-8 。XML 中的首选项依赖

<PreferenceScreen>
    <PreferenceCategory
            android:title="Alerts">

        <CheckBoxPreference
                android:key="alert_email"
                android:title="Send email?" />

        <EditTextPreference
                android:key="alert_email_address"
                android:layout="?android:attr/preferenceLayoutChild"
                android:title="Email Address"
                android:dependency="alert_email" />

    </PreferenceCategory>
</PreferenceScreen>

9781430246800_Fig11-07.jpg

图 11-7 。偏好依赖

带标题的首选项

Android 3.0 引入了一种新的方式来组织偏好。你可以在平板电脑的主设置应用下看到这个。因为平板电脑的屏幕空间比智能手机大得多,所以同时显示更多的偏好信息是有意义的。为此,您可以使用首选项标题。看一下图 11-8

9781430246800_Fig11-08.jpg

图 11-8 。带有首选项标题的主设置页面

请注意,标题出现在左侧下方,就像一个垂直的标签栏。当您单击左侧的每个项目时,右侧的屏幕会显示该项目的首选项。在图 11-8 中,选择了声音,声音首选项显示在右侧。右边是一个 PreferenceScreen 对象,这个设置使用了片段。显然,我们需要做一些不同于本章所讨论的事情。

Android 3.0 最大的变化是在 PreferenceActivity 中添加了标题。这也意味着在 PreferenceActivity 中使用一个新的回调函数来设置标题。现在,当您扩展 PreferenceActivity 时,您会想要实现这个方法:

public void onBuildHeaders(List<Header> target) {
    loadHeadersFromResource(R.xml.preferences, target);
}

完整的源代码请参考 PrefDemo 示例应用。 preferences.xml 文件包含一些新标签,如下所示:

<preference-headers
        xmlns:android="[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)">
    <header android:fragment="com.example.PrefActivity$Prefs1Fragment"
            android:icon="@drawable/ic_settings_sound"
            android:title="Sound"
            android:summary="Your sound preferences" />
    ...

每个 header 标签指向一个扩展了 PreferenceFragment 的类。在刚刚给出的例子中,XML 指定了图标、标题和摘要文本(类似于副标题)。 Prefs1Fragment 是 PreferenceActivity 的内部类,看起来可能是这样的:

public static class Prefs1Fragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.sound_preferences);
    }
}

这个内部类需要做的就是拉入适当的首选项 XML 文件,如图所示。这个 preferences XML 文件包含了我们前面提到的首选项规范的类型,比如 ListPreference 、 CheckBoxPreference 、 PreferenceCategory 等等。非常好的是,当屏幕配置改变时,当偏好显示在小屏幕上时,Android 会注意做正确的事情。当屏幕太小而无法同时显示标题和右侧的偏好设置屏幕时,标题的行为类似于旧的偏好设置。也就是说,您只能看到标题;当您单击标题时,您只能看到相应的首选项屏幕。

首选屏幕

首选项的顶层容器是一个首选项屏幕。在平板电脑和 PreferenceFragment s 之前,你可以嵌套 PreferenceScreen s,当用户点击嵌套的 PreferenceScreen 项目时,新的 PreferenceScreen 将替换当前显示的 PreferenceScreen 。这在小屏幕上工作得很好,但是在平板电脑上看起来就不那么好了,尤其是如果你从标题和片段开始。您可能希望新的 PreferenceScreen 出现在当前片段所在的位置。

为了让 PreferenceScreen 在片段中工作,您需要做的就是为那个 PreferenceScreen 指定一个片段类名。清单 11-9 展示了样本应用中的 XML。

清单 11-9 。通过 PreferenceFragment 调用 PreferenceScreen

<PreferenceScreen
    android:title="Launch a new screen into a fragment"
    android:fragment="com.androidbook.preferences.main.BasicFrag" />

当用户点击此项时,当前片段被替换为 BasicFrag ,然后加载一个新的 XML 布局给 PreferenceScreen ,如 nested _ screen _ basic frag . XML 中所指定。在这种情况下,我们选择不使 BasicFrag 类成为 MainPreferenceActivity 类的内部类,主要是因为不需要来自外部类的共享,并向您展示如果您愿意,您可以这样做。

动态首选项摘要文本

您可能见过首选项摘要包含当前值的首选项。这实际上比你想象的要难实现一些。为了完成这一任务,您创建了一个侦听器回调函数,该函数检测首选项值何时会发生变化,然后相应地更新首选项摘要。第一步是让您的 PreferenceFragment 实现 OnPreferenceChangeListener 接口。然后您需要实现 onPreferenceChange() 回调。清单 11-10 显示了一个例子。回调中的 pkgPref 对象被提前设置为 onCreate() 方法中的首选项。

清单 11-10 。设置首选项监听器

public boolean onPreferenceChange(Preference preference,
                                      Object newValue) {
    final String key = preference.getKey();
    if ("package_name_preference".equals(key)) {
        pkgPref.setSummary(newValue.toString());
    }
    ...
    return true;
}

您必须使用 setOnPreferenceChangeListener(this)在 onResume() 中将该片段注册为一个监听器,并在 onPause() 中通过使用 null 再次调用它来取消注册。现在,每当您注册的首选项有一个待定的更改时,这个回调将被调用,并传入首选项和潜在的新值。回调返回一个布尔值,指示是否继续将首选项设置为新值(真)或不设置(假)。假设您将返回 true 以允许新的设置,这也是您可以更新汇总值的地方。您也可以验证新值并拒绝更改。也许您希望一个 multiselectlistproperty 具有最大数量的选中项。您可以在回调中计算所选项目的数量,如果数量过多,则拒绝更改。

保存带有首选项的状态

首选项对于允许用户根据自己的喜好定制应用非常有用,但是我们可以使用 Android 首选项框架来做更多的事情。当您的应用需要跟踪应用调用之间的一些数据时,即使用户在首选项屏幕中看不到数据,首选项也是完成任务的一种方式。请查找名为 SavingStateDemo 的示例应用以及完整的源代码。

活动类有一个 getPreferences(int mode) 方法。实际上,这只是调用 getSharedPreferences() ,用活动的类名作为标签,加上传入的模式。结果是一个特定于活动的共享首选项文件,您可以用它来跨调用存储有关该活动的数据。清单 11-11 中显示了一个如何使用它的简单例子。

清单 11-11 。使用首选项保存活动的状态

    final String INITIALIZED = "initialized";
    private String someString;

[ ... ]

    SharedPreferences myPrefs = getPreferences(MODE_PRIVATE);

    boolean hasPreferences = myPrefs.getBoolean(INITIALIZED, false);
    if(hasPreferences) {
        Log.v("Preferences", "We've been called before");
        // Read other values as desired from preferences file...
        someString = myPrefs.getString("someString", "");
    }
    else {
        Log.v("Preferences", "First time ever being called");
        // Set up initial values for what will end up
        // in the preferences file
        someString = "some default value";
    }

[ ... ]

    // Later when ready to write out values
    Editor editor = myPrefs.edit();
    editor.putBoolean(INITIALIZED, true);
    editor.putString("someString", someString);
    // Write other values as desired
    editor.commit();

这段代码所做的是为我们的 activity 类获取一个对 preferences 的引用,并检查一个名为 initialized 的布尔“preference”是否存在。我们将“preference”写在双引号中,因为这个值不是用户将要看到或设置的;它只是一个值,我们希望存储在一个共享的首选项文件中,供下次使用。如果我们得到一个值,那么共享的首选项文件就存在,所以这个应用以前一定被调用过。然后,您可以从共享首选项文件中读取其他值。例如,someString 可以是一个活动变量,它应该在上次运行该活动时设置,或者如果这是第一次,则设置为默认值。

要将值写入共享首选项文件,您必须首先获得一个首选项编辑器。然后,您可以将值放入首选项,并在完成后提交这些更改。注意,在幕后,Android 正在管理一个真正共享的 SharedPreferences 对象。理想情况下,一次不会有超过一个编辑处于活动状态。但是调用 commit() 方法非常重要,这样才能更新 SharedPreferences 对象和共享首选项 XML 文件。在这个例子中, someString 的值被写出来,以便在下次运行这个活动时使用。

您可以随时访问、写入和提交值到您的首选项文件。可能的用途包括写出游戏的高分或记录应用最后一次运行的时间。您还可以使用不同名称的 getSharedPreferences() 调用来管理不同的首选项集,所有这些都在同一个应用甚至同一个活动中。

到目前为止,在我们的示例中,MODE_PRIVATE 用于模式。因为共享的首选项文件总是存储在您的应用的/数据/数据/{包} 目录中,因此其他应用无法访问,您只需使用模式 _ 私有。

使用对话框首选项

到目前为止,您已经看到了如何使用 preferences 框架的开箱即用功能,但是如果您想要创建一个定制的首选项呢?如果你想要一个类似于屏幕设置下亮度偏好设置的滑块的东西呢?这就是 DialogPreference 的用武之地。 DialogPreference 是 EditTextPreference 和 ListPreference 的父类。该行为是一个弹出的对话框,向用户显示选项,并通过按钮或后退按钮关闭。但是您可以扩展 DialogPreference 来设置您自己的自定义首选项。在您的扩展类中,您在 onDialogClosed() 中提供了自己的布局、自己的点击处理器和自定义代码,以便将您的首选项数据写入共享的首选项文件。

参考

以下是对您可能希望进一步探索的主题的有用参考:

摘要

本章讲述了在 Android 中管理偏好设置:

  • 可用的首选项类型
  • 将首选项的当前值读入您的应用
  • 从嵌入式代码中设置默认值,并将默认值从 XML 文件写入保存的首选项文件
  • 将首选项组织成组,并定义首选项之间的依赖关系
  • 对首选项进行回调,以验证更改并设置动态摘要文本
  • 使用首选项框架跨调用保存和恢复活动信息
  • 创建自定义首选项