在Android开发中,经常需要对一些整数类型的偏好设置(如音量或亮度级别)进行管理。虽然Android提供了许多现成的控件,如CheckBoxPreference、EditTextPreference、ListPreference等,来构建偏好设置界面,但有时这些控件可能无法满足需求。此时,可以基于现有的控件创建自定义控件,并在不同的应用程序中重复使用它。
DialogPreference是创建此类自定义控件的最佳基类,它提供了在偏好列表中显示两行文本(标题和摘要)的偏好设置,并在点击时显示带有“确定/取消”按钮的对话框。
将其命名为SeekBarPreference。这个控件的参数可以是最小值、最大值和当前默认值。实际的当前值将存储在关联的SharedPreferences中,使用给定的键。
在/res/xml/preferences.xml文件中,可以这样定义:
现有的标签可以用来设置默认的当前值。但是最小值和最大值需要它们自己的标签。为此,/res/values/attrs.xml文件应该扩展如下声明:
在处理XML的最后一步是创建对话框的布局,当控件被点击时会启动。这是一个非常常见的代码,可以省略而不会产生任何影响。只需提一下它包含了最小值、最大值和当前值的TextView和SeekBar。
首先,在控件构造函数中读取所有提到的属性:
private int mMinValue;
private int mMaxValue;
private int mDefaultValue;
mMinValue = attrs.getAttributeIntValue(PREFERENCE_NS, ATTR_MIN_VALUE, DEFAULT_MIN_VALUE);
mMaxValue = attrs.getAttributeIntValue(PREFERENCE_NS, ATTR_MAX_VALUE, DEFAULT_MAX_VALUE);
mDefaultValue = attrs.getAttributeIntValue(ANDROID_NS, ATTR_DEFAULT_VALUE, DEFAULT_CURRENT_VALUE);
在设置对话框时,应该重写onCreateDialogView方法:
@Override
protected View onCreateDialogView() {
// Get current value from settings
mCurrentValue = getPersistedInt(mDefaultValue);
// Inflate layout
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.dialog_slider, null);
// Put minimum and maximum
((TextView) view.findViewById(R.id.min_value)).setText(Integer.toString(mMinValue));
((TextView) view.findViewById(R.id.max_value)).setText(Integer.toString(mMaxValue));
// Setup SeekBar
mSeekBar = (SeekBar) view.findViewById(R.id.seek_bar);
mSeekBar.setMax(mMaxValue - mMinValue);
mSeekBar.setProgress(mCurrentValue - mMinValue);
mSeekBar.setOnSeekBarChangeListener(this);
// Put current value
mValueText = (TextView) view.findViewById(R.id.current_value);
mValueText.setText(Integer.toString(mCurrentValue));
return view;
}
当前值是通过preferences.xml中为特定控件实例指定的键来获取的。同时,在设置SeekBar时应该考虑到其最小值是零。这就是为什么在首选项的最小值与零不同时使用减法的原因。顺便说一句,这段代码仅适用于非负数,并且最大值确实大于最小值。
在上一步之后,应用程序可以启动,用户可以拖动滑块,但当前值标签上的文本不会改变,因为需要引入位置变化处理程序。
为此,SeekBarPreference应该实现OnSeekBarChangeListener接口。在前面的代码中,正是通过“mSeekBar.setOnSeekBarChangeListener(this);”调用来传递这个接口给SeekBar的。只需要实现三个可能的方法中的一个:
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
mCurrentValue = value + mMinValue;
mValueText.setText(Integer.toString(mCurrentValue));
}
接下来,需要持久化更改的值。在对话框关闭时,它会调用onDialogClosed方法,该方法应该被重写:
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (!positiveResult) {
return;
}
if (shouldPersist()) {
persistInt(mCurrentValue);
}
notifyChanged();
}
在积极的结果下,值被持久化,shouldPersist()检查根据preferences.xml中的android:persistent标志来分析是否有必要。最后一行是必要的,因为默认情况下,控件的摘要行是静态的,如果它应该反映当前值,那么应该添加以下代码:
@Override
public CharSequence getSummary() {
String summary = super.getSummary().toString();
int value = getPersistedInt(mDefaultValue);
return String.format(summary, value);
}
在这里,当请求摘要时,原始字符串被视为模板,将当前值放入其中。这在偏好设置屏幕打开时效果很好。但是,要获得相同的行为,通过对话框更改偏好设置后,应该调用notifyChanged()。
实现的控件适用于广泛的偏好设置,并且优雅地扩展了现有的偏好控件集合。动态摘要行方法可以用于不同的偏好控件。