【原文:http://blog.csdn.net/u011960402/article/details/12569539 】
【感謝作者!】
=====================================================================================
本文原文地址,請參照閱讀,若有疑問以原文爲準:
http://developer.android.com/guide/topics/ui/settings.html#Custom
本文上半部翻譯博客地址:
http://blog.csdn.net/u011960402/article/details/12518529
筆者水平有限,翻譯之中難免有錯誤之處,敬請指出,不甚感激!
使用intents
在某些情況下,你希望一個preference item能打開一個不同的activity而不是一個設置,比如一個網絡瀏覽器器去打開一個網頁。在用戶點擊的時候,通過使用Intent回調可以實現,我們只要把一個<intent>元素設爲一個對應的<Preference>元素即可。
例子:下面就是你如何使用一個preference item來打開一個網頁:
- <Preference android:title="@string/prefs_web_page" >
- <intent android:action="android.intent.action.VIEW"
- android:data="http://www.example.com" />
- </Preference>
你可以使用如下屬性來顯式的或者隱式地創建intents:
android:action
指定的action,如同setAction()方法。
android:data
指定的數據,如同setData()方法。
android:mimeType
指定的MIME類型,如同setType()方法。
android:targetClass
組成名字的類部分,如同setComponent()方法。
android:targetPackage
組成名字的package部分,如同setComponent()方法。
創建一個PreferenceActivity
若想在一個activity中顯示你的設置,就擴展PreferenceActivity類吧。他是通過擴展傳統的Activity來顯示一個設置列表,當然這個列表是基於一個有層次的Preference objects的。在用戶做出改變的時候,這個PreferenceActivity會自動把每一個設置和他們的Preference關聯。
注意:假如你正在開發Android3.0及之後的應用,你應當使用PreferenceFragment。如何使用PreferenceFragments請參見下一個部分。
最重要的是在onCreate()的調用中,你不需要加載一個views的layout。取而代之的是,你可以調用addPreferencesFromResource()去把你聲明在XML文件中的preferences加入到activity中。比如,下面是一個有意義的PreferenceActivity所需要的最少的代碼:
- public class SettingsActivity extends PreferenceActivity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.preferences);
- }
- }
事實上,上面這些代碼對一些應用來說,已經足夠了。因爲,當用戶改變一個preference,系統會把這個改變保存到一個默認的SharedPreferences文件中,當別的應用原件需要檢查用戶的設置時,可以從中讀出來。當然,很多應用需要監聽preferences中發生的改變,這就需要更多的代碼來實現了。關於監聽SharedPreferences文件的改變,可以參見<讀取Preferences>這一章節。
使用Preference Fragments
假如你正在開發Android3.0及之後的應用,你應當使用PreferenceFragment去顯示你的Preference objects的列表。你可以把PreferenceFragment加入到任何一個activity中——你不需要使用PreferenceActivity。
不管你正在使用哪種activity,相比於使用activities,使用Fragments更加靈活。同時,若是有可能的話,我們建議你使用PreferenceFragment替代PreferenceActivity去控制你的設置的顯示。
PreferenceFragment的實現,和在onCreate()方法中用addPreferenceFromResourc()加載preference文件一樣的簡單。比如:
- 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);
- }
- ...
- }
和別的Fragment一樣,你可以把這個fragment加入到一個Activity中。比如:
- 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 object,加入你需要的話,可以調用getActivity()來得到。然而,需要注意的,只有fragment和一個activity相關聯你才能使用getActivity()函數。當fragment沒有關聯,或者已經解除了關聯了,getActivity()將會返回null。
設置默認值
你創建的preference有可能定義了一些對你應用非常重要的行爲,因此,在用戶第一次打開你的應用的時候,你需要通過相關的SharedPreferences文件來定義一些默認值。
你需要做的第一件事情就是通過XML文件中的android:defaultValue來爲每一個Preference object指定一個默認值。這個值可以是和對應Preference object相關聯的任何一個類型。例如:
- <!-- 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。
一個boolean變量用來表示默認值是否應當被設置多次。
當爲false的時候,系統只有在這個方面在之前沒有被調用過的情況纔會去設置默認值(或者在KEY_HAS_SET_DEFAULT_VALUES在默認的shared preference中被設置爲false的情況下也會重新被設置)
如果你把第三個參數設爲false,你可以每次都能安全地打開你的應用,而不需要把用戶保存的preference重設爲默認值。然而,假如你設爲true,你需要把這些值重新設爲默認值。
使用Preference Headers
在一些不常見的情況下,你可能需要實現一個在第一個屏幕上只顯示子窗口的列表這樣的設置(就像系統的設置一樣,如圖4和圖5所示)。當你在Android3.0及以上的版本上開發的時候,你可以使用一個新的稱之爲Header的特性,而不需要使用上文提到的內嵌PreferenceScreen元素來實現。
使用headers來創建設置,需要做到以下幾點:
1.把每一組的設置都分爲單獨的PreferenceFragment實例。也就是說,每一組設置需要一個單獨的XML文件。
2.創建一個XML headers文件,列出所有的設置組,同時聲明每一個設置列表對應的fragment。
3.擴展PreferenceActivity類去host你的設置。
4.實現onBuildHeaders()回調去指定headers文件。
使用PreferenceActivity的好處,就是在大屏的設備上(如pad)可以自適應爲兩個屏幕,如圖4所示。
即使你的應用支持Android3.0之前的版本,你可以使用PreferenceFragment來實現新舊設備的兼容。(具體見《支持舊版本的preferenc headers》這一章節)。
圖4,用headers實現的two-panelayout
1.headers是用XML headers文件定義的
2.每一個設置組都是由headers文件中<header>元素指定的PreferenceFragment定義。
圖5使用設置headers的手提設備,當一個item被選擇的時候,相應的PreferenceFragment會替代這個headers
創建headers文件
在列表中的每一個設置都是用根元素<preference-headers>下的一個單獨的<header>來指定的。例如:
- <?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可以指定你選擇header之後所顯示的PreferenceFragment。
<extras>允許你使用一個鍵值來匹配在Bundle中fragment。fragment可以通過調用getArgumnet()來接收到參數。你可能有很多種原因來想fragment傳遞參數,一個好的原因是爲每一個組重複使用同樣的PreferenceFragment子類,這就可以使用argument來指定fragment應當加載哪一個preferences XML 文件。
比如,下面就是一個fragment可以用於不同的設置組,每一個header都在<extras>參數中定義了一個”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);
- }
- }
- }
顯示headers
要想像是preferences header,你必須實現onBuildHeaders()回調方法,並且調用loadHeadersFromResource()。比如:
- public class SettingsActivity extends PreferenceActivity {
- @Override
- public void onBuildHeaders(List<Header> target) {
- loadHeadersFromResource(R.xml.preference_headers, target);
- }
- }
當用戶選擇header列表中的item時,系統會打開對應的PreferenceFragment。
注意:當使用preference headers的時候,你的preferenceActivity子類並不一定需要實現onCreate()方法,因爲唯一需要做的就是加載header而已。
preferenceheader如何支援舊的版本
假如你的應用支持android3.0以前的版本,你仍然可以使用headers來提供一個two-pane的layout在android3.0之後的版本上。你需要去做的就是去創建一個額外的preferences XML文件,使用基本的<Preference>元素。
不需要打開一個新的PreferenceScreen,每一個<Preference>元素髮送一個Intent給PreferenceActivity來指定哪一個preference XML 文件應該被加載。
例如,這裏有一個用於android3.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>
這裏有一個用於android3.0之前版本的preference文件(res/xml/preferenc_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>
因爲,只有在Android3.0及之後纔會支持<preference-headers>,這個系統調用onBuildHeader()只會這這些版本中才會生效。爲了加載“legacy”headers文件(preference_headers_legacy.xml),你必須堅持Android的版本,假如版本低於Android3.0,調用addPreferencesFromResource()去加載legacy header文件。例如:
- @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);
- }
唯一需要去做的是處理加載的preference文件傳入的Intent。所以,堅持intent的action,然後根據XML中<intent>來進行不同的處理。
- 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()將會堆積所有的preference到一個單獨的list中,因此,一定要確認它在elseif的狀態中只被調用了一次。
讀Preference
一般而言,所有你的app的preferences應該保存在一個你的應用通過調用static方法PreferenceManager.getDefaultSharedPreferences()都可以訪問的地方。他返回一個包含了所有你的PreferenceActivity使用的Preference objects相關聯鍵值對的SharedPreferences。
例如,下面就是你如何讀取你應用中別的activity的preference值的例子:
SharedPreferencessharedPref=PreferenceManager.getDefaultSharedPreferences(this);
String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
監聽preference的改變
你可能需要在用戶改變了preference後立即得到通知,爲了接受收改變的發生,需要實現SharedPreference.OnSharedPreferenceChangeListener接口,並且通過調用registerOnSharedPreferenceChangeListener()來註冊對SharedPreferences的監聽。
這個接口只有一個回調方法,onSharedPreferenceChanged(),你會發現其實他是很容易來實現這樣的接口的,例如:
- 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, ""));
- }
- }
- }
在這個例子中,這個方法會檢查設置的改變是否是一個已知的preference值。它調用findPreference()來得到改變的Preference object。因此他能夠根據用戶的選擇改變item的描述。也即是說,當這個設置是一個ListPreference或者別的多選擇的設置,當設置改變的時候,你可以調用setSummary()去顯示當前的狀態(如圖5所示的Sleep設置)。
注意:正如Android設置文檔中關於設置的描述一樣,我們推薦你在用戶改變preference的時候都更新ListPreference的summary以便更清晰地表示當前的設置。
對於一個具有良好生命週期的activity來說,我們推薦你在onResum()和onPause()調用中註冊和銷燬你的SharedPreferences.OnSharedPreferenceChangeListener:
- @Override
- protected void onResume() {
- super.onResume();
- getPreferenceScreen().getSharedPreferences()
- .registerOnSharedPreferenceChangeListener(this);
- }
- @Override
- protected void onPause() {
- super.onPause();
- getPreferenceScreen().getSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(this);
- }
管理網絡的使用
從Android4.0開始,系統的設置應用允許用戶去看他的應用在前臺和後臺使用了多少網絡流量。用戶能夠關閉一些app的後臺數據,爲了避免用戶關閉你的後臺數據的訪問,你可以讓用戶通過應用自己的設置來更好地管理數據的訪問和使用。
比如,你運行用戶來設置你的app多長時間同步一下數據,或者說是否設置爲只有WIFI的情況下才上傳/下載數據,或者漫遊的時候是否允許app使用數據等等。有了這些設置之後,用戶就很少回在系統設置中不需要你的數據訪問了,因爲他們可以更好的控制你的應用的數據訪問。
當你在你的PreferenceActivity中加入了必要的preference去控制app的數據訪問的時候。你應當在你的manifest文件中加入ACTION_MANAGE_NETWORK_USAGE的intent filter。例子:
- <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 filter向系統表明了他自己可以控制數據的使用。因此,當用戶在系統設置中檢查app使用的流量時,一個可見的設置按鈕顯示你的PreferenceActivity,用戶就可以精確知道你的app的流量。
創建一個自定義的Preference
Android的framework包含了一系列Preference的子類,並允許你使用他們來實現不同類型的UI。然後,你也許會想要一些沒有內建的設置類型,比如一個數字或者數據piker。這樣的情況下,你可以通過擴展Preference類或者其他子類來創建自定義的preference。
當你擴展Preference類的時候,有以下幾點非常重要:
當用戶選擇設置的時候要指定顯示的用戶接口
當佔用的時候要保存設置的值
當Preference顯示的時候需要初始化爲當前或者默認的值。
當系統由需求要提供默認的值
加入Preference提供它自己的UI(比如對話框),要能夠保存和恢復狀態以應對生命週期的改變(比如說用戶旋轉屏幕)
下面的部分就是講如何完成這些部分。
指定用戶接口
假如你直接擴展Preference類,你應當實現onClick()來定義用戶選擇item時的action。然而,大多數自定義的設置擴展與DialogPreference以用來顯示一個對話框,這其實會讓整個過程變得很簡單。當你擴展DialogPreference的時候,你必須在類創建的時候調用SetDialogLayoutResourcs()去指定對話框的layout。
例如,下面是一個自定義的DialogPreference的構造,他聲明瞭layout並指定了默認的positive和negative對話框按鈕的text。
- 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);
- }
- ...
- <strong>}
- </strong>
保存設置的值
你可以在任何時候調用Preference類的persist*()方法來保存設置的值,比如persistInt()用來保存整形,persistBoolean()用來保存布爾型。
注意:每一個Preference只能保存一個數據類型,因此你必須使用和你自定義的Preference相對應的persit*()方法。
什麼時候你選擇去保存這個設置取決於你擴展的Preference類,假如你擴展DialogPreference,你應當在只有對話框因爲positive原因而關閉的時候才應該保存(用戶選擇ok按鈕)。
當一個DialogPreference關閉的時候,系統調用onDialogClosed()方法。這個方法包含了一個布爾型參數,他是true的時候會指定用戶的結果是“positive”,然後用戶選擇positive按鈕,你就應當保存這個新的值,比如:
- @Override
- protected void onDialogClosed(boolean positiveResult) {
- // When the user selects "OK", persist the new value
- if (positiveResult) {
- persistInt(mNewValue);
- }
- }
在這個例子中,mNewValue是一個用來保存設置當前值的類成員。調用persistInt()把值保存到SharedPreferences文件中(會自動使用XML文件中這個Preferences指定的鍵值)
初始化當前的值
當系統把你的Preferences加入屏幕時,它會調用onSetInitialValue()去通知你是否有一個保存的值。假如沒有保存的值,這個調用會返回默認值。
onSetInitialVaulue()方法通過一個布爾型變量restorePersistedValue來表明這個設置是否已經有一個保存的值。假如返回true,你應當調用諸如getPersistedInt()之類的方法來得到這個保存的值。通常來說,你想通過這個保存的值來更新UI。
假如restorePersistedValue返回false,你應當使用第二個參數所傳遞的默認值。
- @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*()方法都會有一個參數,當沒有找到保存只是,這個參數將會是默認的值。在上面的例子中,一個本地的常量就用來指定爲默認值以防getPersisitedInt()不能返回保存的值。
警告:你不能使用defaultValue作爲getPersisted*()方法的默認值,因爲他的值在restorePersistedValue是true的情況下總是null。
提供一個默認值
假如你的Preference類的實例指定了一個默認值(通過android:defaultValue參數指定),系統可以在實例化的時候通過調用onGetDefaultValue()來檢索這個值。你必須實現這個方法以便系統能夠保存默認值到SharedPreferences。例如:
- @Override
- protected Object onGetDefaultValue(TypedArray a, int index) {
- return a.getInteger(index, DEFAULT_VALUE);
- }
這個方法的參數提供了任何你需要的東西:一個參數的數組和一個關於android:defaultValue的索引位置。你必須實現這個方法來獲得默認值的原因是你必須爲這個參數指定默認以防它沒有定義。
保存和恢復Preference的狀態
就像layout中的一個view一樣,你的Preference子類必須能夠保存和恢復他的狀態,以防止activity的重啓(比如用戶選擇屏幕的時候)。爲了更好的保存和恢復你的preference的狀態,你必須實現onSaveInstanceState()和onRestoreInstanceState()。
你的Preference的狀態是有一個實現了Parcelable接口的object來定義的。Android framework提供了這樣的object作爲一個開始點來定義你的狀態object:Preference.BaseSavedState類。
爲了定義你的Preference類如何保存他的狀態的,你應當擴展Preference.BaseSavedState類,你需要重寫很少的方法並且定義CREATOR object。
對大多數app而言,你能夠拷貝下面的實現,若是你的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加入到你的app(通常作爲你的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);
- }
至此,該篇文章翻譯結束。後期的博文中將會用到這篇文章中介紹的內容。