第5章 設(shè)置(Settings)本章不同于通常網(wǎng)絡(luò)上經(jīng)常使用的SharedPreference,而是從底層了解Preference。FAQ QQ群213821767 應(yīng)用程序通常包括允許用戶修改應(yīng)用程序的特性和行為的設(shè)置功能。例如,一些應(yīng)用程序允許用戶指定通知是否啟用或指定多久使用云同步數(shù)據(jù)。如果你想要為你的應(yīng)用程序提供設(shè)置,你應(yīng)該使用Android的Preference APIs來構(gòu)建統(tǒng)一的接口。本章的主角就是Preference,下面先讓我們看一下圖5-1:
圖5-1 這是android短信息應(yīng)用程序的設(shè)置界面截圖。它使用就是就是Preference 5.1 概述相比使用View對象來構(gòu)建用戶接main,設(shè)置是構(gòu)建Preference的子類。一個Preference對象是構(gòu)建一個單一設(shè)置的一個部分。每一個Preference作為一個item在list并為用戶修改設(shè)置提供了適當(dāng)?shù)慕缑?。例如,一個CheckBoxPreference創(chuàng)建一個用于顯示checkbox的list item,ListPreference創(chuàng)建一個選擇列表來顯示一個對話框的item。每一個Preference其實都以鍵值對的形式保存在你應(yīng)用程序的SharedPreferences文件中。當(dāng)用戶改變設(shè)置時,系統(tǒng)會更新SharedPreferences文件中的鍵值對。我們只需要讀取文件中的設(shè)置數(shù)據(jù)即可。SharedPreferences支持以下數(shù)據(jù)類型的保存: Boolean Float Int Long String String Set 因為你的應(yīng)用程序設(shè)置界面是使用Preference對象構(gòu)建的而不是view,你需要使用Activity或Fragment的子類來顯示設(shè)置列表: ◆ 如果你的應(yīng)用程序支持android 3.0以下版本,你必須使用PreferenceActivity類來構(gòu)建。 ◆ 如果高于或等于android 3.0版本,你可以使用PreferenceFragment。當(dāng)然你屏幕如果足夠大的話你還是可以使用PreferenceActivity創(chuàng)建雙面板布局來顯示多組設(shè)置 5.1.1 Preference 你應(yīng)用中的每一個設(shè)置都代表一個Preference對象。每一個Preference的子類包含一組核心的屬性,如允許你指定設(shè)置的標(biāo)題和默認(rèn)值這樣的屬性。每一個子類也提供自己的屬性和用戶界面。就想上面圖5-1那樣,每一個設(shè)置都是List View中的一個item,也是一個Preference對象。常見的Preference如下: CheckBoxPreference 用checkbox顯示一個item的設(shè)置是否為打開或關(guān)閉。他保存的是boolean值,true表示選中 ListPreference 以單選按鈕列表的形式打開一個對話框,保存的值能支持任意類型 EditTextPreference 使用EditText打開一個對話框。保存的值為一個String。 5.2 在XML中定義Preferences雖然你可以在運行時實例化新的Preference對象,但你也可以在XML中用Preference層級對象來定義。使用XML定義設(shè)置是首選,因為XML文件結(jié)構(gòu)更容易閱讀的并且更新也很簡單。此外,你的應(yīng)用程序的設(shè)置通常是預(yù)先確定的,但你仍然可以在運行時修改它們。每一個Preference子類都能使用XML節(jié)點來匹配聲明。如<CheckBoxPreference>。你必須在項目的res/xml目錄下保存這種XML文件。盡管你可以任意命名你的文件名字,但建議使用preferences.xml,方便以后識別自己寫的東西。注意如果你想要為你的設(shè)置創(chuàng)建多面板布局,那你需要為每一個fragment創(chuàng)建單獨的XML文件。 根節(jié)點的XML文件必須是一個< PreferenceScreen >元素。在這個元素中你可以添加每個Preference。每個你添加在< PreferenceScreen >元素下的子節(jié)點顯示為單一列表項的設(shè)置。如代碼清單5-1所示: 按 Ctrl+C 復(fù)制代碼 按 Ctrl+C 復(fù)制代碼
代碼清單5-1 在上面的例子中有一個CheckBoxPreference和一個ListPreference。這兩個items包含以下三個屬性: ◆android:key 這個屬性是必須的,對于一個preferences 來說是一個持久的數(shù)據(jù)值。當(dāng)在SharedPreferences中保存這個Setting值時這個指定唯一的key(一個字符串)是被系統(tǒng)使用的。但某些特殊情況,如preferences是一個PreferenceCategory或 PreferenceScreen,或者是一個XML中<Intent>調(diào)用時,又或者是一個Fragment顯示時(用android:fragment屬性),以上這些特殊情況下,這個key就不是必須的了。 ◆android:title 為設(shè)置提供了一個用戶可見的名稱。 ◆android:defaultValue 這指定初始值,系統(tǒng)應(yīng)該建立在SharedPreferences文件。你應(yīng)該為所有設(shè)置提供一個默認(rèn)值。 關(guān)于其他更多屬性,請直接查看Preference文檔。
圖 5-2 根據(jù)title的設(shè)置分類 當(dāng)你的列表設(shè)置超過大約10項,您可能想通過添加標(biāo)題定義分組設(shè)置或在一個單獨的屏幕顯示這些組。 5.2.1創(chuàng)建設(shè)置組(groups) 如果你列出10個或更多的設(shè)置,用戶可能會有些頭疼。這樣我們就可以使用分組。以下有兩種分組方法: ◆使用titles ◆使用subscreens 1. 使用titles 如果你想要根據(jù)標(biāo)題來提供分界線,請使用PreferenceCategory如代碼清單5-2所示: <PreferenceScreen xmlns:android="http://schemas./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>
代碼清單5-2 2. 使用subscreens
如果你想要放置設(shè)置組到一個subscreen中,請使用PreferenceScreen如圖5-3和代碼清單5-3:
圖 5-3 <PreferenceScreen xmlns:android="http://schemas./apk/res/android"> <!--打開一個subscreen --> <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" ... /> <!--打開另一個嵌套的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>
代碼清單5-3 5.2.2使用Intents 某些情況下, 你可能想要一個preference item來打開不同的activity而不是設(shè)置屏幕,就像一個web瀏覽器來查看一個web頁面。當(dāng)用戶選擇一個preference item時可以調(diào)用Intent來啟動。方法就是添加一個<intent>節(jié)點到<Preference>節(jié)點中。如代碼清單5-4所示: <Preference android:title="@string/prefs_web_page" > <intent android:action="android.intent.action.VIEW" android:data="http://www." /> </Preference>
代碼清單5-4
你能使用以下屬性創(chuàng)建隱式和顯式的intents: ◆android:action 如同setAction()方法一樣設(shè)置action ◆android:data 如同setData()方法一樣設(shè)置data ◆android:mimeType 如同setType()方法一樣設(shè)置MIME類型 ◆android:targetClass 如同setComponent()方法一樣設(shè)置組件類名 ◆android:targetPackage 如同setComponent()方法一樣設(shè)置組件包名 5.3 創(chuàng)建一個Preference Activity為了在Acitivity中顯示你的設(shè)置,你可以繼承PreferenceActivity類。這是擴展于傳統(tǒng)Activity的一個類,它基于Preference對象層級來顯示一個設(shè)置列表。當(dāng)用戶做出一個改變時PreferenceActivity能自動保存與每一個Preference相關(guān)的設(shè)置。注意:如果在3.0或以上系統(tǒng)版本中,你應(yīng)該使用PreferenceFragment。最重要的是要記住,你在onCreate()回調(diào)期間沒有加載一個視圖的布局。而是調(diào)用addPreferencesFromResource()來添加你定義的XML文件。例如代碼清單5-5所示: public class SettingsActivity extends PreferenceActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); } }
代碼清單5-5 只要用戶修改preference,系統(tǒng)將改變保存到一個默認(rèn)的SharedPreferences文件。 5.4 使用Preference Fragments如果你在android3.0或更高版本上開發(fā),你應(yīng)該使用PreferenceFragment來顯示Preference 對象列表。你不應(yīng)該在使用PreferenceActivity了。因為Fragments提供更為靈活的應(yīng)用程序結(jié)構(gòu)如代碼清單5-6所示: public static class SettingsFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); } ... }
代碼清單5-6 然后你能吧這個fragment添加到Activity,如代碼清單5-7所示: public class SettingsActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getFragmentManager().beginTransaction() .replace(android.R.id.content, new SettingsFragment()) .commit(); } }
代碼清單5-7 注意:一個PreferenceFragment沒有它自己的Context對象。如果你需要一個Content對象,你能調(diào)用getActivity()方法。然而,當(dāng)fragment沒有附加到activity中或者activity聲明周期結(jié)束時分離后,你使用getActivity()返回的將是null。 5.5 Setting的默認(rèn)值你創(chuàng)建preferences可能是為你的應(yīng)用程序定義一些重要的行為,所以當(dāng)用戶第一次打開你的應(yīng)用程序時,為每一個Preference相關(guān)的SharedPreferences 文件初始化默認(rèn)值是必要的。首先你必須為每一個Preference對象指定一個默認(rèn)值,你可以在XML文件中使用android:defaultValue屬性。例如代碼清單5-8所示: 按 Ctrl+C 復(fù)制代碼 按 Ctrl+C 復(fù)制代碼
代碼清單5-8 然后,在Main Activity里的onCreate()方法中調(diào)用一次setDefaultValues(),如代碼清單5-9所示: PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
代碼清單5-9 在onCreate()方法的最開始就可以執(zhí)行此方法,因為可能你的界面需要依據(jù)默認(rèn)值來設(shè)置一個屬性。這個方法中有三個參數(shù): 1.應(yīng)用程序的Context 2.Preference XML資源ID 3.這個boolean表示是否多次設(shè)置默認(rèn)值,當(dāng)然大部分情況下默認(rèn)值一般我們只需要設(shè)置一次,就傳false即可 5.6 使用Preference Headers在少數(shù)情況下,如用首次屏幕顯示的時候,你可能想要讓用戶先設(shè)置一些配置屬性。在android3.0或更高版本系統(tǒng)下,你可以使用新的“headers”功能來代替以前的subscreens的嵌套。使用headers步驟如下: 1. 單獨的每組設(shè)置作為獨立PreferenceFragment的實例。即,每組設(shè)置需要一個單獨的XML文件。 2. 創(chuàng)建一個XML頭文件,其中列出了每個設(shè)置組和聲明這fragment包含相應(yīng)的設(shè)置列表。 3. 擴展PreferenceActivity類來托管您的設(shè)置。 4. 實現(xiàn)onBuildHeaders()回調(diào)用來指定頭文件。 一個很棒的好處是,PreferenceActivity使用這個設(shè)計自動給出了雙欄布局如圖5-4大屏幕上運行時。 即使你的應(yīng)用程序支持Android 3.0以上的版本,你也可以使用PreferenceFragment來構(gòu)建應(yīng)用程序用于較新的設(shè)備 圖 5-4 使用headers的雙面板布局 1. headers使用一個xml heanders文件定義 2.每一組設(shè)置通過PreferenceFragment來定義,并且在<header>節(jié)點中指定 圖 5-5 這是一個手機設(shè)備,當(dāng)一個item選中時候,會調(diào)用PreferenceFragment 5.6.1創(chuàng)建headers文件 每一組設(shè)置你都可以在<preference-headers>跟節(jié)點中指定一個<header>節(jié)點,如代碼清單5-10所示: <?xml version="1.0" encoding="utf-8"?> <preference-headers xmlns:android="http://schemas./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" > <!—這個鍵值對可以當(dāng)成一個fragment的參數(shù)--> <extra android:name="someKey" android:value="someHeaderValue" /> </header> </preference-headers>
代碼清單5-10 使用android:fragment屬性,每一個header聲明一個PreferenceFragment實例,當(dāng)用戶選擇這個header時就會打開這個PreferenceFragment。<extras>節(jié)點允許你通過鍵值對的形式傳參,一般是使用Bundle。Fragment通過調(diào)用getArguments()來得到參數(shù)。關(guān)于參數(shù)的用途比較常見的就是為每一個組重用相同的PreferenceFragment子類并且使用參數(shù)還是制定你將要載入哪一個preferences XML文件。例如,下面是一個fragment,它被多個設(shè)置組重用,下面代碼清單5-11中在XML中使用了<extra>節(jié)點,key為“settings”: 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); } } }
代碼清單5-11 5.6.2顯示headers 為了顯示preference headers, 你必須實現(xiàn)onBuildHeaders()回調(diào)方法并且調(diào)用loadHeadersFromResource()如代碼清單5-12所示: public class SettingsActivity extends PreferenceActivity { @Override public void onBuildHeaders(List<Header> target) { loadHeadersFromResource(R.xml.preference_headers, target); } }
代碼清單5-12 當(dāng)用戶從headers列表中選擇一個item時,系統(tǒng)會打開相關(guān)的PreferenceFragment。注意當(dāng)使用preference headers時候,你的PreferenceActivity 子類不需要onCreate()方法,因為這個activity的任務(wù)僅僅是載入headers而已。 5.6.3老版本中支持Preference header 如果你的應(yīng)用程序既支持3.0以下的版本,也支持3.0以上的版本,要么3.0以上我們使用headers能提供雙面板布局。低于3.0的版本我們就可以添加額外的preferences XML但里面不是使用<header>而是使用<Preference>節(jié)點了。但每一個<Preference>都發(fā)送一個intent到PreferenceActivity。例如讓我們先看下3.0或以上版本的代碼清單5-13中(res/xml/preference_headers.xml): <preference-headers xmlns:android="http://schemas./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>
代碼清單5-13 然后讓我們再看下3.0以下版本的代碼清單5-14中(res/xml/preference_headers_legacy.xml): <PreferenceScreen xmlns:android="http://schemas./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>
代碼清單5-14 因為android3.0中支持<preference-headers>,僅在android3.0或更高版本中系統(tǒng)會在PreferenceActivity中調(diào)用onBuildHeaders()方法。當(dāng)然如果用戶的系統(tǒng)不是3.0(HONEYCOMB)的你就必須調(diào)用preference_headers_legacy.xml,然后調(diào)用addPreferencesFromResource()來載入xml文件。例如代碼清單5-15所示: @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { //低于3.0版本載入legacy preferences headers addPreferencesFromResource(R.xml.preference_headers_legacy); } } // 高于或等于3.0版本會調(diào)用此方法 @Override public void onBuildHeaders(List<Header> target) { loadHeadersFromResource(R.xml.preference_headers, target); }
代碼清單5-15 剩下的3.0以下版本就是通過處理Intent來識別哪個preference文件要被加載到Activity中來。所以需要檢索intent的action并與在preference XML<intent>下已知action字符串比較,如代碼清單5-16所示: 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) { // 載入3.0以下版本的legacy preferences文件 addPreferencesFromResource(R.xml.preference_headers_legacy); } }
代碼清單5-16 注意連續(xù)調(diào)用addPreferencesFromResource()會堆疊所有的preferences到一個單獨的列表中,所以確保他只調(diào)用一次,把它寫到else-if的條件分支下。 5.7 讀取Preferences默認(rèn)的,所有你應(yīng)用中的preferences會保存到一個文件中,你可以調(diào)用靜態(tài)方法PreferenceManager.getDefaultSharedPreferences()來獲得你保存的preferences。它將返回一個SharedPreferences對象包含所有你在PreferenceActivity中使用的Preference對象的鍵值對。例如,下面代碼清單5-17教你如何讀取: SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
代碼清單5-17 5.7.1監(jiān)聽Preference的改變 有些情況下你可能想只要一個preferences改變你就想得到通知。當(dāng)任意一個preferences發(fā)生改變時,為了取得一個回調(diào),我們可以實現(xiàn)SharedPreference.OnSharedPreferenceChangeListener這個接口并通過SharedPreferences.registerOnSharedPreferenceChangeListener()來注冊監(jiān)聽。這個接口只有一個回調(diào)方法,就是onSharedPreferenceChanged()你很容易就找到。如代碼清單5-18所示: 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); // 為選中的值設(shè)置用戶描述摘要。 connectionPref.setSummary(sharedPreferences.getString(key, "")); } } }
代碼清單5-18 這個例子中,onSharedPreferenceChanged()方法會檢測改變的設(shè)置是否為一個已知的preference key。如果是就會調(diào)用findPreference()來獲得Preference對象,并且這是改變后的對象你可以做你想做的事情,這里我們設(shè)置了一個摘要用于當(dāng)用戶選中時給出提示信息。其實這是一個比較好的方法,特別是多個被選中時,你可以通過現(xiàn)有的API讓用戶知道他們做了些什么并得到反饋。還有請注意記得在Activity聲明周期中的onPause()和 onResume()方法中注冊于注銷你的監(jiān)聽,如代碼清單5-19所示: @Override protected void onResume() { super.onResume(); getPreferenceScreen().getSharedPreferences() .registerOnSharedPreferenceChangeListener(this); } @Override protected void onPause() { super.onPause(); getPreferenceScreen().getSharedPreferences() .unregisterOnSharedPreferenceChangeListener(this); }
代碼清單5-19 5.8 管理網(wǎng)絡(luò)的使用從Android4.0開始,系統(tǒng)的設(shè)置應(yīng)用程序允許用戶能看到他們的應(yīng)用程序在前臺和后臺使用了多少網(wǎng)絡(luò)數(shù)據(jù)。用戶對于個別Apps能關(guān)閉使用后臺數(shù)據(jù)。為了避免用戶關(guān)閉你的程序從后臺訪問數(shù)據(jù)的功能,你應(yīng)該使用數(shù)據(jù)連接有效并允許用戶通過你應(yīng)用程序的設(shè)置來完善你應(yīng)用程序的數(shù)據(jù)使用。例如你可能允許用戶控制你的APP多久同步一次數(shù)據(jù),是否你的app僅在Wifi情況下才更新和下載,漫游情況下如何處理等。這樣的好處是給用戶更精準(zhǔn)的控制你的程序使用多少數(shù)據(jù),有這樣的精準(zhǔn)控制,用戶就不會在系統(tǒng)設(shè)置中直接把你的應(yīng)用訪問數(shù)據(jù)的功能給關(guān)掉。一旦你在PreferenceActivity 中添加了必要的preferences來控制你App的數(shù)據(jù),并養(yǎng)成了這種寫程序的習(xí)慣,那接下來我很樂意給你說明一下,你應(yīng)該在manifest文件中添加一個intent filter名字為ACTION_MANAGE_NETWORK_USAGE,如代碼清單5-20所示: <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>
代碼清單5-20 這個intent filter表明了這個activity告訴系統(tǒng)我能控制這個應(yīng)用程序的數(shù)據(jù)使用。因此,當(dāng)用戶在設(shè)置應(yīng)用中檢查你app使用了多少數(shù)據(jù)時,一個App設(shè)置按鈕便可用了,你點擊它會啟動PreferenceActivity然后讓用戶在精確控制你的app數(shù)據(jù)使用情況。 5.9 構(gòu)建自定義的PreferenceAndroid框架包含各種各樣的Preference子類允許你構(gòu)建自己的UI。然而你可能發(fā)現(xiàn)一個設(shè)置沒有好的內(nèi)置方案,如一個number picker或date picker。在這種情況下你需要創(chuàng)建自定義的preference,你需要繼承Preference類。當(dāng)然擴展Preference類后有一些重要的事情要做: ◆當(dāng)用戶選擇設(shè)置的時候指定用戶接口 ◆在適當(dāng)?shù)那闆r下保存setting的值 ◆當(dāng)進入我們的View時,使用當(dāng)前值或默認(rèn)值初始化Preference ◆當(dāng)被系統(tǒng)請求時,提供默認(rèn)值 ◆如果Preference提供它自己的UI(如一個對話框),保存和恢復(fù)狀態(tài)并處理生命周期的改變 。 5.9.1指定用戶界面 如果你直接擴展Preference類,當(dāng)用戶選擇一個item時,你需要實現(xiàn)onClick()用來定義action。其實大部分情況下就是直接繼承的DialogPreference顯示對話框的形式,這樣簡化的程序。如果你繼承了DialogPreference,你必須在類的構(gòu)造函數(shù)中調(diào)用setDialogLayoutResourcs()來指定布局。如代碼清單5-21所示: 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); } ... }
代碼清單5-21 5.9.2保存設(shè)置的值 你可以在任意時刻調(diào)用Preference類的persist*()方法來保存一個值,如設(shè)置的值為int,那么就使用persistInt()。這個方法用在對話框關(guān)閉的時候調(diào)用比較好,它會給用戶一個提示。當(dāng)點擊positive按鈕時,你就可以保存新的值。如代碼清單5-22所示: @Override protected void onDialogClosed(boolean positiveResult) { // 當(dāng)用戶選擇OK時,保存新的值 if (positiveResult) { persistInt(mNewValue); } }
代碼清單5-22 在上面這個例子中,mNewValue是一個類成員變量,并且是int型的。 5.9.3初始化當(dāng)前值 當(dāng)系統(tǒng)添加你的Preference 到屏幕時,它會調(diào)用onSetInitialValue() 來通知你的值是否是已經(jīng)存在的值。如果不存在,這個調(diào)用會提供一個默認(rèn)值。onSetInitialValue()方法通過一個boolean值來表明一個值是否已經(jīng)被存儲了。如果為true,你應(yīng)該把存儲的值給取出來,你可以使用getPersistedInt()這樣類似的方法取值。如果restorePersistedValue這個參數(shù)的值為false,那么你就可以使用第二個默認(rèn)值參數(shù)了,如代碼清單5-23所示: @Override protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { if (restorePersistedValue) { // 恢復(fù)狀態(tài) mCurrentValue = this.getPersistedInt(DEFAULT_VALUE); } else { // 從xml屬性中設(shè)置默認(rèn)狀態(tài) mCurrentValue = (Integer) defaultValue; persistInt(mCurrentValue); } }
代碼清單5-23 請注意這里當(dāng)restorePersistedValue為true時不能使用參數(shù)自帶的defaultValue,因為它的值為null,只有當(dāng)restorePersistedValue為false時才能使用。 5.9.4提供一個默認(rèn)值 如果Preference的實例指定一個默認(rèn)值(使用android:defaultValue屬性),那么當(dāng)Preference實例化對象時為了取得默認(rèn)值,系統(tǒng)會調(diào)用onGetDefaultValue()方法。你必須實現(xiàn)這個方法,這樣在系統(tǒng)保存默認(rèn)值到SharedPreferences的時候才能正確處理。例如代碼清單5-24所示: @Override protected Object onGetDefaultValue(TypedArray a, int index) { return a.getInteger(index, DEFAULT_VALUE); }
代碼清單5-24 方法參數(shù)提供你需要的一切:數(shù)組屬性和你需要檢索的android:defaultValue的索引位置,原因你必須實現(xiàn)這個方法來提取默認(rèn)值的屬性,因為您必須指定一個本地屬性的默認(rèn)值,以防值是未定義的。 5.9.5保存和恢復(fù)Preference的狀態(tài) 就像一個在布局中的View,你的Preference子類負(fù)責(zé)保存和恢復(fù)它的狀態(tài),以防止activity和fragment被重新啟動。妥善保存和恢復(fù)你Preference類的狀態(tài),你必須實現(xiàn)生命周期中的onSaveInstanceState()和onRestoreInstanceState()回調(diào)。Preference的狀態(tài)可以通過Parcelable接口實現(xiàn)。Android框架提供這樣一個對象,你可以作為入口點定義你對象的狀態(tài),比如Preference.BaseSavedState類。定義你Preference保存狀態(tài),你應(yīng)該繼承Preference.BaseSavedState類。你需要重寫一些方法來定義CREATOR對象。對于大部分應(yīng)用程序,你可以直接復(fù)制一下實現(xiàn)并做一些簡單的改變即可,如代碼清單5-25所示: private static class SavedState extends BaseSavedState { int value; public SavedState(Parcelable superState) { super(superState); } public SavedState(Parcel source) { super(source); // 獲得當(dāng)前preference的值 value = source.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); // 寫入preference的值 dest.writeInt(value); } 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]; } }; }
代碼清單5-25 下面是具體運用在onSaveInstanceState()和andonRestoreInstanceState()中的過程,如代碼清單5-26所示: @Override protected Parcelable onSaveInstanceState() { final Parcelable superState = super.onSaveInstanceState(); // 檢查Preference是否被保存過 if (isPersistent()) { //不需要保存實例狀態(tài),因為它是持久化的,使用父類狀態(tài) return superState; } // 創(chuàng)建自定義的BaseSavedState實例 final SavedState myState = new SavedState(superState); // 使用類成員變量賦值 myState.value = mNewValue; return myState; } @Override protected void onRestoreInstanceState(Parcelable state) { //檢查我們在onSaveInstanceState中是否保存過狀態(tài) if (state == null || !state.getClass().equals(SavedState.class)) { // 沒有保存狀態(tài),調(diào)用父類的方法 super.onRestoreInstanceState(state); return; } // 強制轉(zhuǎn)換到自定義的BaseSavedState SavedState myState = (SavedState) state; super.onRestoreInstanceState(myState.getSuperState()); // 應(yīng)用它的值到UI,以恢復(fù)UI狀態(tài) mNumberPicker.setValue(myState.value); }
代碼清單5-26
|
|
|