基本原理
Preference 框架本质可以看出一个小APP,使用者通过在XML中配置 Preference ,配置内容主要涉及控件和数据。不同的 Preference 产生不同的控件,最终系统会生成列表 ListView 展示这些控件;所配置的数据也会被自动解析出,并渲染到 ListView 中去。而且数据还会使用 SharedPreference 进行持久化保存,且随着用户设置进行改变。
使用者只需要提供 XML 配置文件即可完成控件布局和配置数据读写。
布局部分
PreferenceActivity
PreferenceActivity 是 ListActivity 的子类,ListActivity 所加载的布局是 com.android.internal.R.layout.list_content_simple
,该布局实际是一个ListView
,并拥有 id 为 @android:id/list
,只需要为其配置一个适配器就行了,你可以自定义布局,只要其中含有id 为 @android:id/list
的 ListView 即可,否则会触发运行时异常。
当该活动内容发生变化时,如果设置了空视图(id必须为 @android:id/empty),将展示空视图,空视图将设置给 ListView。
PreferenceActivity 可以自己直接加载 ListView,但这已经不推荐了,故不再叙述。更多时候它在单屏时采用碎片,多屏时使用ListView 展示 Header 集合,而每个 Header 则用碎片展示。
PreferenceActivity 类有一个内部类Header
的集合,如果采用多屏显示应该覆盖下列方法,实例化这个集合。典型的实现如下
1 2 3 4 5 6 7 8
| ArrayList<Header> mHeaders;
@Override public void onBuildHeaders(List<Header> target) { super.onBuildHeaders(target); loadHeadersFromResource(R.xml.setting_activity, target); }
|
典型的XML配置文件如下
1 2 3 4 5 6 7 8 9 10 11
| <preference-headers xmlns:android="http://schemas.android.com/apk/res/android"> <header android:id="@+id/header_application" android:title="外观" android:fragment="com.lxt.incredibly.SettingFragment" android:icon="@android:drawable/ic_menu_share" /> <header android:id="@+id/header_support" android:title="支持" android:icon="@android:drawable/ic_media_play" /> </preference-headers>
|
而后就是为 Header 集合添加适配器 HeaderAdapter 。此时,点击单元将加载其定义的碎片。
PreferenceFragment
PreferenceFragment 在创建视图时会加载系统布局com.android.internal.R.styleable.PreferenceFragment_layout
,该布局内部有一个ListView
控件。ListView 负责管理视图对象,而 PreferenceManager 则管理数据对象,二者的匹配都转移到了 PreferenceScreen 中完成。
使用时首先加载 XML 配置文件
1 2 3 4 5
| @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.settings_fragment); }
|
PreferenceScreen 将使用XML解析出配置信息,并生成一个适配器给 ListView 完成适配这些配置信息。
注意此时还需要在活动中覆盖下列方法,判断碎片是否有效。
1 2 3 4
| @Override protected boolean isValidFragment(String fragmentName) { return (fragmentName.equals(SettingFragment.class.getName())); }
|
实际使用的适配器是 PreferenceGroupAdapter,适配的数据是 Preference,点击item实际执行的是 Preference 的点击事件
1 2 3 4 5 6
| public void onItemClick(AdapterView parent, View view, int position, long id) { Object item = getRootAdapter().getItem(position); if (!(item instanceof Preference)) return; final Preference preference = (Preference) item; preference.performClick(this); }
|
Preference 的 performClick 方法实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public void performClick(PreferenceScreen preferenceScreen) { onClick(); if (mOnClickListener.onPreferenceClick(this)) { return; } PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager .getOnPreferenceTreeClickListener(); if (listener.onPreferenceTreeClick(preferenceScreen, this)) { return; } if (mIntent != null) { Context context = getContext(); context.startActivity(mIntent); } }
|
自定义 Prefrence
Prefrence 的默认布局是 mLayoutResId,改变这个参数能够自定义布局
1
| private int mLayoutResId = com.android.internal.R.layout.preference;
|
mLayoutResId 必须包括一些指定控件
1 2 3 4 5 6 7 8 9
| <LinearLayout android:background="?android:attr/selectableItemBackground" > <ImageView android:id="@+android:id/icon"/> <RelativeLayout android:layout_weight="1"> <TextView android:id="@+android:id/title" /> <TextView android:id="@+android:id/summary"/> </RelativeLayout> <LinearLayout android:id="@+android:id/widget_frame" /> </LinearLayout>
|
实际使用 mWidgetLayoutResId 参数更好。
例如CheckBoxPreference 这个子类
1 2 3
| <style name="Preference.CheckBoxPreference"> <item name="widgetLayout">@layout/preference_widget_checkbox</item> </style>
|
真实的布局如下,实际会用这个控件替换 widget_frame
1 2 3 4 5 6 7
| <CheckBox xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+android:id/checkbox" android:layout_width="wrap_content" android:layout_height="wrap_content" android:focusable="false" android:clickable="false" android:background="@null" />
|
而后在方法 onBindView 中设置配置 CheckBox 控件
1 2 3 4 5 6 7 8 9
| @Override protected void onBindView(View view) { super.onBindView(view); View checkboxView = view.findViewById(com.android.internal.R.id.checkbox); if (checkboxView != null && checkboxView instanceof Checkable) { ((Checkable) checkboxView).setChecked(mChecked); } syncSummaryView(view); }
|
自定义 DialogPrefrence
DialogPrefrence 自身的控件不是很重要,其关键在于点击它时将展示一个 Dialog,可以改变的是 Dialog 的布局。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| protected void showDialog(Bundle state) { mBuilder = new AlertDialog.Builder(context) .setTitle(mDialogTitle) .setIcon(mDialogIcon) .setPositiveButton(mPositiveButtonText, this) .setNegativeButton(mNegativeButtonText, this); View contentView = onCreateDialogView(); if (contentView != null) { onBindDialogView(contentView); mBuilder.setView(contentView); } else { mBuilder.setMessage(mDialogMessage); } onPrepareDialogBuilder(mBuilder); final Dialog dialog = mDialog = mBuilder.create(); if (state != null) { dialog.onRestoreInstanceState(state); } if (needInputMethod()) { requestInputMethod(dialog); } dialog.setOnDismissListener(this); dialog.show(); }
|
这里参数 dialogLayout提供自定义布局,它和 message 是互斥的。
因此自定义 DialogPrefrence 即可以继承该类,提供 View;也可以直接继承 Prefrence 自己提供 Dialog,这样能够发挥自定义 Dialog 的优势,如提供动画效果。
自定义一个颜色选择器
自定义一个颜色选择器是一个综合应用,其实现可以看ColorPickPreference。