美文网首页
课程 4: 偏好

课程 4: 偏好

作者: HsuJin | 来源:发表于2018-01-23 14:54 被阅读0次

这节课是 Android 开发(入门)课程 的第三部分《访问网络》的最后一节课。这节课为 Quake Report App 添加一个偏好 (Preference) 页面,入口放在应用栏,使应用能够根据用户的偏好修改查询地震信息的最小震级,以及按震级大小或时间顺序排列显示地震信息列表。

关键词:SharedPreferences、PreferenceFragment、Menu、Uri.Builder、Preference.OnPreferenceChangeListener

应用的偏好使每个用户都能根据自身需要得到略微不同的应用体验。用户能够调整应用中的偏好,系统会记住用户选定的偏好。无论是重启应用,还是重启设备,当用户再次打开应用时,应用依然能够按照用户设置的偏好运行。这涉及到数据持久性 (Data Persistence),它是下一部分课程的主题。目前 Quake Report App 需要存储的数据较少,可以通过 Android 组件来完成此功能。

事实上,偏好是与原始类型、字符串或字符串集相关联的字符串键 (String Key)。即使关闭应用或设备,Android 也会保留该数据。SharedPreferences Class 就是一个存取偏好的接口,配合 PreferenceFragment Class 搭建的偏好列表使用户能够编辑各种偏好。

下面以 Quake Report App 为例,分步骤描述如何打造一个偏好页面。

Menu

在 Quake Report App 中将偏好页面的入口放在应用栏中,需要用到 Menus 组件。首先在 XML 中定义 Menu 资源:

In 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:icon="@drawable/ic_filter"
        android:title="@string/settings_menu_item"
        android:orderInCategory="1"
        app:showAsAction="ifRoom" />
</menu>
  1. 菜单项包含在 <menu> 内,因此 <menu> 必须是根节点。
  2. 一个菜单项为一个 <item>,若要搭建次级菜单,则在 <item> 内嵌套 <menu> 后添加更多 <item> 菜单项。
  3. android:id:菜单项的 ID,在 Java 中通过 ID 来识别每个菜单项。
  4. android:icon:菜单项的图标,用户通过点击图标与菜单项交互。
  5. android:title:菜单项的标题,用户长按菜单项的图标时会弹出显示。
  6. android:orderInCategory:菜单项的排列顺序,数值越大,优先级越低。例如排列顺序为默认的从左到右时,数值为 1 的菜单项排在最左边。
  7. app:showAsAction:定义菜单项的显示方式,这里设置为 "ifRoom" 表示菜单项仅在有空间时显示,若无空间则按照 orderInCategory 仅显示优先级最高(数值最小)的菜单项,其余项显示在溢出菜单 (Overflow Menu) 中。

更多 Menu 资源可以查看 Android Developers 文档

接下来在 Menu 所在的 Activity 或 Fragment 中处理事件:

In 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);
}

在 Android 中有三种 Menu: Options menu, Context menu, Popup menu,这里用的是 Options menu,主要放置一些对应用产生总体影响的菜单项。

  1. 首先通过 override onCreateOptionsMenu 指定在 XML 中定义的 Menu 资源。
  2. 然后通过 override onOptionsItemSelected 处理 Options Menu 的点击事件,输入参数为用户点击的 MenuItem 对象。通过 getItemId() 获取菜单项的 ID,然后通过 if/else 语句判断是否为期望的菜单项,若是则进一步处理,在 Quake Report App 中即 Intent 打开偏好页面。如果有多个菜单项,可以通过 switch/case 语句匹配指令,使代码更高效。
偏好页面

构建一个如上描述的通过应用栏中菜单项进入的偏好页面。因为需要使用 PreferenceFragment 搭建偏好列表的 UI,所以偏好页面的 Layout 仅放置一个 Fragment:

In 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.android.quakereport.SettingsActivity">
</fragment>

并在 Java 中添加一个自定义 PreferenceFragment 类:

In SettingsActivity.java

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 {

    }
}

另外,在 AndroidManifest 中添加 SettingsActivity 的 <meta-data> 定义偏好页面的 Parent Activity 为 EarthquakeActivity,相当于指定了其“向上”按钮的行为。

In AndroidManifest.xml

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

框架搭建好后,接下来使用 PreferenceFragment 构建 Preference 对象的列表,样式自动延续系统的风格,这些 Preference 对象会自动保存在 SharedPreferences 中。因此,用户修改 PreferenceFragment 偏好列表中的 Preference 对象后,参数会保存在 SharedPreferences 中,应用再通过操作 SharedPreferences 实现内容或结构的调整。

搭建一个 PreferenceFragment 偏好列表,可以通过 XML 文件完成:

In res/xml/settings_main.xml

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

    <EditTextPreference
        android:inputType="numberDecimal"
        android:selectAllOnFocus="true"
        android:title="@string/settings_min_magnitude_label"
        android:key="@string/settings_min_magnitude_key"
        android:defaultValue="@string/settings_min_magnitude_default" />
</PreferenceScreen>
  1. 注意文件的目录为 res > xml,文件放在资源目录的 xml 目录下。
  2. <PreferenceScreen> 必须为根节点,表示顶级 Preference 对象,所以在嵌套 Preference 对象时也需要由 <PreferenceScreen> 包括。
  3. Preference Class 有很多用于不同情景的子类,例如 CheckBoxPreference、SwitchPreference、EditTextPreference、ListPreference 等。这里使用 EditTextPreference,它是一个允许用户输入值的偏好:用户点击会弹出一个含有 EditText 的对话框,用户输入值后会以字符串的形式保存到 SharedPreferences 中。
  4. android:inputType:指定用户输入的数据类型,属于 TextText 的属性。设置为 "numberDecimal" 表示将输入的数据类型限制为数字,允许小数。
  5. android:selectAllOnFocus:设置为 "true" 表示在弹出对话框和输入法时,自动全选 EditText 内的所有字符,方便用户直接修改值,无需先删除原有值。
  6. android:title:偏好的标题,出现在偏好列表中。
  7. android:key:偏好的键,正如前面提到的,偏好其实是与原始类型、字符串或字符串集相关联的字符串键。这个键是 SharedPreferences 识别每项偏好的唯一标识。
  8. android:defaultValue:偏好的默认值,用户修改的就是这个值。在这里虽然值被限制为数字,但是因为传给 SharedPreferences 的数据是字符串,所以这里也保持字符串的数据类型。

在 strings.xml 中定义上述偏好的键和值时,因为要保证唯一性,所以在 <string> 标签内添加 translatable="false" 表示该字符串不可翻译,应保持原样。

<string name="settings_min_magnitude_key" translatable="false">min_magnitude</string>
<string name="settings_min_magnitude_default" translatable="false">6</string>

在 XML 文件中构建好 PreferenceFragment 偏好列表后,在 Java 中 override 其自定义类的 onCreate 添加每项偏好。

In SettingsActivity.java

public static class EarthquakePreferenceFragment extends PreferenceFragment {
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         addPreferencesFromResource(R.xml.settings_main);
     }
}
  1. 对于通过 XML 文件构建的 PreferenceFragment 偏好列表,调用 addPreferencesFromResource 添加;还存在另外两种构建偏好列表的方法,通过调用 addPreferencesFromIntentsetPreferenceScreen 添加,这里不作讨论。
  2. 与 drawable 目录的图像资源以及 Layout 文件类似,XML 文件的 ID 为文件名。
Uri.Builder

至此,Quake Report App 已经添加一个偏好页面,入口放在应用栏,用户修改偏好后会将值传递给 SharedPreferences。所以接下来就从 SharedPreferences 获取 EditTextPreference 的字符串,定制查询地震信息的 URL。这里引入 Uri.Builder Class 能够很方便地构造和修改 URI,其中 URI (Uniform Resource Identifier) 指统一资源标识符,URI 包含两个子类 URL 和 URN,两者的差别可简单理解为:URN 定义资源的属性,URL 提供查找该资源的方法。例如 URN 表示一个人的姓名,URL 表示那个人的住址。

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

@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());
}
  1. 将查询地震信息的 URL 的固定不变的头部定义为常量,注意添加 staticfinal 等关键字。

  2. 通过 PreferenceManager.getDefaultSharedPreferences(this) 获取 SharedPreferences 对象。

  3. 调用 getString 获取偏好的键和值,传入的参数为对应的字符串资源 ID,返回值为偏好的值,若偏好不存在则返回传入的值,因此传入值可以为 null

  4. 通过 Uri.parse 创建一个 Uri 对象,传入的参数为上面定义的 URL 头部,数据类型为 String。

  5. 通过 buildUpon() 创建一个已有 URI 的 Uri.Builder 对象。

  6. Uri.Builder 对象可构造多种 URI,包括绝对层级 URI (Absolute Hierarchical URI)、相对 URI (Relative URI)、不透明 URI (Opaque URI)。这里用到的绝对层级 URI 遵循如下格式:

     <scheme>://<authority><absolute path>?<query>#<fragment>
    

其中 Uri 对象已经从 URL 头部获取了前三个部分,所以剩下的查询参数 (query) 通过 appendQueryParameter 添加,输入参数按照键值对的形式传入。

Preference.OnPreferenceChangeListener

目前虽然 Quake Report App 的偏好已经能正常工作,但是在偏好页面仅显示每个偏好项的标题。如果能同时显示偏好项的标题和值,无需用户点击查看,这会是更好的用户体验。这里通过 OnPreferenceChangeListener 接口来实现这一功能。

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);
}
  1. 因为 OnPreferenceChangeListener 是一个接口,所以需要在 EarthquakePreferenceFragment 类名后添加 implements 表示在这个类内实现接口。
  2. onCreate 内通过 findPreference 找到要偏好项并传入辅助方法。
  3. bindPreferenceSummaryToValue 辅助方法内,设置传入的偏好项的监听器,创建偏好项的 SharedPreferences 对象并通过 getString 获取偏好项的值,最后传给监听器需要 override 的 method 处理。
  4. onPreferenceChange 内将辅助方法传入的偏好项的值通过 setSummary 显示在偏好项标题的下方,输入参数的数据类型为 CharSequence,由于 String 是 CharSequence 的扩展类,所以这里 CharSequence 作为输入参数时,可以传入 String。
  5. onPreferenceChange 中,面对多个偏好项的情况,可以通过 if/else 语句判断每个偏好项,再进行处理。
if (preference instanceof EditTextPreference) {
    preference.setSummary(stringValue);
} else {
    // Handle another preference here.
}
return true;

相关文章

  • 课程 4: 偏好

    这节课是 Android 开发(入门)课程 的第三部分《访问网络》的最后一节课。这节课为 Quake Report...

  • 【学习分享】管理经济学(23)——用“无差异曲线”分析“消费者偏

    【学习分享】管理经济学(23)——用“无差异曲线”分析“消费者偏好” 【课程】圈外商学院 在2004年4月1日,谷...

  • 北大行为经济学公开课学习

    通过中国慕课网学习了北大光华管理学院的课程,老师课程中主要说到社会性偏好,以及社会性偏好中的利他主义和不公平厌恶,...

  • 《穷查理宝典》:自我服务偏好

    4.【自我服务偏好】 自我服务偏好,很好理解,就是字面意思——为自己考虑的意思。众所周知,那种很招人厌的“认为世界...

  • 偏好

    每个人都有自己的偏好 对人或对物 也就无所谓得失 你拥有了因ta而起的欣喜 便无法避免因ta而起的悲痛 你遇见了平...

  • 偏好

    总是有人习惯性的跟不同的人经历相同的事情,而我,更向往的是跟同样的人经历不同的事! 是什么让我彻夜难眠……一杯同事...

  • 偏好

    比起炽热的太阳,我总是愿意享受软软的月光; 比起兰花的温淑,我总是愿意沐浴茉莉的清香; 比起绝壁的孤高,我总是想念...

  • 偏好

    有人喜欢甜,有人喜欢咸,还有人只喜欢天然食品不喜欢加工食品等。 好比咸甜党之争,好比同学认为一切加工食品都是不健康...

  • 偏好

    英语角有个客人,一直有个习惯,他在来之前,到一个固定的饭店吃饭。 他推荐的这家饭店,我们也去尝试了一下,觉得不错,...

  • 偏好

    王小波曾谈到过一桩历史逸闻:花剌子模国王下了一道命令,凡是给君王带来好消息的都给予奖赏,凡是带来坏消息的都抓去喂老...

网友评论

      本文标题:课程 4: 偏好

      本文链接:https://www.haomeiwen.com/subject/mkftaxtx.html