原创

Android官方文档之User Interface(Settings)

设置页面(Settings)用于修改、保存app的特性或行为 等。如某些新闻app可以在设置页面设置其白天/夜间模式、清除缓存、是否允许推送通知等。如下图所示。
如需为应用提供设置页面,应使用Android SDK中的Preference这个API。本文将介绍如何使用这个API构建app的设置页面,如需访问官方原文,您可以点击这个链接:《Settings》

概览(Overview)

从传统上说,一般使用Layout布局资源为Activity、Fragment绑定可视化界面,而使用Preference及其子类也可以定义XML文件。Preference对象可以在一个设置页面中作为一个模块。每一个Preference都可以实现一个特殊的设置功能项。比如说,CheckBoxPreference类可以提供一个列表项,项中包含一个CheckBox控件。而ListPreference可创建一个包含列表项的对话框。

每一个Preference都存储了一个键值对,该键值对默认使用SharedPreferences 存储。当用户修改了某些设置后,系统会更新SharedPreferences 的对应值。一般情况下,SharedPreferences 存储的这些键值对无需您主动访问,除非您需要通过某些键值对来修改app的某些行为。

SharedPreferences 可以存储以下类型的值(由于SharedPreferences存储的是XML格式的文件,所以只能存储一些基础类型的值、String及String数组):

  • Boolean

  • Float

  • Int

  • Long

  • String

  • String[] (String数组)

由于设置页面使用的是Preferences布局,而并没有使用传统的Layout,所以相应的Activity或Fragment也应该是特殊的Activity或Fragment。

  • 如果您的设备低于Android 3.0,那么请使用PreferenceActivity类。

  • 如果您的设备为Android 3.0及以上版本,那么使用Activity 类就可以,而Fragment需使用PreferenceFragment

Preferences类(Preferences)

常见的Preferences类如下:

  • CheckBoxPreference:包含一个列表项,项中有一个CheckBox控件。

  • ListPreference:包含了若干个 radio buttons的对话框。

  • EditTextPreference:包含一个EditText 的对话框。

当然,内置的Preferences不能满足所有需求,这时需要自定义Preferences类。

在XML中定义Preferences (Defining Preferences in XML)

所有Preferences 均有标签与之对应如CheckBoxPreference对应<CheckBoxPreference>标签。

xml文件应存储于res/xml/目录下,通常命名为 preferences.xml,且只定义一个文件即可。
XML 文件的根节点必须是一个 <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:大多情况下,该属性不可缺省。它指定该Preference的键名,该键是一个String类型且必须唯一。当然,在<PreferenceCategory><PreferenceScreen>、指定了<intent>标签的Preference、以及指定了android:fragment属性的Preference中可以不指定android:key属性。

  • android:title:Preference标签的标题。

  • android:defaultValue:缺省值。

当创建了超过10个Preference项时,您可能希望多创建一个Preference设置组( groups of settings )以便归类显示,并为该组起一个名字。如下图所示:

Android官方文档之User Interface(Settings)

创建设置组(Creating setting groups)

您可以将任意个具有相似功能的Preference项设置为一个组。创建设置组有两种方式:

  • 使用标题(Using titles)

  • 使用子屏幕(Using subscreens)

使用标题(Using titles)

如果您需要为Preferences项设置一个组同时为其设置一个标题,那么您可以使用<PreferenceCategory>,该标签表示一个Preferences组,为该标签内设置Preferences子标签即是组中的项。比如:

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

使用子屏幕(Using subscreens)

当点击了某个Preference项后,可以跳转到另一屏Preference组中(见下图所示)——为实现这一功能,您需要在<PreferenceScreen>标签中嵌套<PreferenceScreen>标签,内标签就表示另一屏Preference组。

Android官方文档之User Interface(Settings)

XML示例如下所示:

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

使用 intents(Using intents)

有些时候,当您点击了某个Preference项时,启动一个Activity而不是另一屏Preference组。为实现这个功能,您需要在Preference标签中添加<intent>标签以指定启动Activity的action和 data等属性:
如需启动一个网页,示例如下:

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

<intent>标签同时支持显式启动和隐式启动,其中用于隐式启动的属性为:

  • android:action:相当于代码中的setAction();

  • android:data:相当于代码中的setData();

  • android:mimeType:相当于代码中的setType();

显式启动的属性为:

  • android:targetClass:启动目标组件类名的字节码,相当于代码中的setComponent();

  • android:targetPackage:启动目标组件所在包的包名,相当于setComponent();

创建Preference Activity(Creating a Preference Activity)

为了兼容Android3.0之前的版本,您的Activity需要继承PreferenceActivity(若只兼容Android 3.0及以上版本,只需继承Activity即可)。

为了绑定创建好的Preferences的XML视图文件,需要onCreate()方法中调用addPreferencesFromResource()方法并将xml文件传入:

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

使用Preference Fragment(Using Preference Fragments)

如需将Preference绑定至Fragment,您的Fragment应继承自PreferenceFragment (支持Android 3.0及以后版本),并且可以将这个Fragment应用于任何Activity上(不一定非得是PreferenceActivity)。

与Activity一样,您只需在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);     }     ... }

再将这个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();     } }

!请注意:Fragment并不是一个Context,如需在Fragment中使用Context对象,应调用Fragment的getActivity()获得其宿主Activity实例的引用,但只有在Fragment的onAttach()方法执行完毕以后,getActivity()才能获取Activity实例的引用,在这之前或onDetach()回调后,getActivity()均返回null。(However, be careful to call getActivity() only when the fragment is attached to an activity. When the fragment is not yet attached, or was detached during the end of its lifecycle, getActivity() will return null)

设置缺省值(Setting Default Values)

某些Preferences项非常重要,这需要您为它设置缺省值,以便在第一次打开app时使用这个缺省值。

配置缺省值的方式是在XML文件中设置android:defaultValue属性。属性值的类型对应Preference的类型,如下所示:

 <!-- default value is a boolean --> <CheckBoxPreference  android:defaultValue="true" ... />  <!-- default value is a string --> <ListPreference  android:defaultValue="@string/pref_syncConnectionTypes_default" ... />

接着在onCreate()中、或其他应用的任何Activity可能启动本app的第一时间,调用方法:

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

在onCreate()方法中调用该方法可以确保您的应用在启动时正确设置缺省值。而程序可能需要这些缺省值来确定某些行为。(如是否在蜂窝网络中下载数据)

方法有三个参数:

  • Context;

  • preference的XML ID;

  • 若设为false,表示仅在应用第一次调用该方法时,系统才会指定默认值,若设为true,表示每次启动activity时,默认值都会覆盖掉原来设置的值。

使用Preference标头(Using Preference Headers)

在少数情况下,您需要为自己的app设置子Preference,就像下面两图展示的一样。而在Android 3.0版本及以后,Android推出了“headers”的概念,这使得您不用为了分屏显示设置项而嵌套Preference。

Android官方文档之User Interface(Settings)

Android官方文档之User Interface(Settings)

为了为设置创建标头(headers),您应当:

  1. 将每一屏的设置布局分别绑定至一个PreferenceFragment上,也就是说,每一屏Preference设置页均有各自的XML布局文件。

  2. 创建一个XML标头文件,用于罗列出各页面的设置组,同时声明各自设置页面是由哪个fragment包含的。

  3. 继承PreferenceActivity 类,为其布局添加放置Fragment的位置。

    4.实现onBuildHeaders() 回调方法,来制定标头文件。

创建标头文件的示例(Creating the headers file)

标头XML文件的根标签是 <preference-headers> ,每一个子标签<header>包含了一个fragment:

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

其中有一个<extras>标签,该标签有一个键属性(android:name)和一个值(android:value)属性,键值对用于向fragment传递一个bundle参数,可以通过getArguments()方法获得。下面的例子便是通过“settings”键获得了对应的属性值,并通过该属性值加载不同的fragment:

 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);         }     } }

显示标头(Displaying the headers)

为了显示标头,需要在回调Activity的onBuildHeaders()方法,并调用loadHeadersFromResource()方法,比如:

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

每当用户点击某个设置项时,系统会打开相应的fragment。

若使用了标头,则无需再PreferenceActivity 中回调onCreate()方法。

读取Preferences(Reading Preferences)

在默认情况下,您的app中所有的Preference设置均保存在一个文件中,该文件可以在程序的任何位置调用PreferenceManager.getDefaultSharedPreferences()方法获得这个文件,该方法返回一个 SharedPreferences 对象的引用,该对象包含了所有在PreferenceActivity中设置过的键值对:

 SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");

该示例演示了在同一应用程序的其他Activity中获得PreferenceActivity中的设置值。

监听preference 设置项的变化(Listening for preference changes)

为了监听preference 设置项的变化,需要实现SharedPreference.OnSharedPreferenceChangeListener接口,并调用 registerOnSharedPreferenceChangeListener()方法为SharedPreferences 注册监听器。

在接口SharedPreference.OnSharedPreferenceChangeListener中只包含一个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, ""));         }     } }

调用findPreference()方法传入一个key,系统可以返回包含了该key所对应的Preference实例,该实例的选项值是修改了设置以后的值,这样就可以用来做相应的修改操作。

建议您在 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);

管理网络使用情况(Managing Network Usage)

从Android 4.0开始,用户可以通过设置页面了解自己的应用在前台和后台使用网络的情况。用户可以根据这些信息禁用掉app在后台使用网络。为了避免用户禁止您的应用从后台访问数据,应该有效地使用数据连接,并允许用户通过应用设置优化应用的数据使用。

比如您可以允许用户控制应用同步数据的频率、控制应用是否仅在有 Wi-Fi 时才执行上传/下载操作、以及以及控制应用能否在漫游时使用数据 等(how often your app syncs data, whether your app performs uploads/downloads only when on Wi-Fi,whether your app uses data while roaming, etc)。

在PreferenceActivity 中添加必要的首选项来控制应用的数据使用习惯后,应在清单文件中为 ACTION_MANAGE_NETWORK_USAGE 添加 Intent 过滤器。例如:

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

当用户从系统的“设置”应用检查应用所使用的数据量时,可以使用“查看应用设置”按钮启动 PreferenceActivity,这样,用户就能够优化应用使用的数据量。

创建定制的Preference选项(Building a Custom Preference)

可以通过扩展 Preference 类或其他子类之一来创建自定义首选项。

扩展 Preference 类时,您需要执行以下几项重要操作:

  • 指定在用户选择设置时显示的用户界面(Specify the user interface that appears when the user selects the settings);

  • 适时保存设置的值(Save the setting’s value when appropriate);

  • 使用显示的当前(默认)值初始化 Preference(Initialize the Preference with the current (or default) value when it comes into view);

  • 在系统请求时提供默认值(在系统请求时提供默认值);

  • 如果 Preference 提供自己的 UI(例如对话框),请保存并恢复状态以处理生命周期变更(例如,用户旋转屏幕)。(If the Preference provides its own UI (such as a dialog), save and restore the state to handle lifecycle changes (such as when the user rotates the screen))

这面将一一展示每一步骤:

指定用户界面(Specifying the user interface)

如果您要直接扩展 Preference 类,则需要实现 onClick() 来定义在用户选择该项时发生的操作,不过,大多数自定义设置都会扩展 DialogPreference 以显示对话框,从而简化这一过程。扩展 DialogPreference 时,必须在类构造函数中调用 setDialogLayoutResourcs() 来指定对话框的布局(you must call setDialogLayoutResourcs() during in the class constructor to specify the layout for the dialog)。

自定义 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);     }     ... }

保存设置值(Saving the setting’s value)

如果设置的值为整型数或是用于保存布尔值的 persistBoolean(),则可通过调用 Preference 类的一个 persist*() 方法(如 persistInt())随时保存该值。

每个 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 文件。

初始化当前值(Initializing the current value)

系统将 Preference 添加到屏幕时,会调用 onSetInitialValue() 来通知您设置是否具有保留值。如果没有保留值,则此调用将为您提供默认值。

onSetInitialValue() 方法传递一个布尔值 (restorePersistedValue),以指示是否已为该设置保留值。 如果值为 true,则应通过调用 Preference 类的一个 getPersisted*() 方法(如整型值对应的 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*() 方法均采用一个参数,用于指定在实际上没有保留值或该键不存在时所要使用的默认值。在上述示例中,当 getPersistedInt() 不能返回保留值时,局部常量用于指定默认值。

您不能使用 defaultValue 作为 getPersisted*() 方法中的默认值,因为当 restorePersistedValue 为 true 时,其值始终为 null。

提供默认值(Providing a default value)

如果 Preference 类的实例指定一个默认值(使用 android:defaultValue 属性),则在实例化对象以检索该值时,系统会调用 onGetDefaultValue()。您必须实现此方法,系统才能将默认值保存在 SharedPreferences 中。 例如:

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

方法参数可提供您所需的一切:属性的数组和 android:defaultValue(必须检索的值)的索引位置。之所以必须实现此方法以从该属性中提取默认值,是因为您必须为此属性指定在未定义属性值时所要使用的局部默认值。

保存和恢复首选项的状态(Saving and restoring the Preference’s state)

正如布局中的 View 一样,在重启 Activity 或片段时(例如,用户旋转屏幕),Preference 子类也负责保存并恢复其状态。要正确保存并恢复 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); }

 

阅读原文

 

正文到此结束
Loading...