系统里设置Settings app 里面是用Preference
来做的,这些在其他app里也有涉及,比如dialer的设置部分,关于Preference
这里涉及到一些类,以后会常用碰到的,做一个笔记记录和分析一下。
- Activity :
PreferenceActivity
- Fragment :
PreferenceFragment
- Preference :
Preference
多个子类
SwitchPreference
,RadioButtonPreference
,SeekBarPreference
,DialogPreference
,RingtonePreference
,
以及多个DialogPreference
子类ListPreference
,MultiCheckPreference
,EditTextPreference
。
Preference
Preference这个类内部包含的成员变量有:
private Context mContext;
private PreferenceManager mPreferenceManager;
private PreferenceDataStore mPreferenceDataStore;
private long mId;
private OnPreferenceChangeListener mOnChangeListener;
private OnPreferenceClickListener mOnClickListener;
private CharSequence mTitle;
private int mTitleRes;
private CharSequence mSummary;
private int mIconResId;
private Drawable mIcon;
private Intent mIntent;
private String mFragment;
private Bundle mExtras;
private boolean mEnabled = true;
private boolean mSelectable = true
private OnPreferenceChangeInternalListener mListener;
private PreferenceGroup mParentGroup;
这些属性都是在平时会用到了,一个标准的Preference最简单就只需要一个标题mTitle
。
内部接口:
public interface OnPreferenceChangeListener {
boolean onPreferenceChange(Preference preference, Object newValue);
}
public interface OnPreferenceClickListener {
boolean onPreferenceClick(Preference preference);
}
分别在Preference被点击和改变值的时候回调。
Preference在初始化的时候会默认加载系统layout,然后加载对应属性。
成员方法:
protected View onCreateView(ViewGroup parent)
protected void onBindView(View view)
数据存储相关方法:
public Context getContext()
public SharedPreferences getSharedPreferences()
public SharedPreferences.Editor getEditor()
protected void notifyChanged()
public PreferenceManager getPreferenceManager()
protected boolean persistString(String value)
protected boolean persistInt(int value)
protected boolean persistFloat(float value)
protected boolean persistLong(long value)
存储数据与普通Sharedpreference类似;
protected boolean persistBoolean(boolean value) {
if (!shouldPersist()) {
return false;
}
if (value == getPersistedBoolean(!value)) {
return true;
}
PreferenceDataStore dataStore = getPreferenceDataStore();
if (dataStore != null) {
dataStore.putBoolean(mKey, value);
} else {
SharedPreferences.Editor editor = mPreferenceManager.getEditor();
editor.putBoolean(mKey, value);
tryCommit(editor);
}
return true;
}
protected boolean getPersistedBoolean(boolean defaultReturnValue) {
if (!shouldPersist()) {
return defaultReturnValue;
}
PreferenceDataStore dataStore = getPreferenceDataStore();
if (dataStore != null) {
return dataStore.getBoolean(mKey, defaultReturnValue);
}
return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue);
}
以上就是Preference里比较有用的成员和方法;其余的子类则是分别通过继承和重载来改写view和数据部分,以达到实现不同的功能。
下面以DialogPreference
为例来分析它是如何实现点击Preference就弹出Dialog对话框的。
一进入DialogPreference就看到了一些和AlertDialog相关的成员。
private AlertDialog.Builder mBuilder;
private CharSequence mDialogTitle;
private CharSequence mDialogMessage;
private Drawable mDialogIcon;
private CharSequence mPositiveButtonText;
private CharSequence mNegativeButtonText;
private int mDialogLayoutResId;
private Dialog mDialog;
其余大部分成员方法都是用来设置和Dialog相关的方法,比如设置标题,设置内容等。
然后
@Override
protected void onClick() {
if (mDialog != null && mDialog.isShowing()) return;
showDialog(null);
}
最重要的是重载了onClick,在点击的时候弹出对话框。
由于DialogPreference
是抽象类,所以在对话框关闭的方法里留了空
protected void onDialogClosed(boolean positiveResult) {
//等待子类继承
}
一般子类继承这个方法都是用来更新数据和重绘view。
在xml里的属性用法举例,
<PreferenceScreen //Preference集合,类似于Viewgroup
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:title="@string/network_dashboard_title">
<Preference
android:key="mobile_network_settings" //key
android:title="@string/network_settings_title" //title
android:summary="@string/summary_placeholder" //summary
android:icon="@drawable/ic_network_cell" //icon
android:persistent="false" //是否可持久化存储
android:dependency="toggle_airplane"
android:entries="@array/tty_mode_entries" //列表的names
android:entryValues="@array/tty_mode_values" //列表的values
android:defaultValue="true" //默认值
android:fragment="com.android.settings.TetherSettings" //点击后跳转的fragment
android:order="-15"
settings:keywords="@string/keywords_more_mobile_networks"
settings:userRestriction="no_config_mobile_networks"
settings:useAdminDisabledSummary="true">
<intent
android:action="android.intent.action.MAIN"
android:targetPackage="com.android.phone"
android:targetClass="com.android.phone.MobileNetworkSettings"/>
</Preference>
</PreferenceScreen>
PreferenceActivity
PreferenceActivity是继承自ListActivity的,
ListActivity里面维持了一个ListView,
这里面的Listview是设置了内部成员监听的。
private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id)
{
onListItemClick((ListView)parent, v, position, id);
}
};
mList.setOnItemClickListener(mOnClickListener);
所以PreferenceActivity的界面里,即使不手动设置点击监听,也是会有回调的,PreferenceActivity只需要继承onListItemClick这个方法就行。
PreferenceActivity是个抽象类,在它的Oncreate里有许多默认设置,
其中关键点是构建Header,头部条目。
if (!onIsHidingHeaders()) {
onBuildHeaders(mHeaders);
}
public void onBuildHeaders(List<Header> target) {
// Should be overloaded by subclasses
}
数据部分需要子类去重写,如果子类不重写则PreferenceActivity就显示为空。
重要构造方法:
public void loadHeadersFromResource(@XmlRes int resid, List<Header> target)
public void addPreferencesFromResource(int preferencesResId)
PreferenceActivity经典使用举例:
在Dialer的setting界面,就是DialerSettingsActivity
,继承自PreferenceActivity
;
@Override
public void onBuildHeaders(List<Header> target) {
...
Header soundSettingsHeader = new Header();
soundSettingsHeader.titleRes = R.string.sounds_and_vibration_title;
soundSettingsHeader.fragment = SoundSettingsFragment.class.getName();
soundSettingsHeader.id = R.id.settings_header_sounds_and_vibration;
target.add(soundSettingsHeader);
...
}
比较明显的就是,在这个界面里看到的内容条目都是一个个Header,然后构成列表出现。
当然这些都是用代码方式填充数据的。
另一个继承的PreferenceActivity
的CallFeaturesSetting
,则是用addPreferencesFromResource
。
@Override
protected void onResume() {
super.onResume();
...
addPreferencesFromResource(R.xml.call_feature_setting);
...
}
这两个的区别就在于 一个是从xml,一个是从java代码里添加数据。
onBuildHeaders需要继承,addPreferencesFromResource只需要在Oncreate里调用。
忘了说,这里的Header
是PreferenceActivity
的内部类,属性部分几乎和Preference大致相同。
不过和Preference的区别在于,Preference自带view,而这里的Header更纯粹的属于一个item。
以上就是PreferenceActivity
相关部分,使用起来的特点就是这几个方法,获取数据部分,以及点击回调,其余的就是点击事件处理了。
PreferenceFragment
PreferenceFragment在平时使用中更频繁,比如在Setting里,所有的界面都是一个空Activity靠加载不同的Fragment去调度。
按照生命周期来说与普通的Fragment没差别。
public void onCreate
public View onCreateView
public void onViewCreated
public void onActivityCreated
public void addPreferencesFromIntent(Intent intent)
public void addPreferencesFromResource(@XmlRes int preferencesResId)
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)
public Preference findPreference(CharSequence key)
private void bindPreferences()
所以使用起来也不会很麻烦。
接下来说说Setting中的那些PreferenceFragment
在Setting里,似乎所有的设置子项都是用的PreferenceFragment
比如
- 设置网络的
NetworkDashboardFragment
- 连接设备的
ConnectedDeviceDashboardFragment
- 应用通知的
AppAndNotificationDashboardFragment
- 电池管理的
PowerUsageSummary
- 显示管理的
DisplaySettings
- 声音管理的
SoundSettings
- 存储管理的
StorageDashboardFragment
- 安全设置的
SecuritySettings
- 系统设置的
SystemDashboardFragment
他们全都是PreferenceFragment
的子类。
DashboardFragment extends SettingsPreferenceFragment
SettingsPreferenceFragment extends InstrumentedPreferenceFragment
InstrumentedPreferenceFragment extends ObservablePreferenceFragment
ObservablePreferenceFragment extends PreferenceFragment
以NetworkDashboardFragment
为例看看是怎么做的。
@Override
protected int getPreferenceScreenResId() {
return R.xml.network_and_internet;
}
@Override
protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
return buildPreferenceControllers(context, getLifecycle(), mMetricsFeatureProvider, this
/* fragment */,
this /* mobilePlanHost */);
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle, MetricsFeatureProvider metricsFeatureProvider, Fragment fragment,
MobilePlanPreferenceHost mobilePlanHost) {
....
}
这里只重载了两个方法
getPreferenceScreenResId
获取xml的idgetPreferenceControllers
得到所有找到的preference的控制器
在父类NetworkDashboardFragment
里,AbstractPreferenceController
是用来替换控制的,
比如
@Override
public boolean onPreferenceTreeClick(Preference preference) {
Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();
// If preference contains intent, log it before handling.
mMetricsFeatureProvider.logDashboardStartIntent(
getContext(), preference.getIntent(), getMetricsCategory());
// Give all controllers a chance to handle click.
for (AbstractPreferenceController controller : controllers) {
if (controller.handlePreferenceTreeClick(preference)) {
return true;
}
}
return super.onPreferenceTreeClick(preference);
}
点击的时候用对应的controller去实现点击。
Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();
for (AbstractPreferenceController controller : controllers) {
controller.displayPreference(screen);
}
显示的时候用对应的controller去显示。
这样做的好处就是解耦,让代码逻辑更清晰,方便拓展。
因为设置里的设置项确实非常多,应该各自写各自的,互相影响才小。
好了,基本上该写的都写完了,如果我们要自己写Preference界面,可以用PreferenceActivity
或者PreferenceFragment
都行,他俩都是抽象类,需要子类继承,在使用的时候直接写个xml来使用,设置项也可以单独去用不同的Preference控制。