0%

Preference

基本原理

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方法将从配置文件中解析出 Header 集合对象。
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(); //白点1

//白点2
if (mOnClickListener.onPreferenceClick(this)) {
return;
}

//白点3
PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
.getOnPreferenceTreeClickListener();
if (listener.onPreferenceTreeClick(preferenceScreen, this)) {
return;
}
//白点4 处理Itent,只能在其它元素都不拦截的情况下才能发挥作用
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>
<!-- Preference should place its actual preference widget here. -->
<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) //系统提供的@string/yes
.setNegativeButton(mNegativeButtonText, this); //系统提供的@string/no
View contentView = onCreateDialogView(); //由参数 dialogLayout 提供
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