各位,好久不见,最近时间较为充裕,更新一下博客。
本篇在我的理解、认识范围内,讲述一下Android中的Preference(破粉斯~)这玩意,常用于项目中的设置模块中。在工作中我也主要负责了设置模块相关问题。
首先我看了一些写的比较好的博客博文,也是本篇文章的参考,各位可以跳转进行学习,感谢以下博文:
Android系统应用之Settings_android settings-CSDN博客
Android中的Preference结构的设计与实现 - 浪里小白龙呼呼呼 - 博客园
接下来,我们通过几个方面了解,请看目录:
目录
一、前言
首先我们需要看一下官方的几个网址:
介绍Preference(官方把这玩意放在了:添加设置组件模块中,也可以看出在设置模块中最常用):设置 | Views | Android Developers
Android原生设置代码:AndroidXRef
和一脸懵逼点进本文的你一样,刚开始我在Android原生设置代码(packages/apps/Settings)看到这个玩意,似控件又不似控件,也不理解这Google工程师是有什么*****,非要搞一套这个(就因为更方便的保存SP值吗?),而且这东西大致的使用场景就是系统设置中,像一些软件开发者、学生很少用到。
另外一点你可以在Android Studio中选择新建Setting Activity,他就是使用Preference进行实现的一个demo(但是有些版本AS没有(好像是高版本移除了))
二、Preference是什么
官方:无需与设备存储空间交互,也不需要管理界面,便能构建交互式设置画面。
基本定义:
Preference 在 Android 开发中是一种用于管理应用设置(所以其实在APP的单独设置中也常用)的框架组件(可以发现介绍的是一种组件,而非我们理所当然认为是一种控件,控件更偏向于UI、功能单一,而组件更加广义,涵盖数据处理等)。它为应用提供了一种标准化的方式来处理用户可配置的选项,这些选项可以是简单的布尔值(如开关设置)、文本值(如用户自定义名称),也可以是从一组固定值中选择的选项(如语言选择)。从本质上讲,Preference 是一种数据结构和视图的结合体,既包含了用户设置的具体数据,又定义了这些数据在用户界面上的显示和交互方式
要在Android应用程序中使用Preference,您需要在应用程序的XML资源文件中定义每个偏好设置的首选项元素。
每个首选项元素都具有唯一的键和显示给用户的值。
当用户更改任何偏好设置时,Android会自动保存更改,并在用户下次启动应用程序时恢复它们。
三、Preference有什么用
简化开发流程(你可以简单理解如果没有他你需要监听控件、写XML、写SP值存取等一系列操作)
在 Android 应用开发中,如果没有 Preference 框架,开发者需要手动创建和管理设置界面。这意味着要从最基础的布局设计开始,为每个设置选项创建对应的视图控件,如 TextView 用于显示设置标题,EditText 用于用户输入,CheckBox 用于开关选项等。然后,还需要编写代码来处理这些控件的事件,例如保存用户输入的值、响应开关状态的改变等。而 Preference 框架提供了标准化的流程,开发者只需在 XML 布局文件中定义 Preference 类型和相关属性,就能快速搭建设置界面,大大减少了开发时间和工作量。
确保代码结构清晰
由于 Preference 将设置数据和用户界面显示及交互紧密结合,代码结构更加清晰。开发者可以在一个相对独立的模块中处理与 Preference 相关的代码,包括初始化 Preference、监听 Preference 变化以及从存储中读取和保存 Preference 值等操作。这与应用的其他业务逻辑(如网络请求、数据处理等)相互分离,使得代码易于维护和扩展。
例如,当需要添加新的设置选项时,只需在 XML 布局文件中添加新的 Preference 元素,并在对应的代码中处理该 Preference 的初始化和事件监听,而不会影响到应用的其他功能代码。
数据持久化机制
Preference 利用 SharedPreferences 来实现数据持久化。当用户在设置界面修改 Preference 的值时,例如改变一个 CheckBoxPreference 的勾选状态或在 EditTextPreference 中输入新文本,这些值会自动保存到 SharedPreferences 文件中。SharedPreferences 是一种轻量级的键值对存储方式,适合存储应用的配置数据。它基于 XML 文件(在内部实现上),能够保证数据在应用的多次启动和关闭过程中保持稳定。
状态保持功能
除了数据的持久化存储,Preference 还能保持设置的状态。在应用运行过程中,可能会因为各种原因(如屏幕旋转、切换到后台再回到前台等)导致 Activity 或 Fragment 的重新创建。Preference 框架会确保在这些情况下,设置的状态不会丢失。例如,在一个具有夜间模式开关 Preference 的应用中,无论应用经历了多少次状态变化,只要用户开启或关闭了夜间模式,这个状态都会被准确地记录和恢复。
满足多样化需求
用户对应用的需求是多样化的,Preference 为用户提供了个性化应用的途径。通过各种类型的 Preference,用户可以根据自己的喜好和使用习惯来调整应用的功能和外观。
四、Preference 的常见类型
一般从组件名(很想叫它控件,但是严谨哈哈哈)可以知道他的大致用法、功能
官网上可以看到(翻译一下、去除了弃用的,常用重要标红):
CheckBoxPreference | 提供复选框小部件功能的首选项。 |
DialogPreference | 基于对话框的首选项基类 |
DropDownPreference | 在下拉菜单中而不是在对话框中显示选项的列表首选项。 |
EditTextPreference | 在对话框中显示EditText的对话首选项。 |
EditTextPreference.SimpleSummaryProvider | 一个简单的androidx. preatice.Preatice.SummaryProvider实现EditTextPreection。 |
ListPreference | 将条目的列表显示为对话框的首选项。 |
ListPreference.SimpleSummaryProvider | SummaryProvider实现。 |
MultiSelectListPreference | 将条目的列表显示为对话框的首选项。 |
Preference | 表示在首选项层次结构中显示给用户的单个设置的基本构建块。 |
Preference.BaseSavedState | 用于管理首选项的实例状态的基类。 |
PreferenceCategory | 用于对相似的首选项进行分组的容器。 |
PreferenceDataStore | 一个要实现并提供给偏好框架的数据存储接口。 |
PreferenceDialogFragmentCompat | 抽象基类,表示与DialogPreference关联的对话框。 |
PreferenceFragmentCompat | PreferenceFragmentCompat是使用首选项库的切入点。 |
PreferenceGroup | 多个首选项的容器。 |
PreferenceHeaderFragmentCompat | PreferenceHeaderFragmentCompat实现了一个用于首选项的两窗格片段。 |
PreferenceManager | 用于帮助从活动或XML创建首选项层次结构。 |
PreferenceManager.PreferenceComparisonCallback | 回调类将由与PreessceScreen关联的androidx.回收器视图.小部件.回收器视图. Adapter使用,用于确定两个首选对象何时在语义和视觉上相同。 |
PreferenceManager.SimplePreferenceComparisonCallback | PreferenceComparisonCallback的基本实现,适合与默认的首选项类一起使用。 |
PreferenceScreen | 表示设置屏幕的顶级容器。 |
PreferenceViewHolder | 一个RecyclerView. ViewHolder类,它缓存与默认首选项布局关联的视图。 |
SeekBarPreference | 偏好基于SeekBarPreection,但使用支持偏好作为基础。 |
SwitchPreference | 提供两状态可切换选项的Preection。 |
SwitchPreferenceCompat | 提供两状态可切换选项的Preection。 |
TwoStatePreference | 具有两个可选状态的首选项的通用基类,保存一个布尔值,并且可能具有基于当前状态启用/禁用的依赖首选项。 |
看看表现形式:
如果你想要了解某一个Preference相关属性、方法等,建议直接点击表格的链接跳转到官方文档查看,例如我需要了解一下SwitchPrefernce:
android:summaryOff
android:summaryOn
android:switchTextOff
android:switchTextOn
还有一些基本类型例如title、高度、key等
android:icon | 此属性用于为 Preference 设置一个图标资源。该图标会显示在 Preference 的标题旁边 |
android:summary | 是对 Preference 的简短说明。该文本会在标题( |
android:title | 定义了 Preference 在设置界面中的主要显示文本,是用户识别 Preference 的最直接依据。 |
android:key | 为 Preference 提供一个唯一的标识符。在整个应用的设置体系中,每个 Preference 的 在数据存储(如 SharedPreferences)中,作为存储和检索 Preference 值的键。 |
android:layout | 允许开发者指定一个自定义的布局来替换 Preference 的默认布局。自定义布局必须遵循 Preference 布局的相关规则。(和待会讲的强相关) |
android:fragment | 当用户点击 Preference 时,此属性用于指定要启动的 Fragment。Fragment 是 Android 中用于构建灵活 UI 的组件,它可以包含自己的布局和逻辑。 |
android:order | 用于确定 Preference 在 PreferenceScreen(设置屏幕)中的排列顺序。它是一个整数值,值越小,在屏幕上显示的位置越靠前。 |
android:enabled | 控制 Preference 是否处于可用状态。当设置为 |
android:selectable | 决定 Preference 是否可以被用户选中。对于大多数可交互的 Preference 类型(如 CheckBoxPreference、ListPreference 等),该属性默认为 |
android:widgetLayout | 用于指定一个自定义的布局来替换 Preference 默认的小部件(widget)布局。小部件布局通常包含了 Preference 用于实现交互功能的元素,如 CheckBoxPreference 中的复选框部分。 |
android:shouldDisableView | 这是一个布尔属性,用于确定是否根据 Preference 的可用性(由 |
android:persistent | 用于确定 Preference 的值是否会被持久化存储。默认情况下,多数 Preference 的该属性为 |
android:intent | 当用户点击 Preference 时,可以通过此属性指定一个意图(Intent)。Intent 是 Android 中用于启动组件(如 Activity、Service、Broadcast Receiver)的机制。 |
以上是常用属性 | 以下较不常用 |
android:defaultValue | 为 Preference 指定一个默认值。这个值会在以下两种情况下被使用:一是当 Preference 首次被创建时;二是当找不到存储中对应的值时(例如,存储数据被意外删除或损坏)。 |
android:clickable | 含义:专门控制 Preference 是否可被点击。虽然 |
| 用于创建 Preference 之间的依赖关系。通过指定另一个 Preference 的 |
五、Preference 的布局与样式定制
android:layout:允许开发者指定一个自定义的布局来替换 Preference 的默认布局。
所以在项目设置中会经常看到layout属性然后绑定一个res/layout/文件夹下的布局文件(要知道的是我们Preference文件都在res/xml中)
为了与应用的整体风格相匹配,我们往往需要对 Preference 的布局和样式进行定制。
-
布局定制方面:可以通过创建自定义的 XML 布局文件来重新排列 Preference 在设置界面中的显示顺序和位置。比如,将相关的设置项分组并使用特定的布局方式来展示,增加界面的可读性。同时,还可以添加额外的视图元素,如分割线、标题等,以提升视觉效果。
-
样式定制方面:可以修改 Preference 的文本颜色、背景颜色、字体大小等样式属性。对于不同类型的 Preference,也可以分别设置其特定的样式,例如,将 CheckBoxPreference 的复选框大小或颜色修改得更符合应用的设计风格。这可以通过在主题(Theme)中定义样式或者直接在布局文件中使用属性来实现。
例如在设置源码中:/packages/apps/Settings/res/xml/wifi_settings.xml
<PreferenceCategory
android:key="connected_access_point"
android:layout="@layout/preference_category_no_label"/>
<PreferenceCategory
android:key="access_points"
android:layout="@layout/preference_category_no_label"/>
我们就可以去layout找preference_category_no_label这个布局:
/packages/apps/Settings/res/layout/preference_category_no_label.xml
它定义了一个Space
视图组件。Space
是 Android 提供的一种用于在布局中创建空白间隔空间的视图,其主要作用是在布局的不同元素之间添加一定的间距,以实现更好的视觉布局效果。
<Space xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="0dp" />
六、Preference 的数据存储与读取
了解 Preference 的数据是如何存储和读取的对于实现稳定的设置功能至关重要。
-
存储机制:Android 系统通常使用 SharedPreferences 来存储 Preference 的数据。这是一种轻量级的键值对存储方式,每个 Preference 都有一个对应的键,其值可以是各种基本数据类型,如布尔型、字符串型、整型等。当用户修改 Preference 的值时,系统会自动将新的值更新到 SharedPreferences 文件中。
-
读取过程:在应用需要获取 Preference 的值时,它会通过相应的键从 SharedPreferences 文件中读取。例如,在启动应用时,需要根据之前用户设置的字体大小来显示文本,就可以从存储中读取该值并应用到文本视图中。同时,要注意处理读取数据可能出现的异常情况,如文件不存在或者数据类型不匹配等问题。
可以重温一下SharedPreferences,看我写的一个简单示例:
Android Studio初学者实例:使用SharedPreferences存储来保存QQ登录信息_android保存登录案例-CSDN博客
七、Preference 与用户交互逻辑
Preference 不仅仅是静态的设置选项,它还涉及到与用户交互的复杂逻辑。
-
值改变监听:当用户修改了 Preference 的值时,应用需要及时响应这些变化。可以通过设置 OnPreferenceChangeListener 来监听 Preference 值的改变事件。例如,当用户在设置中更改了音量大小的 Preference 值时,应用需要立即调整音频输出的音量,并可能需要保存这个新的设置状态。
-
点击事件处理:除了值改变监听,某些 Preference 可能还有特定的点击事件逻辑。比如,点击某个帮助文档类型的 Preference 可能会打开一个包含详细帮助信息的新页面。这种点击事件可以通过在 Preference 的 XML 定义中或者在代码中设置 OnPreferenceClickListener 来实现,为用户提供更丰富的交互体验。
这时,我们可以提一下前面未提到的android:controller属性,在原生设置中也经常遇到
在 Android 的 Preference 体系中,android:controller
属性是用于建立一种特殊的关联关系,它允许一个 Preference(通常是具有交互性质的,比如TwoStatePreference
的子类)与另一个 View 或 Preference 建立连接。这种连接可以实现复杂的交互逻辑和视觉反馈机制,从而提升用户在设置界面的体验。
(一)实现视图联动
-
与其他视图的联动
-
当一个 Preference 设置了
android:controller
属性后,它可以和指定的视图进行联动。例如,在某些设置场景中,可能有一个开关 Preference(如CheckBoxPreference
)和一个文本视图(TextView
)。通过将开关 Preference 的android:controller
指向文本视图,当开关状态改变时,可以触发文本视图的更新,比如改变文本的颜色或显示内容,以此来直观地反映开关状态的变化。 -
从代码实现角度看,当 Preference 的状态发生变化时(例如通过用户操作触发了
OnPreferenceChangeListener
),系统会根据android:controller
属性找到关联的视图,并通过相应的方法(如setText()
或setColor()
)对视图进行更新。
-
-
与其他 Preference 的联动
-
除了和普通视图联动,
android:controller
还能实现 Preference 之间的联动。例如,有一个主开关 Preference 和多个子设置 Preference。当主开关关闭时,可以通过android:controller
属性将子设置 Preference 设置为不可用状态(通过改变其enabled
属性),从而实现设置项之间的层级控制。 -
这种联动在实现复杂的设置逻辑时非常有用。例如在系统设置中,可能存在 “Wi - Fi 高级设置” 选项,当 “Wi - Fi” 开关关闭时,“Wi - Fi 高级设置” 相关的 Preference 都应被禁用,通过
android:controller
属性就能轻松实现这种依赖关系的控制。
-
(二)控制显示和隐藏
-
基于状态的显示隐藏
-
android:controller
属性可用于根据另一个视图或 Preference 的状态来控制当前 Preference 的显示和隐藏。例如,在一个具有多种配置模式的应用设置中,有一个模式选择 Preference(如ListPreference
),以及多个仅在特定模式下才需要显示的子设置 Preference。 -
通过将子设置 Preference 的
android:controller
指向模式选择 Preference,并在代码中根据模式选择 Preference 的选中值来判断是否显示子设置 Preference(通过改变其visibility
属性),可以实现动态的设置界面布局,避免用户被过多不相关的设置项干扰。
-
-
优化用户界面布局
-
在屏幕空间有限的移动设备上,合理控制设置项的显示和隐藏对于优化用户界面布局至关重要。通过
android:controller
实现的显示隐藏机制,可以使设置界面更加简洁明了。例如,在音乐播放应用的设置中,当 “均衡器” 开关关闭时,与均衡器相关的多个设置 Preference(如不同频段的增益设置)可以被隐藏,从而为用户提供更清晰的设置界面。
-
(三)协调数据更新
-
数据一致性维护
-
当存在多个相关的设置项时,
android:controller
有助于维护数据的一致性。例如,在一个应用中有一个 “同步设置” Preference 和多个与不同类型数据同步相关的子设置 Preference(如 “联系人同步”“日历同步” 等)。 -
如果 “同步设置” Preference 被关闭,不仅要通过
android:controller
将子设置 Preference 设置为不可用,还要在代码中确保相关的数据同步操作不会被触发,从而保证设置数据和实际操作的一致性。
-
-
跨组件数据更新
-
在涉及多个组件(如 Activity、Fragment)的设置场景中,
android:controller
可以促进跨组件的数据更新。例如,一个设置 Activity 中有多个 Fragment,每个 Fragment 包含不同类型的 Preference。通过android:controller
在不同 Fragment 的 Preference 之间建立联系,可以在一个 Fragment 中的 Preference 状态改变时,及时更新其他 Fragment 中的相关 Preference 和数据。 -
这种跨组件的数据协调更新能够避免数据不一致问题,尤其是在复杂的多屏幕设置应用中,确保用户在不同设置页面看到的信息和可操作的设置项都是准确和最新的。
-
随便找了一个Controller类:/packages/apps/Settings/src/com/android/settings/inputmethod/VirtualKeyboardPreferenceController.java 可以看看都做了哪些事情
// 定义了一个名为VirtualKeyboardPreferenceController的类,它继承自AbstractPreferenceController并实现了PreferenceControllerMixin接口
public class VirtualKeyboardPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin {
// 用于管理输入法的管理器实例
private final InputMethodManager mImm;
// 设备策略管理器实例,用于管理设备的策略相关操作
private final DevicePolicyManager mDpm;
// 包管理器实例,用于获取应用程序的包相关信息
private final PackageManager mPm;
// 构造函数,用于初始化类的实例
// 接收一个Context上下文对象作为参数
public VirtualKeyboardPreferenceController(Context context) {
super(context);
// 通过上下文获取包管理器实例
mPm = mContext.getPackageManager();
// 通过上下文获取设备策略管理器实例
mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
// 通过上下文获取输入法管理器实例
mImm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
}
// 判断该偏好设置是否可用的方法
// 根据配置文件中的布尔值来确定是否显示虚拟键盘偏好设置
@Override
public boolean isAvailable() {
return mContext.getResources().getBoolean(R.bool.config_show_virtual_keyboard_pref);
}
// 获取偏好设置的键值的方法
// 返回虚拟键盘偏好设置对应的键值
@Override
public String getPreferenceKey() {
return "virtual_keyboard_pref";
}
// 更新偏好设置状态的方法
// 根据输入法的相关信息来更新偏好设置的摘要(summary)信息
@Override
public void updateState(Preference preference) {
// 获取当前启用的输入法列表
final List<InputMethodInfo> imis = mImm.getEnabledInputMethodList();
if (imis == null) {
// 如果列表为空,设置偏好设置的摘要为默认的空摘要字符串
preference.setSummary(R.string.summary_empty);
return;
}
// 获取当前用户允许使用的输入法列表
final List<String> permittedList = mDpm.getPermittedInputMethodsForCurrentUser();
final List<String> labels = new ArrayList<>();
// 遍历当前启用的输入法列表
for (InputMethodInfo imi : imis) {
// 判断输入法是否被组织允许使用
final boolean isAllowedByOrganization = permittedList == null
|| permittedList.contains(imi.getPackageName());
if (!isAllowedByOrganization) {
continue;
}
// 将允许使用的输入法的标签添加到labels列表中
labels.add(imi.loadLabel(mPm).toString());
}
if (labels.isEmpty()) {
// 如果允许使用的输入法标签列表为空,设置偏好设置的摘要为默认的空摘要字符串
preference.setSummary(R.string.summary_empty);
return;
}
// 获取双向格式化器实例,用于处理文本的双向显示格式
final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
String summary = null;
// 遍历允许使用的输入法标签列表,构建偏好设置的摘要信息
for (String label : labels) {
if (summary == null) {
summary = bidiFormatter.unicodeWrap(label);
} else {
summary = mContext.getString(R.string.join_many_items_middle, summary,
bidiFormatter.unicodeWrap(label));
}
}
// 设置偏好设置的摘要为构建好的摘要信息
preference.setSummary(summary);
}
}
八、关于PreferenceManager、PreferenceScreen
PreferenceManager
是 Android 中用于管理 Preference 相关操作的重要类。它作为一个核心的协调者,在应用的设置模块中起着关键作用,帮助开发者从 Activity 或 XML 文件中创建和管理 Preference 层次结构。
public class MySettingsActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PreferenceManager preferenceManager = getPreferenceManager();
// 假设在res/xml/中有一个名为preferences.xml的文件
preferenceManager.inflateFromResource(this, R.xml.preferences, null);
}
}
PreferenceScreen
在 Android 的 Preference 框架中是一个顶级容器类,用于组织和管理一组 Preference。它是 Preference 层次结构中的根元素,定义了整个设置界面的布局和内容结构。
层次结构基础、管理显示顺序
可以对PreferenceScreen进行遍历,在实际工作中经常用到
PreferenceScreen preferenceScreen = (PreferenceScreen) getPreferenceManager().inflateFromResource(getActivity(), R.xml.fragment_preferences, null);
if (preferenceScreen!= null) {
PreferenceScreenTraversal.traverse(preferenceScreen);
}
九、工作中遇到的一些Preference问题解决与思考
WLAN直连设备名称布局下方分割线会移动
在进入界面时,分割线由于summary加载前后,会发生移动
解决办法:summary预先占好位置
写在最后:本文大量借助了AI,查询了一些知识,甚至包括本文分为哪几个要点写,然后进行筛选重要点。大家一定要会利用AI,自从有AI工具我也很少看博客了,除非疑难杂症的报错等。