概述
本文將了解 如何通過將設定螢屏添加到應用來自定義 應用中所顯示的地震串列,用戶可以選擇應顯示地震的最小震級 并可以更改是 按震級還是按時間來顯示地震,要將此 功能添加到應用,需要添加新設定活動,然后使用 用戶的偏好來更改用于 查詢地震的 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
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/253503.html
標籤:其他
