关注
安卓学习日志 Day14 — 偏好设置

概述

本文将了解 如何通过将设置屏幕添加到应用来自定义 应用中所显示的地震列表。用户可以选择应显示地震的最小震级 并可以更改是 按震级还是按时间来显示地震。要将此 功能添加到应用,需要添加新设置活动,然后使用 用户的偏好来更改用于 查询地震的 URL。

追求“惊艳”是 Android 的基本原则之一。 这通常意味着每个用户都需要 适合自己和满足自身偏好的略微不同的体验。因此,我们需要 通过一种方法使用户能够调整应用中的偏好,并且使系统记住 用户选定的偏好。例如,你询问用户感兴趣的 最小震级,而用户回答为 “5”。则每次获取该用户的地震信息时, 即使用户关闭应用又重新将其打开. 仍然只需获取震级高于 5 的地震。即便用户关闭 手机,随后又再次开机依然如此!

在用户设备上存储数据(通常指数据持久化) 是一个庞大的主题,同时也将成为整个 下一学习阶段的主题。

但目前,Android 的设计者希望存储少量数据 以追踪客户偏好, 因此通过 Android 组件来完成此功能。

SharedPreferences

偏好其实就是与简单类型、字符串或字符串集 相关联的字符串键。即使应用已关闭,或者电话已关机, Android 也会保留该数据。 Android 提供 SharedPreferences 类用于直接获取和 设置偏好。SharedPreferences 可以处理 将偏好数据读写到 设备上持久化存储(文件)的所有详细信息。

除了存储偏好,还需要提供一种方法以使 用户通过用户界面来编辑应用中的偏好。 对于数字偏好,需要允许用户键入 数字。对于应从列表中选择的偏好,则需要 显示选项列表,并允许用户从中选择一个选项等。

虽然我们自己可以设置此类活动,但是 Android 提供 名为 PreferenceFragment 的类,该类可以显示 UI 小工具 (Preference 对象)的列表,用于轻松编辑各种 偏好。

为防止你错过这些内容,术语 Fragment 是最新的。让我们来谈谈它 的含义吧。

因为很多 Android 活动可以从 嵌入偏好编辑小工具集获益,所以 Android 构架团队要尽可能简化此任务。

想象一下如果能够通过可置于活动布局中的 假定“PreferencesView”来完成此操作。 但是,我们需要通过一种方法使“PreferencesView”比简单视图更为智能, 而同时又不希望将其变成完全成熟的 活动。它只需作为活动的一小部分,例如, 活动的 Fragment。

Fragment 的行为很像相应活动中的 微型活动。通过引入 Fragment 可以实现适用于 大屏幕平板电脑和其他 Android 设备的 更加灵活的布局。

Fragment 可以在多个活动内重用,且一个活动 可以使用多个 Fragment。对于平板电脑 UI, 常用的模式是同时使用两个 Fragment。

现在,无需了解更多关于 Fragment 的内容, 在本中,不会讲述与多个 Fragment 相关的复杂内容。 我们将要做的是 在 SettingsActivity 内使用 PreferenceFragment 来显示用户可以编辑的 偏好列表。

有关 Fragment 的更多高级内容,请参阅创建单窗格和多窗格布局以及 构建灵活的 UI

构建偏好屏幕

添加 SettingsActivity 并将其启动,然后将菜单 添加到 EarthquakeActivity!

第一步,向 res/values/strings.xml 文件添加一些字符串。

在 strings.xml 中:

<!-- Settings Menu Item [CHAR LIMIT=NONE] -->
<string name="settings_menu_item">Settings</string>
<!-- Settings Activity Title [CHAR LIMIT=NONE] -->
<string name="settings_title">Earthquake Settings</string>

下一步,将创建 SettingsActivity 类。

在 SettingsActivity.java 中:

package com.example.quakereport;

import android.os.Bundle;
import android.preference.PreferenceFragment;

import androidx.appcompat.app.AppCompatActivity;

public class SettingsActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);
    }

    public static class EarthquakePreferenceFragment extends PreferenceFragment {

    }
}

在 res/layout/settings_activity.xml 中定义设置活动的布局:

在 settings_activity.xml 中:

<fragment
    android:name="com.example.android.quakereport.SettingsActivity$EarthquakePreferenceFragment"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.quakereport.SettingsActivity">
</fragment>

当然,需要 在标签内的 AndroidManifest.xml 中声明新活动:

在 AndroidManifest.xml 中:

<activity
    android:name=".SettingsActivity"
    android:label="@string/settings_title">
    <meta-data
        android:name="android.support.PARENT_ACTIVITY"
        android:value="com.example.quakereport.EarthquakeActivity"/>
</activity>

菜单

如果将一个较大的 SETTINGS 按钮放置在地震列表上方或下方, 则会使 EarthquakeActivity 显得非常杂乱。将该按钮放置在 位于 EarthquakeActivity 顶部的应用栏内更为合适。

活动可以使用菜单资源文件,并在 应用栏中显示菜单。因此,接下来将在新菜单资源文件中 定义菜单项:

在 res/menu/main.xml 中:

     <menu xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto"
         xmlns:tools="http://schemas.android.com/tools"
         tools:context="com.example.android.quakereport.EarthquakeActivity">
         <item
             android:id="@+id/action_settings"
             android:title="@string/settings_menu_item" 
             android:icon="@drawable/ic_filter"
             android:orderInCategory="1"
             app:showAsAction="ifRoom" />
     </menu>

覆盖 EarthquakeActivity.java 中的一些方法以使用 菜单,然后在用户单击菜单项时作出响应:

在 EarthquakeActivity.java 中:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    if (id == R.id.action_settings) {
        Intent settingsIntent = new Intent(this, SettingsActivity.class);
        startActivity(settingsIntent);
        return true;
    }
    return super.onOptionsItemSelected(item);
}

现在,我们已经拥有了菜单,其中“Settings”项可以打开(现在是空白的) SettingsActivity!

更改完成前后的差异

最小震级偏好

启动并运行 SettingsActivity 后,将添加 可以编辑的偏好!

目前,我们已经创建应用,仅显示 震级等于或高于 6.0 的 10 个最近地震( 通过硬编码 HTTP 请求以从 URL http://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&eventtype=earthquake&orderby=time&minmag=6&limit=10 获取数据)。 这看起来有些任意,因为应用的其余部分 可以在技术上处理任何地震列表的显示。因此, 将允许用户修改应用 将显示的最小震级。

首先,将向资源文件再添加几个字符串。

在 strings.xml 中:

  <!-- Strings For Minimum Magnitude Preference [CHAR LIMIT=30] -->
<string name="settings_min_magnitude_label">Minimum Magnitude</string>
<string name="settings_min_magnitude_key" translatable="false">min_magnitude</string>
<string name="settings_min_magnitude_default" translatable="false">6</string>

下一步,需要创建资源,该资源可以定义 屏幕应该显示的偏好编辑小工具的类型。在 res/xml 目录中创建 settings_main.xml 文件,该文件 内容如下:

在 res/xml/settings_main.xml 中:

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:title="@string/settings_title">

    <EditTextPreference
        android:defaultValue="@string/settings_min_magnitude_default"
        android:inputType="numberDecimal"
        android:key="@string/settings_min_magnitude_key"
        android:selectAllOnFocus="true"
        android:title="@string/settings_min_magnitude_label" />

</PreferenceScreen>

最后,在 SettingsActivity 中,覆盖 EarthquakePreferenceFragment 内部类中的 onCreate() 方法,以使用以前定义的 settings_main XML 资源。

在 SettingsActivity.java 中:

 package com.example.android.quakereport;

 import android.os.Bundle;
 import android.preference.PreferenceFragment;
 import android.support.v7.app.AppCompatActivity;

 public class SettingsActivity extends AppCompatActivity {

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.settings_activity);
     }

     public static class EarthquakePreferenceFragment extends PreferenceFragment {

         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             addPreferencesFromResource(R.xml.settings_main);
         }
     }
 }

现在,Settings UI 应该包含用于设置 最小震级的单个偏好(代码更改前后对比):

根据用户偏好构造 URL

目前,已经使用硬编码固定 URL 请求地震。 但是,我们需要将最小震级偏好作为查询参数插入到 URL 中。虽然可以通过 一些复杂的字符串串联来实现该功能,但是使用 Uri.Builder 类是一种更好的方法。

等一下,再问一次什么是 URI?URI 即统一资源标识符,是 更常用的 URL。URL 常常指向计算机网络 上的资源,而 URI 可以识别 范围更大的事物(从文件和邮箱到物理 对象,如书)。

由于 URL 是 URI 的子集,因此 Android 提供 方法来操纵 URI 是非常有意义的,因为这些 URI 将 同样适用于 URL。

首先,将 EarthquakeActivity 类中 USGS_REQUEST_URL 常量的值修改为 基准 URI。然后,使用 UriBuilder.appendQueryParameter() 方法将其他参数添加到 URI(例如,JSON 响应格式、已请求 10 个地震、 最小震级值以及排序顺序)。

在 EarthquakeActivity.java 中:

 private static final String USGS_REQUEST_URL = "http://earthquake.usgs.gov/fdsnws/event/1/query";

然后替换 onCreateLoader() 方法的主体以读取 最小震级的用户最新偏好并使用其偏好构造 相应 URI,然后针对 该 URI 创建新 Loader。

在 EarthquakeActivity.java 中:

 @Override
 public Loader<List<Earthquake>> onCreateLoader(int i, Bundle bundle) {

     SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
     String minMagnitude = sharedPrefs.getString(
                 getString(R.string.settings_min_magnitude_key),
                 getString(R.string.settings_min_magnitude_default));
     Uri baseUri = Uri.parse(USGS_REQUEST_URL);
     Uri.Builder uriBuilder = baseUri.buildUpon();

     uriBuilder.appendQueryParameter("format", "geojson");
     uriBuilder.appendQueryParameter("limit", "10");
     uriBuilder.appendQueryParameter("minmag", minMagnitude);
     uriBuilder.appendQueryParameter("orderby", "time");

     return new EarthquakeLoader(this, uriBuilder.toString());
 }

现在已经将最小震级偏好插入到查询 URL 中! 从 UI 的 SettingsActivity 中,将最小震级 设置为较小的值,然后查看 世界范围内发生的所有小地震。

更改完成前后的差异。

偏好摘要

对于用户而言,通过单击 最小震级偏好来查看其当前设置 并不是一种美妙的体验。

更好的方法是打开 Setting Activity 即可 查看偏好名称下方的偏好值, 如果对其进行更改,可以看到摘要将立即更新。

这对于偏好更改后应用能够立即知晓非常有用, 特别是偏好更改应对 UI 产生某些可见影响。

要实现该功能,当偏好发生更改时,PreferenceFragment 可以实现 OnPreferenceChangeListener 接口 以获取通知。然后,当用户更改单个偏好 并进行保存时,将使用已更改偏好的关键字来调用 onPreferenceChange() 方法。请注意,此方法 将返回布尔值,可防止通过返回 false 来 更改建议的偏好设置。

首先声明 EarthquakePreferenceFragment 类应 实现 OnPreferenceChangeListener 接口,然后覆盖 onPreferenceChange() 方法。此方法中的代码 会在偏好更改后更新已显示的偏好摘要。

在 SettingsActivity 中,在 EarthquakePreferenceFragment 类中:

  public static class EarthquakePreferenceFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {

         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             addPreferencesFromResource(R.xml.settings_main);
         }

         @Override
         public boolean onPreferenceChange(Preference preference, Object value) {
             String stringValue = value.toString();
             preference.setSummary(stringValue);
             return true;
         }
     }

但是,在启动设置活动后, 仍需要更新偏好摘要。如果给定偏好的键,可以 使用 PreferenceFragment 的 findPreference() 方法来获取偏好 对象,然后使用 bindPreferenceSummaryToValue() 帮助程序方法来设置偏好。

在 EarthquakePreferenceFragment 中:

 @Override
 public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     addPreferencesFromResource(R.xml.settings_main);

     Preference minMagnitude = findPreference(getString(R.string.settings_min_magnitude_key));
     bindPreferenceSummaryToValue(minMagnitude);
 }

现在需要定义 bindPreferenceSummaryToValue() 帮助程序方法 来设置当前 EarhtquakePreferenceFragment 实例作为 每个偏好上的侦听程序。还将读取 设备上 SharedPreferences 中存储的偏好当前值,然后在 偏好摘要中进行显示(以便用户能够查看 偏好的当前值)。

在 EarthquakePreferenceFragment 中:

 private void bindPreferenceSummaryToValue(Preference preference) {
     preference.setOnPreferenceChangeListener(this);
     SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(preference.getContext());
     String preferenceString = preferences.getString(preference.getKey(), "");
     onPreferenceChange(preference, preferenceString);
 }

更改后,整个 SettingsActivity 文件 将如下所示:

在 SettingsActivity.java 中:

 package com.example.android.quakereport;

 import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.preference.Preference;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceManager;
 import android.support.v7.app.AppCompatActivity;

 public class SettingsActivity extends AppCompatActivity {

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.settings_activity);
     }

     public static class EarthquakePreferenceFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {

         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             addPreferencesFromResource(R.xml.settings_main);

             Preference minMagnitude = findPreference(getString(R.string.settings_min_magnitude_key));
             bindPreferenceSummaryToValue(minMagnitude);
         }

         @Override
         public boolean onPreferenceChange(Preference preference, Object value) {
             String stringValue = value.toString();
             preference.setSummary(stringValue);
             return true;
         }

         private void bindPreferenceSummaryToValue(Preference preference) {
             preference.setOnPreferenceChangeListener(this);
             SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(preference.getContext());
             String preferenceString = preferences.getString(preference.getKey(), "");
             onPreferenceChange(preference, preferenceString);
         }
     }
 }

更改完成前后的差异

下面是运行截图:

地震列表的排序偏好

我们将再添加一个偏好选项。有时,用户要了解 震级最大的地震,而有时用户却要 了解最近发生的地震。我们可以 使用 ListPreference 轻松实现用户的要求。首先,在字符串资源文件中定义 更多字符串。

在 strings.xml 中:

 <!-- Strings For Order-By Preference [CHAR LIMIT=30] -->
 <string name="settings_order_by_label">Order By</string>
 <string name="settings_order_by_key" translatable="false">order_by</string>
 <string name="settings_order_by_default" translatable="false">@string/settings_order_by_magnitude_value</string>

 <!-- Label for order-by magnitude option [CHAR LIMIT=20] -->
 <string name="settings_order_by_magnitude_label">Magnitude</string>
 <string name="settings_order_by_magnitude_value" translatable="false">magnitude</string>

 <!-- Label for order-by most recent option [CHAR LIMIT=20] -->
 <string name="settings_order_by_most_recent_label">Most Recent</string>
 <string name="settings_order_by_most_recent_value" translatable="false">time</string>

下一步,我们将通过创建 new res/values/arrays.xml 文件将这些字符串打包到字符串数组中。

在 res/values/arrays.xml 中:

 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string-array name="settings_order_by_labels">
         <item>@string/settings_order_by_magnitude_label</item>
         <item>@string/settings_order_by_most_recent_label</item>
     </string-array>

     <string-array name="settings_order_by_values">
         <item>@string/settings_order_by_magnitude_value</item>
         <item>@string/settings_order_by_most_recent_value</item>
     </string-array>
 </resources>

然后将 ListPreference 添加到 res/xml/settings_main.xml 文件中。整个 XML 文件应包含以下内容:

在 res/xml/settings_main.xml 中:

 <?xml version="1.0" encoding="utf-8"?>
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:title="@string/settings_title">

     <ListPreference
         android:defaultValue="@string/settings_order_by_default"
         android:entries="@array/settings_order_by_labels"
         android:entryValues="@array/settings_order_by_values"
         android:key="@string/settings_order_by_key"
         android:title="@string/settings_order_by_label" />

     <EditTextPreference
         android:defaultValue="@string/settings_min_magnitude_default"
         android:inputType="numberDecimal"
         android:key="@string/settings_min_magnitude_key"
         android:selectAllOnFocus="true"
         android:title="@string/settings_min_magnitude_label" />

 </PreferenceScreen>

然后,构建用于创建 HTTP 请求的 URI 时,需要查找用户首选的排序顺序。从 SharedPreferences 进行读取, 然后查看与键相关的值: getString(R.string.settings_order_by_key)。构建 URI 和 附加查询参数时,将使用用户的偏好 (存储在 orderBy 变量中), 而非将“orderby”参数硬编码为“time”。

在 EarthquakeActivity.java 中:

     @Override
     public Loader<List<Earthquake>> onCreateLoader(int i, Bundle bundle) {

         SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
         String minMagnitude = sharedPrefs.getString(
                 getString(R.string.settings_min_magnitude_key),
                 getString(R.string.settings_min_magnitude_default));

         String orderBy = sharedPrefs.getString(
                 getString(R.string.settings_order_by_key),
                 getString(R.string.settings_order_by_default)
         );

         Uri baseUri = Uri.parse(USGS_REQUEST_URL);
         Uri.Builder uriBuilder = baseUri.buildUpon();

         uriBuilder.appendQueryParameter("format", "geojson");
         uriBuilder.appendQueryParameter("limit", "10");
         uriBuilder.appendQueryParameter("minmag", minMagnitude);
         uriBuilder.appendQueryParameter("orderby", orderBy);

         return new EarthquakeLoader(this, uriBuilder.toString());
     }

最后,我们将在 EarthquakePreferenceFragment 中添加其他逻辑,以使其能够识别新的 ListPreference,该操作类似针对 EditTextPreference 的操作。在 Fragment 的 onCreate() 方法中,根据偏好对象的键来查找“order by”偏好 对象。然后调用此偏好对象的 bindPreferenceSummaryToValue() 帮助程序方法, 该方法可将此 Fragment 设置为 OnPreferenceChangeListener 并 更新摘要,以使其显示 SharedPreferences 中存储的当前值。

在 SettingsActivity.java 中,在 EarthquakePreferenceFragment 类中:

 @Override
 public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     addPreferencesFromResource(R.xml.settings_main);

      Preference minMagnitude = findPreference(getString(R.string.settings_min_magnitude_key));
      bindPreferenceSummaryToValue(minMagnitude);

      Preference orderBy = findPreference(getString(R.string.settings_order_by_key));
      bindPreferenceSummaryToValue(orderBy);
 }

由于这是 EarthquakePreferenceFragment 遇到的第一个 first ListPreference, 请更新 EarthquakePreferenceFragment 中的 toonPreferenceChange() 方法,以 正确更新 ListPreference 的摘要(使用标记, 而非键)。

在 SettingsActivity.java 中,在 EarthquakePreferenceFragment 类中:

 @Override
 public boolean onPreferenceChange(Preference preference, Object value) {
     String stringValue = value.toString();
     if (preference instanceof ListPreference) {
         ListPreference listPreference = (ListPreference) preference;
         int prefIndex = listPreference.findIndexOfValue(stringValue);
         if (prefIndex >= 0) {
             CharSequence[] labels = listPreference.getEntries();
             preference.setSummary(labels[prefIndex]);
         }
     } else {
         preference.setSummary(stringValue);
     }
     return true;
 }

我们已完成一个简单的设置活动!代码如下 所示:

在 SettingsActivity.java 中:

 package com.example.android.quakereport;

 import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceManager;
 import android.support.v7.app.AppCompatActivity;

 public class SettingsActivity extends AppCompatActivity {

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.settings_activity);
     }

     public static class EarthquakePreferenceFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {

         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             addPreferencesFromResource(R.xml.settings_main);

             Preference minMagnitude = findPreference(getString(R.string.settings_min_magnitude_key));
             bindPreferenceSummaryToValue(minMagnitude);

             Preference orderBy = findPreference(getString(R.string.settings_order_by_key));
             bindPreferenceSummaryToValue(orderBy);
         }

         @Override
         public boolean onPreferenceChange(Preference preference, Object value) {
             String stringValue = value.toString();
             if (preference instanceof ListPreference) {
                 ListPreference listPreference = (ListPreference) preference;
                 int prefIndex = listPreference.findIndexOfValue(stringValue);
                 if (prefIndex >= 0) {
                     CharSequence[] labels = listPreference.getEntries();
                     preference.setSummary(labels[prefIndex]);
                 }
             } else {
                 preference.setSummary(stringValue);
             }
             return true;
         }

         private void bindPreferenceSummaryToValue(Preference preference) {
             preference.setOnPreferenceChangeListener(this);
             SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(preference.getContext());
             String preferenceString = preferences.getString(preference.getKey(), "");
             onPreferenceChange(preference, preferenceString);
         }
     }
 }

运行应用时,请转至设置,然后基于 震级或时间修改排序顺序,然后查看地震列表的变化!

练习完成前后的差异

录屏演示:

https://www.bilibili.com/video/BV1Ky4y1p71z?p=3

QuakeReport — 应用测试

参考

有关偏好的 材料设计指南

Using Shared Preferences

Save key-value data | Android Developers

SharedPreferences | Android Developers

PreferenceFragment | Android Developers

Settings with PreferenceFragment | CodePath Android Cliffnotes

Using PreferenceFragmentCompat to Store User Preferences

Preference Fragment · Developing for Android

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/weixin_45075891/article/details/113282025

文章分类Android

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--