本章不同于通常網(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
相比使用View對象來構(gòu)建用戶接main,設(shè)置是構(gòu)建Preference的子類。一個(gè)Preference對象是構(gòu)建一個(gè)單一設(shè)置的一個(gè)部分。每一個(gè)Preference作為一個(gè)item在list并為用戶修改設(shè)置提供了適當(dāng)?shù)慕缑妗@?,一個(gè)CheckBoxPreference創(chuàng)建一個(gè)用于顯示checkbox的list item,ListPreference創(chuàng)建一個(gè)選擇列表來顯示一個(gè)對話框的item。每一個(gè)Preference其實(shí)都以鍵值對的形式保存在你應(yīng)用程序的SharedPreferences文件中。當(dāng)用戶改變設(shè)置時(shí),系統(tǒng)會更新SharedPreferences文件中的鍵值對。我們只需要讀取文件中的設(shè)置數(shù)據(jù)即可。SharedPreferences支持以下數(shù)據(jù)類型的保存:
Boolean
Float
Int
Long
String
String Set
因?yàn)槟愕膽?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)用中的每一個(gè)設(shè)置都代表一個(gè)Preference對象。每一個(gè)Preference的子類包含一組核心的屬性,如允許你指定設(shè)置的標(biāo)題和默認(rèn)值這樣的屬性。每一個(gè)子類也提供自己的屬性和用戶界面。就想上面圖5-1那樣,每一個(gè)設(shè)置都是List View中的一個(gè)item,也是一個(gè)Preference對象。常見的Preference如下:
CheckBoxPreference
用checkbox顯示一個(gè)item的設(shè)置是否為打開或關(guān)閉。他保存的是boolean值,true表示選中
ListPreference
以單選按鈕列表的形式打開一個(gè)對話框,保存的值能支持任意類型
EditTextPreference
使用EditText打開一個(gè)對話框。保存的值為一個(gè)String。
雖然你可以在運(yùn)行時(shí)實(shí)例化新的Preference對象,但你也可以在XML中用Preference層級對象來定義。使用XML定義設(shè)置是首選,因?yàn)閄ML文件結(jié)構(gòu)更容易閱讀的并且更新也很簡單。此外,你的應(yīng)用程序的設(shè)置通常是預(yù)先確定的,但你仍然可以在運(yùn)行時(shí)修改它們。每一個(gè)Preference子類都能使用XML節(jié)點(diǎn)來匹配聲明。如<CheckBoxPreference>。你必須在項(xiàng)目的res/xml目錄下保存這種XML文件。盡管你可以任意命名你的文件名字,但建議使用preferences.xml,方便以后識別自己寫的東西。注意如果你想要為你的設(shè)置創(chuàng)建多面板布局,那你需要為每一個(gè)fragment創(chuàng)建單獨(dú)的XML文件。
根節(jié)點(diǎn)的XML文件必須是一個(gè)< PreferenceScreen >元素。在這個(gè)元素中你可以添加每個(gè)Preference。每個(gè)你添加在< PreferenceScreen >元素下的子節(jié)點(diǎn)顯示為單一列表項(xiàng)的設(shè)置。如代碼清單5-1所示:
代碼清單5-1
在上面的例子中有一個(gè)CheckBoxPreference和一個(gè)ListPreference。這兩個(gè)items包含以下三個(gè)屬性:
◆android:key
這個(gè)屬性是必須的,對于一個(gè)preferences 來說是一個(gè)持久的數(shù)據(jù)值。當(dāng)在SharedPreferences中保存這個(gè)Setting值時(shí)這個(gè)指定唯一的key(一個(gè)字符串)是被系統(tǒng)使用的。但某些特殊情況,如preferences是一個(gè)PreferenceCategory或 PreferenceScreen,或者是一個(gè)XML中<Intent>調(diào)用時(shí),又或者是一個(gè)Fragment顯示時(shí)(用android:fragment屬性),以上這些特殊情況下,這個(gè)key就不是必須的了。
◆android:title
為設(shè)置提供了一個(gè)用戶可見的名稱。
◆android:defaultValue
這指定初始值,系統(tǒng)應(yīng)該建立在SharedPreferences文件。你應(yīng)該為所有設(shè)置提供一個(gè)默認(rèn)值。
關(guān)于其他更多屬性,請直接查看Preference文檔。
圖 5-2 根據(jù)title的設(shè)置分類
1. 通過指定<PreferenceCategory>節(jié)點(diǎn)的分類
2. 通過使用android:title指定title分類.
當(dāng)你的列表設(shè)置超過大約10項(xiàng),您可能想通過添加標(biāo)題定義分組設(shè)置或在一個(gè)單獨(dú)的屏幕顯示這些組。
5.2.1創(chuàng)建設(shè)置組(groups)
如果你列出10個(gè)或更多的設(shè)置,用戶可能會有些頭疼。這樣我們就可以使用分組。以下有兩種分組方法:
◆使用titles
◆使用subscreens
1. 使用titles
如果你想要根據(jù)標(biāo)題來提供分界線,請使用PreferenceCategory如代碼清單5-2所示:
<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>
代碼清單5-2
2. 使用subscreens
如果你想要放置設(shè)置組到一個(gè)subscreen中,請使用PreferenceScreen如圖5-3和代碼清單5-3:
圖 5-3
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <!--打開一個(gè)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" ... /> <!--打開另一個(gè)嵌套的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
某些情況下, 你可能想要一個(gè)preference item來打開不同的activity而不是設(shè)置屏幕,就像一個(gè)web瀏覽器來查看一個(gè)web頁面。當(dāng)用戶選擇一個(gè)preference item時(shí)可以調(diào)用Intent來啟動。方法就是添加一個(gè)<intent>節(jié)點(diǎn)到<Preference>節(jié)點(diǎn)中。如代碼清單5-4所示:
<Preference android:title="@string/prefs_web_page" > <intent android:action="android.intent.action.VIEW" android:data="http://www.example.com" /></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è)置組件包名
為了在Acitivity中顯示你的設(shè)置,你可以繼承PreferenceActivity類。這是擴(kuò)展于傳統(tǒng)Activity的一個(gè)類,它基于Preference對象層級來顯示一個(gè)設(shè)置列表。當(dāng)用戶做出一個(gè)改變時(shí)PreferenceActivity能自動保存與每一個(gè)Preference相關(guān)的設(shè)置。注意:如果在3.0或以上系統(tǒng)版本中,你應(yīng)該使用PreferenceFragment。最重要的是要記住,你在onCreate()回調(diào)期間沒有加載一個(gè)視圖的布局。而是調(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)將改變保存到一個(gè)默認(rèn)的SharedPreferences文件。
如果你在android3.0或更高版本上開發(fā),你應(yīng)該使用PreferenceFragment來顯示Preference 對象列表。你不應(yīng)該在使用PreferenceActivity了。因?yàn)镕ragments提供更為靈活的應(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
然后你能吧這個(gè)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
注意:一個(gè)PreferenceFragment沒有它自己的Context對象。如果你需要一個(gè)Content對象,你能調(diào)用getActivity()方法。然而,當(dāng)fragment沒有附加到activity中或者activity聲明周期結(jié)束時(shí)分離后,你使用getActivity()返回的將是null。
你創(chuàng)建preferences可能是為你的應(yīng)用程序定義一些重要的行為,所以當(dāng)用戶第一次打開你的應(yīng)用程序時(shí),為每一個(gè)Preference相關(guān)的SharedPreferences 文件初始化默認(rèn)值是必要的。首先你必須為每一個(gè)Preference對象指定一個(gè)默認(rèn)值,你可以在XML文件中使用android:defaultValue屬性。例如代碼清單5-8所示:
代碼清單5-8
然后,在Main Activity里的onCreate()方法中調(diào)用一次setDefaultValues(),如代碼清單5-9所示:
PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
代碼清單5-9
在onCreate()方法的最開始就可以執(zhí)行此方法,因?yàn)榭赡苣愕慕缑嫘枰罁?jù)默認(rèn)值來設(shè)置一個(gè)屬性。這個(gè)方法中有三個(gè)參數(shù):
1.應(yīng)用程序的Context
2.Preference XML資源ID
3.這個(gè)boolean表示是否多次設(shè)置默認(rèn)值,當(dāng)然大部分情況下默認(rèn)值一般我們只需要設(shè)置一次,就傳false即可
在少數(shù)情況下,如用首次屏幕顯示的時(shí)候,你可能想要讓用戶先設(shè)置一些配置屬性。在android3.0或更高版本系統(tǒng)下,你可以使用新的“headers”功能來代替以前的subscreens的嵌套。使用headers步驟如下:
1. 單獨(dú)的每組設(shè)置作為獨(dú)立PreferenceFragment的實(shí)例。即,每組設(shè)置需要一個(gè)單獨(dú)的XML文件。
2. 創(chuàng)建一個(gè)XML頭文件,其中列出了每個(gè)設(shè)置組和聲明這fragment包含相應(yīng)的設(shè)置列表。
3. 擴(kuò)展PreferenceActivity類來托管您的設(shè)置。
4. 實(shí)現(xiàn)onBuildHeaders()回調(diào)用來指定頭文件。
一個(gè)很棒的好處是,PreferenceActivity使用這個(gè)設(shè)計(jì)自動給出了雙欄布局如圖5-4大屏幕上運(yùn)行時(shí)。
即使你的應(yīng)用程序支持Android 3.0以上的版本,你也可以使用PreferenceFragment來構(gòu)建應(yīng)用程序用于較新的設(shè)備
圖 5-4 使用headers的雙面板布局
1. headers使用一個(gè)xml heanders文件定義
2.每一組設(shè)置通過PreferenceFragment來定義,并且在<header>節(jié)點(diǎn)中指定
圖 5-5 這是一個(gè)手機(jī)設(shè)備,當(dāng)一個(gè)item選中時(shí)候,會調(diào)用PreferenceFragment
5.6.1創(chuàng)建headers文件
每一組設(shè)置你都可以在<preference-headers>跟節(jié)點(diǎn)中指定一個(gè)<header>節(jié)點(diǎn),如代碼清單5-10所示:
<?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" > <!—這個(gè)鍵值對可以當(dāng)成一個(gè)fragment的參數(shù)--> <extra android:name="someKey" android:value="someHeaderValue" /> </header></preference-headers>
代碼清單5-10
使用android:fragment屬性,每一個(gè)header聲明一個(gè)PreferenceFragment實(shí)例,當(dāng)用戶選擇這個(gè)header時(shí)就會打開這個(gè)PreferenceFragment。<extras>節(jié)點(diǎn)允許你通過鍵值對的形式傳參,一般是使用Bundle。Fragment通過調(diào)用getArguments()來得到參數(shù)。關(guān)于參數(shù)的用途比較常見的就是為每一個(gè)組重用相同的PreferenceFragment子類并且使用參數(shù)還是制定你將要載入哪一個(gè)preferences XML文件。例如,下面是一個(gè)fragment,它被多個(gè)設(shè)置組重用,下面代碼清單5-11中在XML中使用了<extra>節(jié)點(diǎn),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, 你必須實(shí)現(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列表中選擇一個(gè)item時(shí),系統(tǒng)會打開相關(guān)的PreferenceFragment。注意當(dāng)使用preference headers時(shí)候,你的PreferenceActivity 子類不需要onCreate()方法,因?yàn)檫@個(gè)activity的任務(wù)僅僅是載入headers而已。
5.6.3老版本中支持Preference header
如果你的應(yīng)用程序既支持3.0以下的版本,也支持3.0以上的版本,要么3.0以上我們使用headers能提供雙面板布局。低于3.0的版本我們就可以添加額外的preferences XML但里面不是使用<header>而是使用<Preference>節(jié)點(diǎn)了。但每一個(gè)<Preference>都發(fā)送一個(gè)intent到PreferenceActivity。例如讓我們先看下3.0或以上版本的代碼清單5-13中(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>
代碼清單5-13
然后讓我們再看下3.0以下版本的代碼清單5-14中(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>
代碼清單5-14
因?yàn)閍ndroid3.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所示:
@Overridepublic 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)用此方法@Overridepublic void onBuildHeaders(List<Header> target) { loadHeadersFromResource(R.xml.preference_headers, target);}
代碼清單5-15
剩下的3.0以下版本就是通過處理Intent來識別哪個(gè)preference文件要被加載到Activity中來。所以需要檢索intent的action并與在preference XML<intent>下已知action字符串比較,如代碼清單5-16所示:
final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";... @Overridepublic 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到一個(gè)單獨(dú)的列表中,所以確保他只調(diào)用一次,把它寫到else-if的條件分支下。
默認(rèn)的,所有你應(yīng)用中的preferences會保存到一個(gè)文件中,你可以調(diào)用靜態(tài)方法PreferenceManager.getDefaultSharedPreferences()來獲得你保存的preferences。它將返回一個(gè)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的改變
有些情況下你可能想只要一個(gè)preferences改變你就想得到通知。當(dāng)任意一個(gè)preferences發(fā)生改變時(shí),為了取得一個(gè)回調(diào),我們可以實(shí)現(xiàn)SharedPreference.OnSharedPreferenceChangeListener這個(gè)接口并通過SharedPreferences.registerOnSharedPreferenceChangeListener()來注冊監(jiān)聽。這個(gè)接口只有一個(gè)回調(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
這個(gè)例子中,onSharedPreferenceChanged()方法會檢測改變的設(shè)置是否為一個(gè)已知的preference key。如果是就會調(diào)用findPreference()來獲得Preference對象,并且這是改變后的對象你可以做你想做的事情,這里我們設(shè)置了一個(gè)摘要用于當(dāng)用戶選中時(shí)給出提示信息。其實(shí)這是一個(gè)比較好的方法,特別是多個(gè)被選中時(shí),你可以通過現(xiàn)有的API讓用戶知道他們做了些什么并得到反饋。還有請注意記得在Activity聲明周期中的onPause()和 onResume()方法中注冊于注銷你的監(jiān)聽,如代碼清單5-19所示:
@Overrideprotected void onResume() { super.onResume(); getPreferenceScreen().getSharedPreferences() .registerOnSharedPreferenceChangeListener(this);} @Overrideprotected void onPause() { super.onPause(); getPreferenceScreen().getSharedPreferences() .unregisterOnSharedPreferenceChangeListener(this);}
代碼清單5-19
從Android4.0開始,系統(tǒng)的設(shè)置應(yīng)用程序允許用戶能看到他們的應(yīng)用程序在前臺和后臺使用了多少網(wǎng)絡(luò)數(shù)據(jù)。用戶對于個(gè)別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文件中添加一個(gè)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
這個(gè)intent filter表明了這個(gè)activity告訴系統(tǒng)我能控制這個(gè)應(yīng)用程序的數(shù)據(jù)使用。因此,當(dāng)用戶在設(shè)置應(yīng)用中檢查你app使用了多少數(shù)據(jù)時(shí),一個(gè)App設(shè)置按鈕便可用了,你點(diǎn)擊它會啟動PreferenceActivity然后讓用戶在精確控制你的app數(shù)據(jù)使用情況。
Android框架包含各種各樣的Preference子類允許你構(gòu)建自己的UI。然而你可能發(fā)現(xiàn)一個(gè)設(shè)置沒有好的內(nèi)置方案,如一個(gè)number picker或date picker。在這種情況下你需要創(chuàng)建自定義的preference,你需要繼承Preference類。當(dāng)然擴(kuò)展Preference類后有一些重要的事情要做:
◆當(dāng)用戶選擇設(shè)置的時(shí)候指定用戶接口
◆在適當(dāng)?shù)那闆r下保存setting的值
◆當(dāng)進(jìn)入我們的View時(shí),使用當(dāng)前值或默認(rèn)值初始化Preference
◆當(dāng)被系統(tǒng)請求時(shí),提供默認(rèn)值
◆如果Preference提供它自己的UI(如一個(gè)對話框),保存和恢復(fù)狀態(tài)并處理生命周期的改變 。
5.9.1指定用戶界面
如果你直接擴(kuò)展Preference類,當(dāng)用戶選擇一個(gè)item時(shí),你需要實(shí)現(xiàn)onClick()用來定義action。其實(shí)大部分情況下就是直接繼承的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è)置的值
你可以在任意時(shí)刻調(diào)用Preference類的persist*()方法來保存一個(gè)值,如設(shè)置的值為int,那么就使用persistInt()。這個(gè)方法用在對話框關(guān)閉的時(shí)候調(diào)用比較好,它會給用戶一個(gè)提示。當(dāng)點(diǎn)擊positive按鈕時(shí),你就可以保存新的值。如代碼清單5-22所示:
@Overrideprotected void onDialogClosed(boolean positiveResult) { // 當(dāng)用戶選擇OK時(shí),保存新的值 if (positiveResult) { persistInt(mNewValue); }}
代碼清單5-22
在上面這個(gè)例子中,mNewValue是一個(gè)類成員變量,并且是int型的。
5.9.3初始化當(dāng)前值
當(dāng)系統(tǒng)添加你的Preference 到屏幕時(shí),它會調(diào)用onSetInitialValue() 來通知你的值是否是已經(jīng)存在的值。如果不存在,這個(gè)調(diào)用會提供一個(gè)默認(rèn)值。onSetInitialValue()方法通過一個(gè)boolean值來表明一個(gè)值是否已經(jīng)被存儲了。如果為true,你應(yīng)該把存儲的值給取出來,你可以使用getPersistedInt()這樣類似的方法取值。如果restorePersistedValue這個(gè)參數(shù)的值為false,那么你就可以使用第二個(gè)默認(rèn)值參數(shù)了,如代碼清單5-23所示:
@Overrideprotected 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í)不能使用參數(shù)自帶的defaultValue,因?yàn)樗闹禐閚ull,只有當(dāng)restorePersistedValue為false時(shí)才能使用。
5.9.4提供一個(gè)默認(rèn)值
如果Preference的實(shí)例指定一個(gè)默認(rèn)值(使用android:defaultValue屬性),那么當(dāng)Preference實(shí)例化對象時(shí)為了取得默認(rèn)值,系統(tǒng)會調(diào)用onGetDefaultValue()方法。你必須實(shí)現(xiàn)這個(gè)方法,這樣在系統(tǒng)保存默認(rèn)值到SharedPreferences的時(shí)候才能正確處理。例如代碼清單5-24所示:
@Overrideprotected Object onGetDefaultValue(TypedArray a, int index) { return a.getInteger(index, DEFAULT_VALUE);}
代碼清單5-24
方法參數(shù)提供你需要的一切:數(shù)組屬性和你需要檢索的android:defaultValue的索引位置,原因你必須實(shí)現(xiàn)這個(gè)方法來提取默認(rèn)值的屬性,因?yàn)槟仨氈付ㄒ粋€(gè)本地屬性的默認(rèn)值,以防值是未定義的。
5.9.5保存和恢復(fù)Preference的狀態(tài)
就像一個(gè)在布局中的View,你的Preference子類負(fù)責(zé)保存和恢復(fù)它的狀態(tài),以防止activity和fragment被重新啟動。妥善保存和恢復(fù)你Preference類的狀態(tài),你必須實(shí)現(xiàn)生命周期中的onSaveInstanceState()和onRestoreInstanceState()回調(diào)。Preference的狀態(tài)可以通過Parcelable接口實(shí)現(xiàn)。Android框架提供這樣一個(gè)對象,你可以作為入口點(diǎn)定義你對象的狀態(tài),比如Preference.BaseSavedState類。定義你Preference保存狀態(tài),你應(yīng)該繼承Preference.BaseSavedState類。你需要重寫一些方法來定義CREATOR對象。對于大部分應(yīng)用程序,你可以直接復(fù)制一下實(shí)現(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
下面是具體運(yùn)用在onSaveInstanceState()和andonRestoreInstanceState()中的過程,如代碼清單5-26所示:
@Overrideprotected Parcelable onSaveInstanceState() { final Parcelable superState = super.onSaveInstanceState(); // 檢查Preference是否被保存過 if (isPersistent()) { //不需要保存實(shí)例狀態(tài),因?yàn)樗浅志没?使用父類狀態(tài) return superState; } // 創(chuàng)建自定義的BaseSavedState實(shí)例 final SavedState myState = new SavedState(superState); // 使用類成員變量賦值 myState.value = mNewValue; return myState;} @Overrideprotected void onRestoreInstanceState(Parcelable state) { //檢查我們在onSaveInstanceState中是否保存過狀態(tài) if (state == null || !state.getClass().equals(SavedState.class)) { // 沒有保存狀態(tài),調(diào)用父類的方法 super.onRestoreInstanceState(state); return; } // 強(qiáng)制轉(zhuǎn)換到自定義的BaseSavedState SavedState myState = (SavedState) state; super.onRestoreInstanceState(myState.getSuperState()); // 應(yīng)用它的值到UI,以恢復(fù)UI狀態(tài) mNumberPicker.setValue(myState.value);}
代碼清單5-26