本文原文地址,请参照阅读,若有疑问以原文为准:
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文件。例如:
@Overridepublic 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@Overridepublic 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";...@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) { // 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:
@Overrideprotected void onResume() { super.onResume(); getPreferenceScreen().getSharedPreferences() .registerOnSharedPreferenceChangeListener(this);}@Overrideprotected 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); } ...}
保存设置的值
你可以在任何时候调用Preference类的persist*()方法来保存设置的值,比如persistInt()用来保存整形,persistBoolean()用来保存布尔型。
注意:每一个Preference只能保存一个数据类型,因此你必须使用和你自定义的Preference相对应的persit*()方法。
什么时候你选择去保存这个设置取决于你扩展的Preference类,假如你扩展DialogPreference,你应当在只有对话框因为positive原因而关闭的时候才应该保存(用户选择ok按钮)。
当一个DialogPreference关闭的时候,系统调用onDialogClosed()方法。这个方法包含了一个布尔型参数,他是true的时候会指定用户的结果是“positive”,然后用户选择positive按钮,你就应当保存这个新的值,比如:
@Overrideprotected 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,你应当使用第二个参数所传递的默认值。
@Overrideprotected 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。例如:
@Overrideprotected 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()两个方法
@Overrideprotected 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;}@Overrideprotected 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);}
至此,该篇文章翻译结束。后期的博文中将会用到这篇文章中介绍的内容。