lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

設定

應用程式通常會包含可讓使用者修改應用程式功能和行為的設定。例如,有些應用程式可讓使用者指定是否啟用通知,或指定應用程式與雲端同步資料的頻率。

如果您想要為應用程式提供設定,應該使用 Android Preference API 建置與其他 Android 應用程式使用者體驗一致的介面 (包含系統設定)。 本文件說明如何使用 Preference API 建置應用程式設定。

設定設計

如要瞭解如何設計設定,請參閱設定設計指南。

圖 1.Android 簡訊應用程式設定的螢幕擷取畫面。 選取一個 Preference 定義的項目可以開啟介面變更設定。

總覽

現在不使用 View 物件建置使用者介面,而是使用您在 XML 檔案中所宣告 Preference 類別的各種子類別來建置設定。

一個 Preference 物件是單一設定的建置區塊。 每個 Preference 都以項目的形式在清單中顯示,並為使用者提供適當的 UI 以修改設定。 例如,CheckBoxPreference 可以建立顯示核取方塊的清單項目,而 ListPreference 可以建立開啟對話方塊 (其中包含選擇清單) 的項目。

每個您新增的 Preference 都會有一個對應的鍵值配對,系統可使用此鍵值對將設定儲存到您應用程式設定的預設 SharedPreferences 檔案。 當使用者變更設定,系統會為您更新 SharedPreferences 檔案中的對應值。 您唯一需要與關聯的 SharedPreferences 檔案直接互動的時候,是當您需要讀取值才能根據使用者設定判斷應用程式行為時。

儲存在 SharedPreferences 的每個設定值可以是下列其中一個資料類型:

  • 布林值
  • 浮動
  • 整數
  • 長整數
  • 字串
  • 字串 Set

由於您的應用程式設定 UI 是使用 Preference 物件,而不是 View 物件建置,因此您需要使用專門的 ActivityFragment 子類別來顯示清單設定:

  • 如果應用程式支援的 Android 版本早於 3.0 (API 級別 10 及較早版本),您必須以 PreferenceActivity 類別延伸的形式建置 Activity。
  • 在 Android 3.0 及更新版本上,您應該使用傳統 Activity,它託管了顯示應用程式設定的 PreferenceFragment。 然而,當您有多個設定群組時,還可以使用 PreferenceActivity 在大螢幕建立兩個面板的版面配置。

如何設定 PreferenceActivityPreferenceFragment 執行個體在建立偏好設定 Activity使用偏好設定片段小節中有相關說明。

偏好設定

應用程式的每個設定都會以 Preference 類別的特定子類別代表。每個子類別包含一組核心屬性,可讓您為設定指定標題等項目和預設值。 每個子類別還提供自己專屬的屬性和使用者介面。 例如,圖 1 顯示簡訊應用程式設定的螢幕擷取畫面。 設定畫面中的每個清單項目都由不同的 Preference 物件支援。

下列為幾個最常見的偏好設定:

CheckBoxPreference
為啟用或停用的設定顯示含有核取方塊的項目。儲存的值是布林值 (如果選取 true)。
ListPreference
開啟含有選項按鈕清單的對話方塊。儲存的值可以是任何支援的值類型 (如上所列)。
EditTextPreference
開啟含有 EditText 小工具的對話方塊。儲存的值是 String

請參閱 Preference 類別,以取得所有其他子類別及其對應屬性的清單。

當然,內建類別無法滿足所有需要,您的應用程式可能需要更專門的功能。 例如,平台目前無法提供 Preference 類別以選取數字或日期。因此,您可能需要定義自己的 Preference 子類別。 如需執行此操作的協助,請參閱建置自訂偏好設定

在 XML 中定義偏好設定

雖然您可以在執行階段將新的 Preference 物件具現化,但您仍應該在 XML 中定義設定清單,並在其中包含 Preference 物件的階層。 使用 XML 檔案定義設定集合是較建議的做法,因為該檔案提供的易讀結構很容易更新。 而且,雖然您仍然可以在執行階段修改集合,但您的應用程式設定通常是預先定義好的。

每個 Preference 子類別都可透過與類別名稱相符的 XML 元素進行宣告,例如 <CheckBoxPreference>

您必須將 XML 檔案儲存在 res/xml/ 目錄中。雖然您可以自由命名檔案,但在傳統上會命名為 preferences.xml。 您通常只需要一個檔案,因為階層 (會開啟自己的設定清單) 中的子目錄是透過 PreferenceScreen 的巢狀執行個體進行宣告。

注意:如果您想要為設定建立多個面板的版面配置,則您需要為每個片段準備個別的 XML 檔案。

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>

此範例中有一個 CheckBoxPreference 和一個 ListPreference。這兩個項目都包含下列三個屬性:

android:key
需要這個屬性才能保留資料值的偏好設定。它會指定當系統將此設定值儲存於 SharedPreferences 時要使用的唯一索引鍵 (字串)。

只有在下列情況下不需要此屬性:偏好設定為 PreferenceCategoryPreferenceScreen,或者偏好設定指定 Intent 進行呼叫 (搭配 &lt;intent&gt; 元素) 或 Fragment 進行顯示 (搭配 android:fragment 屬性)。

android:title
這可為設定提供使用者可見的名稱。
android:defaultValue
這可指定系統在 SharedPreferences 檔案中應設定的初始值。您應該為所有設定提供預設值。

如需所有其他支援的屬性相關資訊,請參閱 Preference (及個別子類別) 文件。

圖 2.設定含有標題的類別。
1.類別以 <PreferenceCategory> 元素指定。
2.標題以 android:title 屬性指定。

如果您的設定清單超過約 10 個項目,您可以新增標題來定義設定群組,或在分開的畫面顯示這些群組。 這些選項在下列幾節有詳細的描述。

建立設定群組

如果您的清單有 10 個以上的項目,掃描、理解和處理這些項目對使用者而言可能會很困難。 如要解決這個問題,可將部分或所有設定分成不同的群組,有效地將一長串清單分成多個簡短的清單。 一組包含相關設定的群組可以下列其中一種方式顯示:

您可以使用上述一種或兩種分組技巧來整理您的應用程式設定。決定要使用哪種方法或如何分割設定時,您應該要依照 Android 設計設定指南中的指導方針進行。

使用標題

如果您想要在設定群組間使用標題區分 (如圖 2 所示),將 Preference 物件的每個群組放入 PreferenceCategory

例如:

<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>

使用子畫面

如果您想將設定群組放入子畫面 (如圖 3 所示),將 Preference 物件的群組放入 PreferenceScreen

圖 3.設定子畫面。&lt;PreferenceScreen&gt; 元素建立的項目會在選取時開啟獨立的清單以顯示巢狀設定。

例如:

<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>

使用意圖

在某些情況下,您可能會希望偏好設定項目開啟不同的 Activity 而不是設定畫面,例如,開啟網路瀏覽器以檢視網頁。 如要在使用者選取偏好設定項目時呼叫 Intent,請新增 &lt;intent&gt; 元素做為對應 &lt;Preference&gt; 元素的子項。

例如,您可以使用下列方法透過偏好設定項目開啟網頁:

<Preference android:title="@string/prefs_web_page" >
    <intent android:action="android.intent.action.VIEW"
            android:data="http://www.example.com" />
</Preference>

您可以使用下列屬性建立隱含和明確意圖:

android:action
根據 setAction() 方法指派的動作。
android:data
根據 setData() 方法指派的資料。
android:mimeType
根據 setType() 方法指派的 MIME 類型。
android:targetClass
根據 setComponent() 方法指派的元件名稱類別部分。
android:targetPackage
根據 setComponent() 方法指派的元件名稱封裝部分。

建立偏好設定 Activity

如要在 Activity 中顯示設定,延伸 PreferenceActivity 類別。這是傳統 Activity 類別的延伸,可根據 Preference 物件的階層顯示設定清單。當使用者進行變更時,PreferenceActivity 會自動保留與每個 Preference 關聯的設定。

注意:如果您針對 Android 3.0 及更新版本開發應用程式,您應該改為使用 PreferenceFragment。 前往下一個使用偏好設定片段章節。

最需要注意的一件事就是,不要在 onCreate() 呼叫期間載入檢視的版面配置。您應該要呼叫 addPreferencesFromResource(),將您在 XML 檔案中宣告的偏好設定新增到 Activity 中。 例如,下列為功能 PreferenceActivity 所需的基本程式碼:

public class SettingsActivity extends PreferenceActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
    }
}

事實上,這個程式碼對某些應用程式而言已經足夠,因為使用者修改偏好設定後,系統會將變更儲存到預設 SharedPreferences 檔案,當您需要檢查使用者設定時,您的其他應用程式元件就能進行讀取。 但是,許多應用程式需要更多一點程式碼,以接聽對偏好設定進行的變更。 如需在 SharedPreferences 檔案接聽變更的相關資訊,請參閱讀取偏好設定

使用偏好設定片段

如果您針對 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);
    }
    ...
}

之後,您可以將此片段新增至 Activity,做法與任何其他 Fragment 一樣。例如:

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new SettingsFragment())
                .commit();
    }
}

注意:PreferenceFragment 沒有自己的 Context 物件。 如果您需要 Context 物件,可以呼叫 getActivity()。 不過,必須小心,只能在片段附加到 Activity 時才能呼叫 getActivity()。 如果未附加片段,或是在生命週期期間中斷連結,getActivity() 將傳回 null。

設定預設值

您建立的偏好設定可能為應用程式定義了一些重要的行為,因此當使用者第一次開啟您的應用程式時,務必使用每個 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()

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

onCreate() 期間呼叫它,可確保您的應用程式正確地以預設值初始化,您的應用程式可能需要讀取這些預設值,才能判斷一些行為 (例如,使用行動網路時是否可下載資料)。

這個方法採用三種引數:

  • 您的應用程式 Context
  • 您要設定預設值之偏好設定 XML 檔案的資源 ID。
  • 布林值指出預設值是否要設定一次以上。

    如果為 false,系統只會在過去從未呼叫此方法時設定預設值 (或者預設值共用偏好設定檔案的 KEY_HAS_SET_DEFAULT_VALUES 為 false)。

只要將第三個引數設為 false,您可以在每次 Activity 啟動時很安全地呼叫此方法,而不會將使用者儲存的偏好設定重設為預設值。 不過,如果您將它設為 true,會將任何之前的值覆寫為預設值。

使用偏好設定標頭

在少數情況下,您可能會想將設定設計為只在第一個畫面顯示子畫面清單(與系統設定應用程式一樣,如圖 4 和 5 所示)。 當您為 Android 3.0 及更新版本開發這類設計時,您應該使用 Android 3.0 的新「標頭」功能,而不是使用巢狀 PreferenceScreen 元素建置子畫面。

如要使用標頭建置設定,您必須:

  1. 將每個設定群組分成不同的 PreferenceFragment 執行個體。也就是說,每個設定群組需要一個獨立的 XML 檔案。
  2. 建立一個 XML 標頭檔案,其中列出每個設定群組以及宣告哪些片段包含對應的設定清單。
  3. 延伸 PreferenceActivity 類別以託管您的設定。
  4. 實作 onBuildHeaders() 回呼以指定標頭檔案。

使用這個設計最大的好處在於在大螢幕執行時, PreferenceActivity 會自動顯示兩個面板的版面配置,如圖 4 所示。

即使應用程式支援的 Android 版本較 3.0 舊,您還是可以建置應用程式在新版的裝置上使用 PreferenceFragment 顯示兩個面板,同時仍然支援舊版裝置上的傳統多畫面階層 (請參閱使用偏好設定標頭支援舊版)。

圖 4.含標頭的兩個面板版面配置。
1.標頭以 XML 標頭檔案定義。
2.每個設定群組是由標頭檔案 &lt;header&gt; 元素指定的PreferenceFragment 所定義。

圖 5.含設定標頭的手機裝置。選取項目後,關聯的 PreferenceFragment 會取代標頭。

建立標頭檔案

您標頭清單中的每個設定群組是由根 &lt;preference-headers&gt; 元素中的單一 &lt;header&gt; 元素指定。 例如:

<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header
        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one" />
    <header
        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" >
        <!-- key/value pairs can be included as arguments for the fragment. -->
        <extra android:name="someKey" android:value="someHeaderValue" />
    </header>
</preference-headers>

有了 android:fragment 屬性,每個標頭會宣告當使用者選取標頭時,應該開啟的 PreferenceFragment 執行個體。

&lt;extras&gt; 元素可讓您將鍵值配對傳送到 Bundle 中的片段。片段可以透過呼叫 getArguments() 擷取引數。您可以因各種理由將引數傳送到片段,但其中一個最好的理由是針對每個群組重複使用 PreferenceFragment 中相同的子類別,並使用引數指定應載入片段的偏好設定 XML 檔案。

例如,當每個標頭使用 "settings" 索引鍵定義 &lt;extra&gt; 引數時,下列片段可在多個設定群組重複使用:

public static class SettingsFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String settings = getArguments().getString("settings");
        if ("notifications".equals(settings)) {
            addPreferencesFromResource(R.xml.settings_wifi);
        } else if ("sync".equals(settings)) {
            addPreferencesFromResource(R.xml.settings_sync);
        }
    }
}

顯示標頭

如要顯示偏好設定標頭,必須實作 onBuildHeaders() 回呼方法,並呼叫 loadHeadersFromResource()。 例如:

public class SettingsActivity extends PreferenceActivity {
    @Override
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.preference_headers, target);
    }
}

當使用者從標頭清單選取項目時,系統會開啟關聯的 PreferenceFragment

注意:使用偏好設定標頭時,PreferenceActivity 的子類別不需要實作 onCreate() 方法,這是因為 Activity 唯一需要做的工作就是載入標頭。

使用偏好設定標頭支援舊版

如果您應用程式支援的 Android 版本比 3.0 舊,您仍然可以在 Android 3.0 及更新版本執行時,使用標頭提供兩個面板的版面配置。 您只需要建立一個額外的偏好設定 XML 檔案,該檔案要使用行為與標頭項目 (供舊版 Android 使用) 一樣的基本 <Preference> 元素。

但是,不會開啟新的 PreferenceScreen,每個 <Preference> 元素會傳送一個 IntentPreferenceActivity,以指定要載入的偏好設定 XML 檔案。

例如,下列為使用 Android 3.0及更新版本的偏好設定標頭 XML 檔案 (res/xml/preference_headers.xml):

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header
        android:fragment="com.example.prefs.SettingsFragmentOne"
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one" />
    <header
        android:fragment="com.example.prefs.SettingsFragmentTwo"
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" />
</preference-headers>

而,這裡有個偏好設定檔案為 Android 3.0 以下版本提供相同的標頭 (res/xml/preference_headers_legacy.xml):

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <Preference
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one"  >
        <intent
            android:targetPackage="com.example.prefs"
            android:targetClass="com.example.prefs.SettingsActivity"
            android:action="com.example.prefs.PREFS_ONE" />
    </Preference>
    <Preference
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" >
        <intent
            android:targetPackage="com.example.prefs"
            android:targetClass="com.example.prefs.SettingsActivity"
            android:action="com.example.prefs.PREFS_TWO" />
    </Preference>
</PreferenceScreen>

因為 Android 3.0 已加入對 &lt;preference-headers&gt; 的支援,系統只會在 Androd 3.0 或更新版本執行時,才會呼叫 PreferenceActivity 中的 onBuildHeaders()。 如要載入「舊版」標頭檔案 (preference_headers_legacy.xml),您必須檢查 Android 版本,如果版本比 Android 3.0 更舊 (HONEYCOMB),呼叫 addPreferencesFromResource() 以載入舊版標頭檔案。 例如:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        // Load the legacy preferences headers
        addPreferencesFromResource(R.xml.preference_headers_legacy);
    }
}

// Called only on Honeycomb and later
@Override
public void onBuildHeaders(List<Header> target) {
   loadHeadersFromResource(R.xml.preference_headers, target);
}

最後一件要做的事,是處理傳送到 Activity 的 Intent,以識別要載入的偏好設定檔案。 擷取意圖的動作,並將它與偏好設定 XML &lt;intent&gt; 標籤中使用的已知動作字串進行比對:

final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    String action = getIntent().getAction();
    if (action != null && action.equals(ACTION_PREFS_ONE)) {
        addPreferencesFromResource(R.xml.preferences);
    }
    ...

    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        // Load the legacy preferences headers
        addPreferencesFromResource(R.xml.preference_headers_legacy);
    }
}

請注意,addPreferencesFromResource() 的連續呼叫會將所有偏好設定堆疊在單一清單中,因此請確定將條件鏈結到 else-if 陳述式時只會呼叫它一次。

讀取偏好設定

根據預設,透過呼叫靜態方法 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(),而且在 Activity 中實作介面可能對您來說會更為容易。 例如:

public class SettingsActivity extends PreferenceActivity
                              implements OnSharedPreferenceChangeListener {
    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
    ...

    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
        String key) {
        if (key.equals(KEY_PREF_SYNC_CONN)) {
            Preference connectionPref = findPreference(key);
            // Set summary to be the user-description for the selected value
            connectionPref.setSummary(sharedPreferences.getString(key, ""));
        }
    }
}

在此範例中,方法會檢查是否是針對已知的偏好設定索引鍵設定進行變更。它會呼叫 findPreference() 以取得變更的 Preference 物件,以便將項目摘要修改為使用者選取的描述。 也就是說,當設定為 ListPreference 或其他多選擇設定時,如果設定變更為顯示目前狀態 (如圖 5 顯示的休眠設定),您應該呼叫 setSummary()

注意:如 Android 設計文件中有關設定的說明所述,我們建議您在每次使用者變更偏好設定時更新 ListPreference 的摘要,以描述目前的設定。

為了在 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 的意圖篩選器。 例如:

<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>

這個意圖篩選器向系統指出,是 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);
    }
    ...
}

儲存設定值

您可以呼叫其中一個 Preference 類別的 persist*() 方法隨時儲存設定值,例如,如果設定值是整數,使用 persistInt(),或者使用 persistBoolean() 儲存布林值。

注意:每個 Preference 只能儲存一個資料類型,因此您必須使用適合您自訂 Preference 使用之資料類型的 persist*() 方法。

當您選擇保留設定時,可以依據您延伸的 Preference 類別加以設定。如果您延伸 DialogPreference,則應該只在對話方塊因正值結果 (使用者選取 [確定] 按鈕) 關閉時保留該值。

DialogPreference 關閉時,系統會呼叫 onDialogClosed() 方法。這個方法包括一個布林值引數,指定使用者結果是否為「正值」— 如果值為 true,則表示使用者選取正值按鈕,而您應該儲存新值。 例如:

@Override
protected void onDialogClosed(boolean positiveResult) {
    // When the user selects "OK", persist the new value
    if (positiveResult) {
        persistInt(mNewValue);
    }
}

在此範例中,mNewValue 是保留設定目前值的類別成員。 呼叫 persistInt() 可將值儲存到 SharedPreferences 檔案 (自動使用 XML 檔案中為此 Preference 指定的索引鍵)。

初始化目前值

當系統將您的 Preference 新增到畫面時,會呼叫 onSetInitialValue() 以通知您設定是否有持續值。 如果沒有持續值,這個呼叫會提供您預設值。

onSetInitialValue() 方法會傳送布林值 restorePersistedValue,以指出該設定的值是否已經持續。 如果是 true,則您應該呼叫其中一個 Preference 類別的 getPersisted*() 方法以擷取持續值,例如,如果為整數值,可使用 getPersistedInt()。 您通常都會想要擷取持續值,如此您可以正確的更新 UI 以反映之前儲存的值。

如果 restorePersistedValuefalse,則您應該使用在第二引數中傳送的預設值。

@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
    if (restorePersistedValue) {
        // Restore existing state
        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
    } else {
        // Set default state from the XML attribute
        mCurrentValue = (Integer) defaultValue;
        persistInt(mCurrentValue);
    }
}

在實際上沒有持續值或沒有索引鍵的情況下,每個 getPersisted*() 方法會採用指定要使用之預設值的引數。 在上述範例中,會使用本機常數來指定預設值,以避免 getPersistedInt() 無法傳回持續值。

注意:無法使用 defaultValue 作為 getPersisted*() 方法中的預設值,因為當 restorePersistedValuetrue 時,其值永遠是 null。

提供預設值

如果 Preference 類別的執行個體指定預設值 (包含 android:defaultValue 屬性),則系統會在將物件具現化時呼叫 onGetDefaultValue(),以擷取值。 您必須實作此方法,才能讓系統將預設值儲存在 SharedPreferences 中。 例如:

@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    return a.getInteger(index, DEFAULT_VALUE);
}

方法引數可以提供您所需的所有物件:屬性陣列,以及您必須擷取之 android:defaultValue 的索引位置。 您必須實作此方法以便從屬性擷取預設值的原因,是因為您必須在未定義值的情況為屬性指定一個本機預設值。

儲存和還原偏好設定狀態

如同版面配置中的 View,您的 Preference 子類別負責在 Activity 或片段重新啟動 (例如,當使用者旋轉螢幕時) 時,儲存和還原其狀態。 如要正確的儲存和還原 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);
}