PreferenceFragmentCompat 设置界面
很早之就接触过PreferenceScreen设置项的常用用法(见《PreferenceScreen 设置项》),现在抽空再来了解一下它的承载主体PreferenceFragmentCompat及设置项的用法。
1、混乱的deprecate
先来屡一下Preference相关的包和类,到底哪些被deprecate?推荐用哪个?因为适配这个工作量不小,选错可能会给以后留下一个“惊喜”!
1.1、android.preference(退出历史)
最早Preference跟随系统库发布,位于Android源码android.preference包中,不需要添加额外依赖就可以直接使用。其中就有常用的Preference、PreferenceScreen、PreferenceActivity、PreferenceFragment等。但是这一锅全部被deprecate掉,不再推荐使用。

1.2、androidx.preference(推荐)
Google推出Jetpack,将一众混乱的各种support包、deprecate的包都统一起来。今天要说的PreferenceFragmentCompat就在androidx.preference包中。由于不是捆绑系统的类,所以使用PreferenceFragmentCompat需要在build.gradle中添加额外的依赖。
implementation 'androidx.preference:preference:1.1.1'
参考:Android 开发者 > Jetpack > androidx.preference
1.3、PreferenceFragment
PreferenceFragment这个单独拿出来说一下,是因为在android.preference和androidx.preference包中都有,但是最终还是被deprecate掉,在代码的不断重构过程中最终还是被Google“抛弃”。与此类似的还有PreferenceDialogFragment、ListPreferenceDialogFragment、EditTextPreferenceDialogFragment等,都改为了_Compat兼容的类。
它的用法和后面要讲的PreferenceFragmentCompat差不多,继续往下看吧。
2、PreferenceFragmentCompat
继续《PreferenceScreen 设置项》写好xml设置页面,还需要借助PreferenceFragmentCompat,并且动态添加到Activity设置页面中。
2.1、继承PreferenceFragmentCompat
PreferenceFragmentCompat是抽象类,该类中只有一个抽象方法onCreatePreferences(Bundle savedInstanceState, String rootKey)
/**
* 在{@link #onCreate(Bundle)}期间调用以提供此片段的首选项。希望子类直接或通过辅助方法(例如{@link
* #addPreferencesFromResource(int)})调用{@link #setPreferenceScreen(PreferenceScreen)}。 。
*
* @param savedInstanceState 如果要从先前保存的状态重新创建片段,则为该状态。
* @param rootKey 如果不为null,则此首选项片段应使用此键植于{@link PreferenceScreen}。
*/
public abstract void onCreatePreferences(Bundle savedInstanceState, String rootKey);
继承PreferenceFragmentCompat并实现该方法,相对于Activity的setContentView(R.layout.*)方法,调用setPreferencesFromResource(int preferencesResId,String key)加载res/xml目录下的设置页面。
public class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
}
}
注意:可以使用PreferenceActivity似乎更加方便,不过整个页面只能是设置页,不利于页面的组合,使用Fragment会体会到它的强大便捷之处。android.preference.PreferenceActivity已经废弃,应当使用AndroidX包中的PreferenceActivity。
2.2、动态添加PreferenceFragmentCompat
PreferenceFragmentCompat也是Fragment,需要添加到Activity中。在Activity布局中放置一个FrameLayout,后面用来替换成设置Fragment。关于Fragment参考《Fragment用法》。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
也可以不需要FrameLayout,直接替换
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
替换成设置Fragment
getSupportFragmentManager() //获取FragmentManager
.beginTransaction() //开启一个事务
.replace(R.id.settings, new SettingsFragment())
.commit(); //提交Fragment
3、Preference回调
Preference中定义了三种类型的回调接口,用来处理设置项的点击、选项值变化等场景。
对比 | 区别 | 联系 | 执行顺序 |
---|
onPreferenceClick | 通过preference.setOnPreferenceClickListener 和preference.setOnPreferenceChangeListener来注册listener使用的 | 最先执行,返回true, 则不调用onPreferenceTreeClick方法 | 2 |
onPreferenceChange | 通过preference.setOnPreferenceClickListener 和preference.setOnPreferenceChangeListener来注册listener使用的 | 独立其他2个点击事件,总是会运行 | 1 |
onPreferenceTreeClick | 是PreferenceActivity 中的一个方法,直接重写该该方法即可。 | 返回true 则不执行默认动作或返回上层调用链。 | 3 |
3.1、变化:Preference.OnPreferenceChangeListener
当Preference的值将要发生变化的时候调用。通过控制返回值true/false是否接受本次值得变化。此时可以提示用户是否进行变更。
public boolean onPreferenceChange(Preference preference, Object newValue) {
//做业务逻辑处理
boolean result = (boolean) newValue;
if (result) {
// 返回true 本次值变化生效
return true;
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("选择")
.setMessage("是否确定关闭?")
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//用户选择取消,不关闭。直接关闭Dialog即可,因为后面已经返回了false,本次修改不造成变化
dialog.dismiss();
}
})
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//用户选择确定,那么本次值虽然后面已经返回false,不再接受,但是这里可以继续根据用户的选择更改。
}
})
.create()
.show();
// 返回false 本次变化失效。在Dialog中通过选择控制值。
return false;
}
}
Preference设置变化监听器setOnPreferenceChangeListener
preference.setOnPreferenceChangeListener(...);
3.2、点击:Preference.OnPreferenceClickListener
点击Preference界面设置项的时候调用,实际验证:当值发生变化后,才调用该函数。如果这个时候再去做点击判断是否改变值就晚了。
场景:用户点击一个设置项,弹出对话框告诉用户是否确认修改,如果取消那么值不变。就可以给指定的Preference设置点击事件;或者先拦截,用户进行选择值再变化。
顺序:先调用onPreferenceChange函数,然后才设置checkbox的状态,最后才会调用onPreferenceClick。
private Preference.OnPreferenceClickListener preferenceClickListener = new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
return false;
}
};
Preference设置点击监听器setOnPreferenceChangeListener
preference.setOnPreferenceClickListener(preferenceClickListener);
3.3、跳转Activity
一般点击设置页面,有些是实现跳转。可以在标签中添加<intent/>实现跳转:
<Preference
app:icon="@drawable/setting_icon_1"
app:key="jump"
app:summary="跳转到另一个Activity"
app:title="跳转">
<intent android:action="com.quibbler.jump" />
</Preference>
或者更精确的添加android:targetPackage和android:targetClass指定跳转的Activity,防治系统中有其它应用有相同的Action冲突:
<intent android:action="com.quibbler.jump"
android:targetPackage="com.easyicon.server"
android:targetClass="com.easyicon.server.activity.JumpHereActivity"/
另一种方法,在继承的PreferenceFragmentCompat类中,通过key找到设置项,添加点击事件。
Preference preference = getPreferenceManager().findPreference("jump");
preference.setOnPreferenceClickListener(preferenceClickListener);
Preference.OnPreferenceClickListener设置项点击监听器,点击进行跳转。关于Activity跳转,参考启动Activity的几种方式。
private Preference.OnPreferenceClickListener preferenceClickListener = new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
String key = preference.getKey();
if ("jump".equals(key)) {
// Intent intent = new Intent();
// intent.setAction("com.quibbler.jump");
// getActivity().startActivity(intent);
}
return false;
}
};
坑点:如果这里onPreferenceClick(Preference preference)方法返回了true,那么在xml设置布局中<Preference/>节点下设置的<intent/>将不起作用。
4、PreferenceManager指定保存文件名
默认保存在/data/data/包名/shared_prefs/包名_preference.xml中,可以通过PreferenceManager指定保存设置Preference的文件名
PreferenceManager preferenceManager = getPreferenceManager();
指定保存SharedPreference文件名:
preferenceManager.setSharedPreferencesName(name);
preferenceManager.setSharedPreferencesMode(Context.MODE_PRIVATE);
5、动态删除/添加Perference
有时候设置项需要根据不同的条件变化,可以加载不同的xml/设置布局,也可以在代码中动态的添加删除Preference设置项。所以必须要有PreferenceFragmentCompat对象
PreferenceFragmentCompat mSettingsFragment = new SettingsFragment();
5.1、动态删除
首先需要找到要删除的Preference,借助PreferenceFragmentCompat对象获取到PreferenceManager设置管理器
PreferenceManager preferenceManager = mSettingsFragment.getPreferenceManager();
借助PreferenceManager设置管理器,通过key找到Preference设置项:
Preference preference = preferenceManager.findPreference("key_set");
①借助PreferenceScreen删除
要从哪删除?当然是PreferenceFragmentCompat对象中设置容器中,我们设置XML布局中的顶级容器标签就是PreferenceScreen。先获取PreferenceFragmentCompat的容器
PreferenceScreen preferenceScreen = mSettingsFragment.getPreferenceScreen();
再从中删除即可:
PreferenceScreen preferenceScreen = mSettingsFragment.getPreferenceScreen();
preferenceScreen.removePreference(preference);
②借助PreferenceCategory删除
还可以单独从某个设置组中删除,先通过PreferenceManager设置管理器找到设置组。从该设置组中删除Preference设置项。
PreferenceCategory preferenceCategory = preferenceManager.findPreference("key_category");
if (null != preferenceCategory) {
//从组别中删除该设置项,最好判空
preferenceCategory.removePreference(preferenceScreen);
}
5.2、动态添加
添加,构造好一个设置项,初始化各种参数title、key等。
Preference preference = new Preference(this);
preference.setTitle("添加新的设置");
preference.setKey("pref_new_key");
直接添加到PreferenceScreen中
PreferenceScreen preferenceScreen = mSettingsFragment.getPreferenceScreen();
preferenceScreen.addPreference(preference);
添加到指定设置组中:
PreferenceCategory preferenceCategory = preferenceManager.findPreference("key_category");
//添加到该组的最后一个位置上
preferenceCategory.addPreference(preference);
注意:无论如何添加设置项都是添加到最后一个位置,或者组的最后一个位置。这就不太好真正实现设置项的隐藏和显示。源码设置Preference的插入似乎有突破口,根据Index插入。
推荐博客:
Google > Docs > Reference > PreferenceFragmentCompat
PreferenceFragmentCompat
Android Preference详解之初识Preference及Preference系
PreferenceFragment去完成设置页面
Android开发之PreferenceActivity的使用