https://developer.android.google.cn/guide/topics/ui/settings.html
應用通常包括允許用戶修改應用特性和行為的設置。例如,有些應用允許用戶指定是否啟用通知,或指定應用與云端同步數據的頻率。
若要為應用提供設置,您應該使用 Android 的 Preference API 構建一個與其他 Android 應用中的用戶體驗一致的界面(包括系統設置)。本文旨在介紹如何使用 Preference API 構建應用設置。
[圖片上傳失敗...(image-d1a065-1640352404665)] 定義的項目將打開一個用于更改設置的界面。](http://upload-images.jianshu.io/upload_images/1662509-7d81f5f527202b86.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
概覽
設置是使用您在 XML 文件中聲明的 Preference 類的各種子類構建而成,而不是使用 View 對象構建用戶界面。
Preference 對象是單個設置的構建基塊。每個 Preference 均作為項目顯示在列表中,并提供適當的 UI 供用戶修改設置。例如,CheckBoxPreference 可創建一個列表項用于顯示復選框,ListPreference 可創建一個項目用于打開包含選擇列表的對話框。
您添加的每個 Preference 都有一個相應的鍵值對,可供系統用來將設置保存在應用設置的默認SharedPreferences 文件中。當用戶更改設置時,系統會為您更新 SharedPreferences 文件中的相應值。您只應在需要讀取值以根據用戶設置確定應用的行為時,才與關聯的 SharedPreferences 文件直接交互。
為每個設置保存在 SharedPreferences 中的值可能是以下數據類型之一:
- 布爾值
- 浮點型
- 整型
- 長整型
- 字符串
- 字符串 Set
由于應用的設置 UI 是使用 Preference 對象(而非 View 對象)構建而成,因此您需要使用專門的 Activity 或 Fragment 子類顯示列表設置:
- 如果應用支持早于 3.0(API 級別 10 及更低級別)的 Android 版本,則您必須將 Activity 構建為 PreferenceActivity 類的擴展。
- 對于 Android 3.0 及更高版本,您應改用傳統 Activity
,以托管可顯示應用設置的 PreferenceFragment
。但是,如果您擁有多組設置,則還可以使用 PreferenceActivity
為大屏幕創建雙窗格布局。
創建首選項 Activity 和使用首選項片段部分將討論如何設置 PreferenceActivity 以及 PreferenceFragment 實例。
首選項
所有應用設置均由 Preference 類的特定子類表示。每個子類均包括一組核心屬性,允許您指定設置標題和默認值等內容。 此外,每個子類還提供自己的專用屬性和用戶界面。 例如,圖 1 顯示的是“信息” 應用的設置屏幕截圖。設置屏幕中的每個列表項均由不同的 Preference 對象提供支持。
一些最常用的首選項如下:
- CheckBoxPreference顯示一個包含已啟用或已停用設置復選框的項目。保存的值是布爾型(如果選中則為 true)。
- ListPreference打開一個包含單選按鈕列表的對話框。保存的值可以是任一受支持的值類型(如上所列)。
- EditTextPreference打開一個包含 EditText 小部件的對話框。保存的值是 String。
有關所有其他子類及其對應屬性的列表,請參閱 Preference 類。
當然,內置類不能滿足所有需求,您的應用可能需要更專業化的內容。 例如,該平臺目前不提供用于選取數字或日期的 Preference 類。因此,您可能需要定義自己的 Preference 子類。如需有關執行此操作的幫助,請參閱構建自定義首選項部分。
使用 XML 定義首選項
雖然您可以在運行時實例化新的 Preference 對象,不過您還是應該使用 Preference 對象的層次結構在 XML 中定義設置列表。使用 XML 文件定義設置的集合是首選方法,因為該文件提供了一個便于更新的易讀結構。此外,應用的設置通常是預先確定的,不過您仍可在運行時修改此集合。
每個 Preference 子類均可以使用與類名(如 <CheckBoxPreference>)匹配的 XML 元素來聲明。
您必須將 XML 文件保存在 res/xml/ 目錄中。盡管您可以隨意命名該文件,但它通常命名為 preferences.xml。 您通常只需一個文件,因為層次結構中的分支(可打開各自的設置列表)是使用 PreferenceScreen 的嵌套實例聲明的。
XML 文件的根節點必須是一個 PreferenceScreen 元素。您可以在此元素內添加每個 Preference。在 <PreferenceScreen> 元素內添加的每個子項均將作為單獨的項目顯示在設置列表中。
例如:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="pref_sync"
android:title="@string/pref_sync"
android:summary="@string/pref_sync_summ"
android:defaultValue="true" />
<ListPreference
android:dependency="pref_sync"
android:key="pref_syncConnectionType"
android:title="@string/pref_syncConnectionType"
android:dialogTitle="@string/pref_syncConnectionType"
android:entries="@array/pref_syncConnectionTypes_entries"
android:entryValues="@array/pref_syncConnectionTypes_values"
android:defaultValue="@string/pref_syncConnectionTypes_default" />
</PreferenceScreen>
創建設置組
如果您提供的列表包含 10 項或更多設置,則用戶可能難以瀏覽、理解和處理這些設置。若要彌補這一點,您可以將部分或全部設置分成若干組,從而有效地將一個長列表轉化為多個短列表。 可以通過下列兩種方法之一提供一組相關設置:
您可以使用其中一種或兩種分組方法來組織應用的設置。決定要使用的方法以及如何拆分設置時,應遵循 Android 設計的設置指南中的準則。
使用標題
若要以分隔線分隔兩組設置并為其提供標題(如下圖所示),請將每組 Preference 對象放入PreferenceCategory 內。
[圖片上傳失敗...(image-29c78f-1640352404665)]
元素指定。 2. 標題由 android:title
屬性指定。](http://upload-images.jianshu.io/upload_images/1662509-6a2a974ecf137ea6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
例如:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/pref_sms_storage_title"
android:key="pref_key_storage_settings">
<CheckBoxPreference
android:key="pref_key_auto_delete"
android:summary="@string/pref_summary_auto_delete"
android:title="@string/pref_title_auto_delete"
android:defaultValue="false"... />
<Preference
android:key="pref_key_sms_delete_limit"
android:dependency="pref_key_auto_delete"
android:summary="@string/pref_summary_delete_limit"
android:title="@string/pref_title_sms_delete"... />
<Preference
android:key="pref_key_mms_delete_limit"
android:dependency="pref_key_auto_delete"
android:summary="@string/pref_summary_delete_limit"
android:title="@string/pref_title_mms_delete" ... />
</PreferenceCategory>
...
</PreferenceScreen>
使用子屏幕
若要將設置組放入子屏幕(如圖所示),請將 Preference 對象組放入 PreferenceScreen 內。
例如:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<!-- opens a subscreen of settings -->
<PreferenceScreen
android:key="button_voicemail_category_key"
android:title="@string/voicemail"
android:persistent="false">
<ListPreference
android:key="button_voicemail_provider_key"
android:title="@string/voicemail_provider" ... />
<!-- opens another nested subscreen -->
<PreferenceScreen
android:key="button_voicemail_setting_key"
android:title="@string/voicemail_settings"
android:persistent="false">
...
</PreferenceScreen>
<RingtonePreference
android:key="button_voicemail_ringtone_key"
android:title="@string/voicemail_ringtone_title"
android:ringtoneType="notification" ... />
...
</PreferenceScreen>
...
</PreferenceScreen>
使用 Intent
在某些情況下,您可能需要首選項來打開不同的 Activity(而不是網絡瀏覽器等設置屏幕)或查看網頁。 要在用戶選擇首選項時調用 Intent,請將<intent> 元素添加為相應 <Preference> 元素的子元素。
例如,您可以按如下方法使用首選項打開網頁:
<Preference android:title="@string/prefs_web_page" >
<intent android:action="android.intent.action.VIEW"
android:data="http://www.example.com" />
</Preference>
創建首選項 Activity
要在 Activity 中顯示您的設置,請擴展 PreferenceActivity 類。這是傳統 Activity
類的擴展,該類根據 Preference 對象的層次結構顯示設置列表。當用戶進行更改時,PreferenceActivity 會自動保留與每個 Preference 相關的設置。
注:如果您在開發針對 Android 3.0 及 更高版本的應用,則應改為使用 PreferenceFragment。轉到下文有關使用首選項片段的部分。
public class SettingsActivity extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
使用首選項片段
如果您在開發針對 Android 3.0(API 級別 11)及更高版本的應用,則應使用 PreferenceFragment 顯示 Preference 對象的列表。您可以將PreferenceFragment 添加到任何 Activity,而不必使用 PreferenceActivity。
與僅使用上述 Activity 相比,無論您在構建何種 Activity,片段都可為應用提供一個更加靈活的體系結構。 因此,我們建議您*盡可能使用PreferenceFragment 控制設置的顯示,而不是使用 PreferenceActivity。PreferenceFragment 的實現就像定義 onCreate() 方法以使用 addPreferencesFromResource() 加載首選項文件一樣簡單。例如:
public static class SettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
}
...
}
設置默認值
您創建的首選項可能會為應用定義一些重要行為,因此在用戶首次打開應用時,您有必要使用每個 Preference 的默認值初始化相關的SharedPreferences 文件。
首先,您必須使用 android:defaultValue 屬性為 XML 文件中的每個 Preference 對象指定默認值。該值可以是適合相應 Preference 對象的任意數據類型。例如:
<!-- default value is a boolean -->
<CheckBoxPreference
android:defaultValue="true"
... />
<!-- default value is a string -->
<ListPreference
android:defaultValue="@string/pref_syncConnectionTypes_default"
... />
然后,通過應用的主 Activity(以及用戶首次進入應用所藉由的任何其他 Activity)中的 onCreate() 方法調用 [setDefaultValues()](https://developer.android.google.cn/reference/android/preference/PreferenceManager.html#setDefaultValues(android.content.Context, int, boolean)):
PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
在 onCreate() 期間調用此方法可確保使用默認設置正確初始化應用,而應用可能需要讀取這些設置以確定某些行為(例如,是否在蜂窩網絡中下載數據)。
此方法采用三個參數, 第三個參數是一個布爾值,用于指示是否應該多次設置默認值。如果該值為 false,則僅當過去從未調用此方法時(或者默認值共享首選項文件中的 KEY_HAS_SET_DEFAULT_VALUES為 false 時),系統才會設置默認值。
使用首選項標頭
在極少數情況下,您可能需要設計設置,使第一個屏幕僅顯示子屏幕的列表(例如在系統“設置”應用中,如圖 4 和圖 5 所示)。** 在開發針對 Android 3.0 及更高版本**的此類設計時,您應該使用“標頭”功能,而非使用嵌套的 PreferenceScreen 元素構建子屏幕。
要使用標頭構建設置,您需要:
- 將每組設置分成單獨的 PreferenceFragment 實例。即,每組設置均需要一個單獨的 XML 文件。
- 創建 XML 標頭文件,其中列出每個設置組并聲明哪個片段包含對應的設置列表。
- 擴展 PreferenceActivity 類以托管設置。
- 實現 onBuildHeaders() 回調以指定標頭文件。
使用此設計的一大好處是,在大屏幕上運行時,PreferenceActivity 會自動提供雙窗格布局(如圖)。
[圖片上傳失敗...(image-b1eb28-1640352404665)]
將替換標頭。](http://upload-images.jianshu.io/upload_images/1662509-36a8614c669eed7e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
讀取首選項
默認情況下,應用的所有首選項均保存到一個可通過調用靜態方法 PreferenceManager.getDefaultSharedPreferences() 從應用內的任何位置訪問的文件中。 這將返回 SharedPreferences 對象,其中包含與 PreferenceActivity 中所用 Preference 對象相關的所有鍵值對。
例如,從應用中的任何其他 Activity 讀取某個首選項值的方法如下:
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
偵聽首選項變更
出于某些原因,您可能希望在用戶更改任一首選項時立即收到通知。 要在任一首選項發生更改時收到回調,請實現SharedPreference.OnSharedPreferenceChangeListener 接口,并通過調用 registerOnSharedPreferenceChangeListener() 為 SharedPreferences 對象注冊偵聽器。
該接口只有 [onSharedPreferenceChanged()](https://developer.android.google.cn/reference/android/content/SharedPreferences.OnSharedPreferenceChangeListener.html#onSharedPreferenceChanged(android.content.SharedPreferences, java.lang.String)) 一種回調方法,而且您可能會發現在 Activity 過程中實現該接口最為簡單。
若要妥善管理 Activity 生命周期,我們建議您在 onResume() 和 onPause() 回調期間分別注冊和注銷SharedPreferences.OnSharedPreferenceChangeListener。
@Override
protected void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
注意:目前,首選項管理器不會在您調用 registerOnSharedPreferenceChangeListener() 時存儲對偵聽器的強引用。 但是,您必須存儲對偵聽器的強引用,否則它將很容易被當作垃圾回收。 我們建議您將對偵聽器的引用保存在只要您需要偵聽器就會存在的對象的實例數據中。
例如,在以下代碼中,調用方未保留對偵聽器的引用。 因此,偵聽器將容易被當作垃圾回收,并在將來某個不確定的時間失敗:
prefs.registerOnSharedPreferenceChangeListener(
// Bad! The listener is subject to garbage collection!
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// listener implementation
}
});
有鑒于此,請將對偵聽器的引用存儲在只要需要偵聽器就會存在的對象的實例數據字段中:
SharedPreferences.OnSharedPreferenceChangeListener listener =
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// listener implementation
}
};
prefs.registerOnSharedPreferenceChangeListener(listener);
管理網絡使用情況
從 Android 4.0 開始,通過系統的“設置”應用,用戶可以了解自己的應用在前臺和后臺使用的網絡數據量。然后,用戶可以據此禁止具體的應用使用后臺數據。 為了避免用戶禁止您的應用從后臺訪問數據,您應該有效地使用數據連接,并允許用戶通過應用設置優化應用的數據使用。
例如,您可以允許用戶控制應用同步數據的頻率,控制應用是否僅在有 Wi-Fi 時才執行上傳/下載操作,以及控制應用能否在漫游時使用數據,等等。為用戶提供這些控件后,即使數據使用量接近他們在系統“設置”中設置的限制,他們也不大可能禁止您的應用訪問數據,因為他們可以精確地控制應用使用的數據量。
在 PreferenceActivity 中添加必要的首選項來控制應用的數據使用習慣后,您應立即在清單文件中為 ACTION_MANAGE_NETWORK_USAGE
添加 Intent 過濾器。例如:
<activity android:name="SettingsActivity" ... >
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
此 Intent 過濾器指示系統此 Activity 控制應用的數據使用情況。因此,當用戶從系統的“設置”應用檢查應用所使用的數據量時,可以使用“查看應用設置”按鈕啟動 PreferenceActivity,這樣,用戶就能夠優化應用使用的數據量。
構建自定義首選項
Android 框架包括各種 Preference 子類,您可以使用它們為各種不同類型的設置構建 UI。不過,您可能會發現自己需要的設置沒有內置解決方案,例如,數字選取器或日期選取器。 在這種情況下,您將需要通過擴展 Preference 類或其他子類之一來創建自定義首選項。
擴展 Preference 類時,您需要執行以下幾項重要操作:
- 指定在用戶選擇設置時顯示的用戶界面。
- 適時保存設置的值。
- 使用顯示的當前(默認)值初始化 Preference。
- 在系統請求時提供默認值。
- 如果 Preference 提供自己的 UI(例如對話框),請保存并恢復狀態以處理生命周期變更(例如,用戶旋轉屏幕)。
下文介紹如何完成所有這些任務。
指定用戶界面
如果您要直接擴展 Preference 類,則需要實現 onClick() 來定義在用戶選擇該項時發生的操作。不過,大多數自定義設置都會擴展DialogPreference 以顯示對話框,從而簡化這一過程。擴展 DialogPreference 時,必須在類構造函數中調用 setDialogLayoutResourcs()
來指定對話框的布局。
例如,自定義 DialogPreference 可以使用下面的構造函數來聲明布局并為默認的肯定和否定對話框按鈕指定文本:
public class NumberPickerPreference extends DialogPreference {
public NumberPickerPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setDialogLayoutResource(R.layout.numberpicker_dialog);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(android.R.string.cancel);
setDialogIcon(null);
}
...
}
保存設置的值
初始化當前值
提供默認值
保存和恢復首選項的狀態
正如布局中的 View 一樣,在重啟 Activity 或片段時(例如,用戶旋轉屏幕),Preference 子類也負責保存并恢復其狀態。要正確保存并恢復Preference 類的狀態,您必須實現生命周期回調方法 onSaveInstanceState() 和 onRestoreInstanceState()。
Preference 的狀態由實現 Parcelable 接口的對象定義。Android 框架為您提供此類對象,作為定義狀態對象(Preference.BaseSavedState 類)的起點。
要定義 Preference 類保存其狀態的方式,您應該擴展 Preference.BaseSavedState 類。您只需重寫幾種方法并定義 CREATOR 對象。
對于大多數應用,如果 Preference 子類保存除整型數以外的其他數據類型,則可復制下列實現并直接更改處理 value 的行.
private static class SavedState extends BaseSavedState {
// Member that holds the setting's value
// Change this data type to match the type saved by your Preference
int value;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
// Get the current preference's value
value = source.readInt(); // Change this to read the appropriate data type
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
// Write the preference's value
dest.writeInt(value); // Change this to write the appropriate data type
}
// Standard creator object using an instance of this class
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
如果將上述 Preference.BaseSavedState 實現添加到您的應用(通常,作為 Preference 子類的子類),則需要為 Preference 子類實現onSaveInstanceState() 和 onRestoreInstanceState() 方法。
例如:
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
// Check whether this Preference is persistent (continually saved)
if (isPersistent()) {
// No need to save instance state since it's persistent,
// use superclass state
return superState;
}
// Create instance of custom BaseSavedState
final SavedState myState = new SavedState(superState);
// Set the state's value with the class member that holds current
// setting value
myState.value = mNewValue;
return myState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
// Check whether we saved the state in onSaveInstanceState
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save the state, so call superclass
super.onRestoreInstanceState(state);
return;
}
// Cast state to custom BaseSavedState and pass to superclass
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
// Set this Preference's widget to reflect the restored state
mNumberPicker.setValue(myState.value);
}