Android设置页面开发:Preference库从入门到精通
1. 从“设置”页面说起为什么我们需要Preference如果你开发过Android应用尤其是带有一点复杂度的工具类或内容类应用几乎都绕不开一个功能模块设置页面。用户在这里调整主题、开关通知、选择下载路径、管理账号……这些选项背后往往对应着应用内部需要持久化保存的键值对数据。作为开发者你的第一反应可能是用SharedPreferencesAPI直接在代码里getSharedPreferences().edit().putString().apply()一把梭。这当然能实现功能但随之而来的是大量重复、枯燥且易出错的样板代码你需要为每个设置项手动编写布局XML、在Activity里findViewById、监听点击或选择事件、将UI状态同步到SharedPreferences、再从SharedPreferences读取并应用到业务逻辑中。更头疼的是用户体验的一致性。一个标准的设置页面应该有清晰的分组、统一的样式比如右侧的箭头、开关、摘要文本、便捷的操作反馈比如点击后跳转或弹出选择框甚至还需要考虑配置变更如屏幕旋转后的状态恢复。自己从头实现这套UI和交互工作量不小且很难保证和系统设置或其他主流应用保持一致的交互直觉。这时Android框架提供的Preference库就派上用场了。它不是指一个具体的UI控件而是一整套完整的架构专门用于构建设置界面。它的核心思想是“声明式配置”和“数据自动绑定”。你不再需要手动处理SharedPreferences的读写和UI同步只需要在XML文件中声明你需要的设置项类型比如开关、列表、文本输入框架会自动为你生成对应的界面并处理好数据的存储、读取以及界面状态的更新。这极大地提升了开发效率降低了维护成本并且保证了设置界面交互规范的一致性。在最新的Android开发中Google推荐使用AndroidX Preference 库它是对旧版android.preference包的现代化替代提供了更好的Material Design组件支持、更灵活的定制能力以及与Jetpack组件如ViewModel、LiveData集成的可能性。接下来我将结合一个完整的实例项目带你从零开始深入理解Preference的使用、定制以及那些官方文档里不会写的“坑”。2. 项目环境搭建与基础依赖引入在开始编写任何Preference代码之前我们首先需要建立一个正确的项目环境。如果你还在使用原生的android.preference包我强烈建议你立即迁移到AndroidX版本。它不仅修复了许多旧版的bug而且会持续获得更新和支持。2.1 创建项目与添加依赖使用Android Studio新建一个项目模板选择“Empty Views Activity”即可。确保你的app/build.gradle文件中的依赖项包含了AndroidX Preference库。打开app/build.gradle (Module: app)文件在dependencies块中添加以下依赖dependencies { implementation androidx.appcompat:appcompat:1.6.1 // AndroidX Preference 库核心依赖 implementation androidx.preference:preference:1.2.1 // 如果需要使用PreferenceFragmentCompat还需要添加以下依赖通常已包含在appcompat中但显式声明更安全 implementation androidx.fragment:fragment:1.6.2 // 其他依赖... }这里我使用了1.2.1版本你可以通过 AndroidX版本页面 查询最新稳定版。添加依赖后点击Sync Now同步项目。注意确保你的项目已经迁移到AndroidX。如果项目是旧的可以通过菜单栏的Refactor - Migrate to AndroidX...进行迁移。迁移后所有android.preference.*的import语句都应该被替换为androidx.preference.*。2.2 理解核心组件PreferenceFragmentCompat在AndroidX中承载Preference界面的核心是一个Fragment即PreferenceFragmentCompat。这是与旧版PreferenceActivity或PreferenceFragment最大的区别之一。使用Fragment意味着你的设置页面可以更容易地嵌入到现有的Activity架构中例如作为ViewPager的一页或者放在Navigation组件里管理。我们的主Activity将非常简单仅仅作为一个容器来承载这个Fragment。首先修改activity_main.xml布局文件将其变成一个简单的Fragment容器?xml version1.0 encodingutf-8? FrameLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:idid/settings_container android:layout_widthmatch_parent android:layout_heightmatch_parent /然后在MainActivity.kt(如果你用Java则是MainActivity.java) 中我们使用FragmentManager将我们即将创建的SettingsFragment添加进去// MainActivity.kt package com.example.preferenceDemo import androidx.appcompat.app.AppCompatActivity import android.os.Bundle class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 将SettingsFragment添加到容器中 supportFragmentManager.beginTransaction() .replace(R.id.settings_container, SettingsFragment()) .commit() } }至此我们的舞台已经搭好接下来就是主角SettingsFragment和它的剧本——Preference XML文件的登场。3. 编写Preference XML声明你的设置项Preference的强大之处在于其声明式配置。我们通过一个XML文件来定义整个设置界面的结构和所有条目。这个文件需要放在res/xml/目录下。如果该目录不存在请手动创建。右键点击res目录 - New - Android Resource Directory类型选择xml。然后在该目录下新建一个文件命名为preferences.xml。3.1 PreferenceScreen设置的根容器所有的Preference元素都必须包含在一个PreferenceScreen根标签内。你可以把它理解为一个设置页面的“画布”。?xml version1.0 encodingutf-8? PreferenceScreen xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto !-- 所有的设置项都将在这里定义 -- /PreferenceScreen注意我们引入了app命名空间这是为了使用AndroidX Preference库和Material Components提供的一些额外属性。3.2 常用Preference类型详解与实例下面我们将在preferences.xml中定义几种最常用的Preference类型并解释其关键属性。3.2.1 SwitchPreferenceCompat开关选项这是最常见的设置项用于布尔值true/false的配置比如“启用夜间模式”、“接收推送通知”。SwitchPreferenceCompat android:keypref_key_enable_notification android:title接收通知 android:summary开启后应用将向您推送重要消息 android:defaultValuetrue app:iconSpaceReservedfalse /android:key(必须)这是该设置项的唯一标识符也是它在SharedPreferences中存储时使用的键名。命名最好有规律如pref_key_前缀。android:title显示在主行的标题文字。android:summary显示在标题下方的摘要文字用于详细说明。它可以是一个静态文本也可以通过属性android:summarystring/...”动态绑定到存储的值后面会讲。android:defaultValue该设置的默认值。对于SwitchPreferenceCompat就是true或false。这个值会在用户第一次打开应用且该键值不存在于SharedPreferences时自动写入。app:iconSpaceReserved设置为false表示不为图标预留空间。在Material Design的设置界面中通常不显示图标以保持简洁。如果你需要图标可以设置为true并使用android:icon属性。3.2.2 ListPreference列表选择当用户需要从几个预定义的选项中选择其一时使用比如“主题颜色”、“语言选择”、“视频质量”。ListPreference android:keypref_key_video_quality android:title视频播放质量 android:summary选择视频流的清晰度 android:entriesarray/entries_video_quality android:entryValuesarray/values_video_quality android:defaultValue2 app:useSimpleSummaryProvidertrue /这里引入了两个新的数组资源。我们需要在res/values/arrays.xml文件中定义它们!-- res/values/arrays.xml -- ?xml version1.0 encodingutf-8? resources !-- 显示给用户看的文本 -- string-array nameentries_video_quality item流畅 (360P)/item item标清 (720P)/item item高清 (1080P)/item item超清 (2K)/item /string-array !-- 实际存储的值通常是对应的标识符或码率值 -- string-array namevalues_video_quality item1/item item2/item item3/item item4/item /string-array /resourcesandroid:entries指向一个字符串数组资源定义了显示在对话框列表中的选项文本。android:entryValues指向一个字符串数组资源定义了每个选项对应的实际存储值。entries和entryValues必须一一对应。app:useSimpleSummaryProvider这是一个非常实用的属性。设置为true后摘要区域会自动显示当前选中的entries中的文本如“高清 (1080P)”而不是固定的android:summary。这极大地提升了用户体验无需你再写代码去同步。3.2.3 EditTextPreference文本输入用于让用户输入字符串比如“服务器地址”、“昵称”。EditTextPreference android:keypref_key_custom_server android:title自定义服务器 android:summary请输入服务器IP地址或域名 android:dialogTitle设置服务器地址 android:dialogMessage请确保地址格式正确例如api.example.com android:defaultValuedefault.example.com /点击这个Preference会弹出一个带有输入框的对话框。android:dialogTitle和android:dialogMessage用于定制这个对话框。踩坑点EditTextPreference默认的输入类型是普通文本。如果你需要输入数字、密码等需要在代码中进行定制我们会在第5节详细说明。3.2.4 PreferenceCategory分组标题当设置项较多时用分类标题进行分组会让界面更清晰。PreferenceCategory android:title账户与安全 SwitchPreferenceCompat android:keypref_key_login_remember android:title记住登录状态 android:defaultValuetrue / EditTextPreference android:keypref_key_auto_logout android:title自动登出时间分钟 android:summary设置为0表示不自动登出 android:defaultValue30 / /PreferenceCategoryPreferenceCategory本身不是一个可交互的设置项它只是一个带标题的分组容器其下的所有Preference在视觉上会被归为一组。3.3 构建完整的preferences.xml示例将以上元素组合起来一个功能丰富的设置页面XML就定义好了!-- res/xml/preferences.xml -- ?xml version1.0 encodingutf-8? PreferenceScreen xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto PreferenceCategory android:title通用设置 SwitchPreferenceCompat android:keypref_key_enable_notification android:title接收通知 android:summary开启后应用将向您推送重要消息 android:defaultValuetrue app:iconSpaceReservedfalse / ListPreference android:keypref_key_video_quality android:title视频播放质量 android:summary选择视频流的清晰度 android:entriesarray/entries_video_quality android:entryValuesarray/values_video_quality android:defaultValue2 app:useSimpleSummaryProvidertrue / /PreferenceCategory PreferenceCategory android:title网络与连接 EditTextPreference android:keypref_key_custom_server android:title自定义服务器 android:summary请输入服务器IP地址或域名 android:dialogTitle设置服务器地址 android:dialogMessage请确保地址格式正确 android:defaultValuedefault.example.com / SwitchPreferenceCompat android:keypref_key_use_mobile_data android:title允许使用移动网络 android:summary在Wi-Fi不可用时使用蜂窝数据 android:defaultValuefalse / /PreferenceCategory PreferenceCategory android:title关于 Preference android:keypref_key_app_version android:title版本号 android:summaryv1.0.0 / Preference android:keypref_key_user_agreement android:title用户协议 android:summary点击查看 / /PreferenceCategory /PreferenceScreen注意最后一个分类中使用了基础的Preference控件。它通常用于展示信息或触发一个动作如跳转到网页本身不存储任何值。它的点击事件需要我们在代码中处理。4. 创建Fragment并加载Preference界面现在我们来创建承载这个XML的Fragment。在java/你的包名/目录下或kotlin/你的包名/新建一个Kotlin类或Java类命名为SettingsFragment并让它继承自PreferenceFragmentCompat。// SettingsFragment.kt package com.example.preferenceDemo import android.os.Bundle import androidx.preference.PreferenceFragmentCompat class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { // 这是必须重写的方法用于指定Preference界面的资源文件 setPreferencesFromResource(R.xml.preferences, rootKey) } }就是这么简单setPreferencesFromResource(R.xml.preferences, rootKey)这一行代码框架就会自动解析我们编写的preferences.xml文件构建出完整的UI层级并建立起所有Preference项与底层一个默认的SharedPreferences文件通常以包名命名之间的双向数据绑定。现在运行应用你应该能看到一个完整的、可交互的设置页面。开关可以点击列表项点击会弹出选择框文本项点击会弹出输入对话框。所有修改的值都会自动保存到SharedPreferences中即使退出应用再进来状态也会保持。5. 进阶监听变化、自定义与数据获取基础功能跑通了但真实项目中的需求往往更复杂。比如当用户关闭通知开关时我们可能需要立刻取消系统的通知渠道注册当服务器地址改变时需要重启网络服务或者我们需要定制一个输入数字的对话框。5.1 监听Preference的变化PreferenceFragmentCompat提供了便捷的方式来监听特定Preference的变化。我们需要找到对应的Preference对象然后为其设置一个OnPreferenceChangeListener。通常我们在onCreatePreferences方法之后视图创建完成的onViewCreated回调中进行这些操作。// SettingsFragment.kt class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences, rootKey) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 1. 找到开关Preference val notificationPreference findPreferenceSwitchPreferenceCompat(pref_key_enable_notification) notificationPreference?.setOnPreferenceChangeListener { preference, newValue - // newValue 是改变后的新值对于Switch是Boolean类型 val isEnabled newValue as Boolean if (isEnabled) { // 用户打开了通知执行相关逻辑例如创建通知渠道 // createNotificationChannel() Log.d(Settings, 通知已启用) } else { // 用户关闭了通知执行相关逻辑例如取消渠道 // cancelNotificationChannel() Log.d(Settings, 通知已禁用) } true // 返回true表示接受这个改变false会拒绝改变UI状态会回滚 } // 2. 找到列表Preference val qualityPreference findPreferenceListPreference(pref_key_video_quality) qualityPreference?.setOnPreferenceChangeListener { preference, newValue - // newValue 是 entryValues 中的值String类型 val selectedQuality newValue as String // 根据 selectedQuality (如 2) 更新播放器配置 // updatePlayerQuality(selectedQuality) Log.d(Settings, 视频质量切换为: $selectedQuality) true } // 3. 找到文本输入Preference val serverPreference findPreferenceEditTextPreference(pref_key_custom_server) serverPreference?.setOnPreferenceChangeListener { preference, newValue - val newServer newValue as String // 验证服务器地址格式 if (isValidServerAddress(newServer)) { // 重启网络服务 // restartNetworkService(newServer) Log.d(Settings, 服务器地址更新为: $newServer) true } else { // 格式无效给用户一个提示这里用Toast简单演示生产环境应用更友好的方式 Toast.makeText(requireContext(), 服务器地址格式无效, Toast.LENGTH_SHORT).show() false // 返回false拒绝此次更改输入框会保持原值 } } // 4. 处理普通Preference的点击事件如“用户协议” val agreementPreference findPreferencePreference(pref_key_user_agreement) agreementPreference?.setOnPreferenceClickListener { // 跳转到WebView或系统浏览器 // val intent Intent(Intent.ACTION_VIEW, Uri.parse(https://example.com/agreement)) // startActivity(intent) Toast.makeText(requireContext(), 跳转到用户协议页面, Toast.LENGTH_SHORT).show() true // 返回true表示点击事件已被处理 } } private fun isValidServerAddress(address: String): Boolean { // 简单的格式校验逻辑 return address.isNotBlank() address.contains(.) } }通过findPreferenceT(key)方法我们可以获取到XML中定义的任何一个Preference对象。注意类型参数T需要和XML中定义的类型匹配。重要经验findPreference的调用必须在onCreatePreferences之后因为此时Preference的层级结构才被创建。onViewCreated是一个安全且常用的位置。另外findPreference可能返回null所以务必使用安全调用操作符?.。5.2 自定义Preference以数字输入框为例EditTextPreference默认的输入框是EditText其输入类型是普通文本。如果我们想限制用户只能输入数字比如“自动登出时间”就需要进行定制。创建自定义Preference有多种方式最简单的是继承现有的类并重写关键方法。我们创建一个NumberEditTextPreference。// NumberEditTextPreference.kt package com.example.preferenceDemo import android.content.Context import android.text.InputType import android.util.AttributeSet import androidx.preference.EditTextPreference class NumberEditTextPreference : EditTextPreference { // 必须提供这三个构造函数以便XML可以正常实例化 constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) override fun onAttached() { super.onAttached() // 在Preference被添加到界面时我们可以配置其对话框 // 但更常见的做法是在显示对话框时配置EditText } // 更推荐的做法是重写onCreateDialogView或onBindDialogView来定制对话框内容 // 这里我们采用一个更直接的方法在点击后、对话框显示前设置EditText的属性 override fun onClick() { // 先调用父类方法确保对话框会被创建 super.onClick() // 在对话框显示后获取其中的EditText并设置输入类型 dialog?.window?.decorView?.post { val editText dialog?.findViewByIdandroid.widget.EditText(android.R.id.edit) editText?.inputType InputType.TYPE_CLASS_NUMBER // 设置为纯数字键盘 // 你还可以设置其他属性如hint, maxLength等 // editText?.hint 请输入数字 } } }然后在preferences.xml中使用我们自定义的类。注意要使用完整的包名com.example.preferenceDemo.NumberEditTextPreference android:keypref_key_auto_logout android:title自动登出时间分钟 android:summary设置为0表示不自动登出 android:defaultValue30 /这样当用户点击这个设置项时弹出的输入框键盘就会是数字键盘。这是一种轻量级的定制方式。对于更复杂的定制比如自定义布局、添加验证逻辑可能需要重写onCreateDialogView并返回一个完全自定义的视图。5.3 在应用其他部分读取Preference值设置的值最终都存储在SharedPreferences中。虽然Preference库自动管理了存储但在业务代码如Activity、Service、ViewModel中我们仍然需要读取这些值。AndroidX Preference库默认使用一个全局的PreferenceManager.getDefaultSharedPreferences(context)实例。为了保持一致性我们应该使用相同的机制来读取。方法一直接使用SharedPreferences API在任何有Context的地方如Activity都可以这样读取// 在某个Activity或ViewModel中 val sharedPrefs PreferenceManager.getDefaultSharedPreferences(this) val isNotificationEnabled sharedPrefs.getBoolean(pref_key_enable_notification, true) // 第二个参数是默认值应与XML中一致 val serverAddress sharedPrefs.getString(pref_key_custom_server, default.example.com) val videoQuality sharedPrefs.getString(pref_key_video_quality, 2)方法二使用Preference DataStore未来方向对于新项目Google推荐使用Preference DataStore替代SharedPreferences它提供了异步API、强类型安全和更好的错误处理。AndroidX Preference库也提供了与DataStore的集成方案但这涉及更多迁移工作。如果你的应用还不复杂继续使用默认的SharedPreferences是完全可行的。6. 主题、样式与深色模式适配默认的Preference界面可能不符合你的应用主题。AndroidX Preference库基于Material Components因此可以通过主题进行高度定制。6.1 应用Material主题确保你的res/values/themes.xml文件继承了Material Components主题例如resources xmlns:toolshttp://schemas.android.com/tools !-- Base application theme. -- style nameTheme.PreferenceDemo parentTheme.MaterialComponents.DayNight.NoActionBar !-- 定制你的主题颜色 -- item namecolorPrimarycolor/purple_500/item item namecolorPrimaryVariantcolor/purple_700/item item namecolorOnPrimarycolor/white/item item namecolorSecondarycolor/teal_200/item item namecolorSecondaryVariantcolor/teal_700/item item namecolorOnSecondarycolor/black/item !-- 启用Edge-to-Edge -- item nameandroid:windowLayoutInDisplayCutoutModeshortEdges/item item nameandroid:windowLightStatusBar?attr/isLightTheme/item item nameandroid:windowLightNavigationBar?attr/isLightTheme/item /style /resourcesDayNight.NoActionBar主题会自动支持深色模式。你需要在res/values-night/目录下提供对应的颜色资源。6.2 定制Preference样式如果你想微调Preference的外观比如字体大小、间距可以自定义样式。首先在themes.xml中为Preference指定一个自定义样式style nameTheme.PreferenceDemo parentTheme.MaterialComponents.DayNight.NoActionBar ... !-- 覆盖Preference的主题属性 -- item namepreferenceThemestyle/PreferenceThemeOverlay/item /style然后在res/values/styles.xml中定义这个覆盖样式style namePreferenceThemeOverlay parentstyle/PreferenceThemeOverlay.v14.Material !-- 这里可以覆盖Preference库内部使用的属性 -- !-- 例如修改标题文字外观 -- item nameandroid:textAppearanceLargestyle/TextAppearance.AppCompat.Title/item !-- 修改摘要文字颜色 -- item nameandroid:textColorSecondary?attr/colorOnSurfaceVariant/item /style更精细的控件样式定制需要为特定的Preference控件定义样式这需要查阅AndroidX Preference库的源码或文档了解其内部使用的样式属性过程较为复杂。对于大多数应用使用默认主题并配合应用的整体色彩体系已经足够。7. 常见问题排查与实战心得在实际使用中你可能会遇到一些意料之外的情况。这里分享几个我踩过的坑和解决方案。7.1 Preference的Key冲突与数据污染问题现象两个不同的Preference不小心使用了相同的android:key导致数据互相覆盖或者在清理应用数据后默认值没有正确写入。根因分析android:key是存储在SharedPreferences中的唯一键。如果重复后一个Preference的读写会覆盖前一个。默认值仅在键不存在时由Preference框架在第一次访问时自动写入。如果代码中其他地方先于Preference界面初始化就使用了SharedPreferences并操作了该键可能会导致默认值逻辑紊乱。解决方案命名规范建立统一的key命名规范如模块名_功能名settings_notification_enable并在团队内共享一个常量文件。隔离作用域对于非全局的设置考虑使用getSharedPreferences(module_name, MODE_PRIVATE)创建独立的Preference文件并在自定义的PreferenceFragmentCompat子类中通过preferenceManager.sharedPreferencesName module_name来指定。谨慎处理默认值在业务代码中读取Preference值时传入的默认值应与XML中定义的android:defaultValue保持一致。更好的做法是将这些默认值也定义在常量文件中。7.2 动态摘要Summary不更新问题现象为ListPreference设置了app:useSimpleSummaryProvidertrue但切换选项后摘要并没有实时更新为选中的条目文本。排查过程检查XML配置确认属性已正确设置。确认使用的是AndroidX库且版本较新旧版可能有bug。在onPreferenceChangeListener中打印日志确认监听器被触发且返回true。解决方案这个问题通常不是配置错误。SimpleSummaryProvider的工作机制是在Preference的当前值发生变化后会自动更新摘要。但有时UI刷新需要一点时间或特定触发。一个可靠的实践是在onPreferenceChangeListener中手动触发一下摘要更新qualityPreference?.setOnPreferenceChangeListener { preference, newValue - // ... 你的业务逻辑 ... // 手动通知摘要可能需要更新对于SimpleSummaryProvider这步有时是必要的 preference.summary newValue.toString() // 这只是个示例SimpleSummaryProvider会自己处理 // 或者更通用的方法调用preference的相应方法如果可用 (preference as? ListPreference)?.summaryProvider?.provideSummary(preference)?.let { preference.summary it } true }更常见的做法是依赖框架自动更新。如果自动更新失效检查是否在别处错误地设置了android:summary静态文本这会覆盖动态提供器。7.3 自定义Preference的布局错乱问题现象自定义了一个复杂的Preference比如包含多个按钮的布局在列表中出现高度异常、点击区域错乱等问题。根因分析Preference的列表项视图是通过Preference.onCreateView()和适配器机制复用的。如果自定义布局的根元素不是LinearLayout、RelativeLayout等常见布局或者设置了不匹配的布局参数就可能与Preference的内部测量逻辑冲突。解决方案遵循模板自定义布局时尽量参考AndroidX Preference库中内置Preference的布局文件如preference_material.xml。可以从中拷贝根布局的样式和关键View的ID如android:idandroid:id/title。使用PreferenceViewHolder如果重写了onBindViewHolder方法确保调用了super.onBindViewHolder(holder)以便框架完成基础绑定。隔离点击事件如果自定义布局中有可点击的子View如按钮需要处理好这些子View的点击事件并调用preference.performClick()或preference.onClick()来触发Preference本身的点击逻辑如弹出对话框避免事件冲突。7.4 与ViewModel/LiveData集成在MVVM架构中我们可能希望将设置值暴露为LiveData以便在UI中观察。虽然Preference库本身不直接提供LiveData支持但我们可以通过扩展轻松实现。一种简单的方式是创建一个工具类或使用PreferenceDataStore它原生支持Flow可转换为LiveData。如果仍使用SharedPreferences可以这样封装class SettingsRepository(private val context: Context) { private val sharedPrefs PreferenceManager.getDefaultSharedPreferences(context) val notificationEnabledLiveData MutableLiveDataBoolean() init { // 初始化LiveData的值 notificationEnabledLiveData.value sharedPrefs.getBoolean(pref_key_enable_notification, true) // 监听SharedPreferences变化需要注册和反注册 // 注意SharedPreferences的OnSharedPreferenceChangeListener在API 31上有内存泄漏风险需谨慎使用。 // 更好的方式是迁移到DataStore。 } fun setNotificationEnabled(enabled: Boolean) { sharedPrefs.edit().putBoolean(pref_key_enable_notification, enabled).apply() notificationEnabledLiveData.postValue(enabled) } }然后在Fragment中观察这个LiveData。不过这引入了额外的状态同步问题。对于设置页面这种本身由UI驱动数据变化的场景直接在OnPreferenceChangeListener中处理业务逻辑往往更直接、清晰。MVVM的引入应评估其必要性避免过度设计。经过以上七个部分的拆解从环境搭建、XML编写、Fragment集成到事件监听、自定义控件、主题适配再到问题排查你应该已经掌握了AndroidX Preference库的绝大部分核心用法。它绝不是简单的UI组件而是一套完整的设置界面解决方案。理解其“声明式”和“自动绑定”的设计哲学能让你在开发类似配置功能时事半功倍写出更稳健、更易维护的代码。

相关新闻

基于YOLOv8的智能PCB缺陷检测系统:从数据到部署全流程实践

基于YOLOv8的智能PCB缺陷检测系统:从数据到部署全流程实践

1. 项目概述:为什么PCB缺陷检测需要“智能”化?在电子制造业干了十几年,我经手过的PCB板少说也有几十万片。早期我们怎么检?靠人眼,在强光下拿着放大镜一片片看,效率低不说,人眼疲劳后漏检、误检…

2026/10/19 14:18:16 阅读更多 →
Python脚本自动化别在本地死磕了!python在线运行帮你10分钟跑通全流程

Python脚本自动化别在本地死磕了!python在线运行帮你10分钟跑通全流程

Python脚本自动化别在本地死磕了!python在线运行帮你10分钟跑通全流程 昨天帮朋友处理一批CSV数据,3000多条记录要做清洗、去重、分类汇总。 朋友的第一反应是:打开本地IDE,装pandas,写脚本,跑一遍……结果…

2026/10/19 14:18:18 阅读更多 →
3步精准测量:用XInputTest免费检测游戏手柄延迟与轮询率

3步精准测量:用XInputTest免费检测游戏手柄延迟与轮询率

3步精准测量:用XInputTest免费检测游戏手柄延迟与轮询率 【免费下载链接】XInputTest Xbox 360 Controller (XInput) Polling Rate Checker 项目地址: https://gitcode.com/gh_mirrors/xin/XInputTest 你是否曾在激烈的游戏对抗中,明明已经按下了…

2026/10/17 23:41:22 阅读更多 →

最新新闻

Flutter性能优化:benchmark_harness鸿蒙化适配与微基准测试实战

Flutter性能优化:benchmark_harness鸿蒙化适配与微基准测试实战

做 Flutter 性能优化的人多少都和 benchmark_harness 打过交道——这是 Dart 官方维护的微基准测试库,核心价值是让你用标准化的方式量化函数耗时,而不是靠 print 打时间戳拍脑袋。但大多数人没用它跑过鸿蒙环境。我这次要交代的,就是把它完整…

2026/10/19 14:19:06 阅读更多 →
Flask + uni-app 电商小程序实战:从后端设计到前端联调

Flask + uni-app 电商小程序实战:从后端设计到前端联调

如果你正打算做一个电商类的购物小程序,又不想把前后端拆得太开、来回折腾,那 Python Flask uni-app 这套组合,我实测下来很适合。我最近完整做了一个商城购物平台的电商小程序,从数据库设计、后端接口到前端页面全部自己写。标题…

2026/10/19 14:19:06 阅读更多 →
AI写作如何支撑公众号矩阵:一个人批量运营多号的完整方法

AI写作如何支撑公众号矩阵:一个人批量运营多号的完整方法

做公众号矩阵这件事,我是从去年才开始认真折腾的。起因很直接——手头几个号分别对应不同的内容方向,靠我一个人晚上下班后一篇篇手工码字,根本写不过来。后来我把AI写作真正纳入生产流程,才摸索出一套一个人也能批量运营多个账号…

2026/10/19 14:19:06 阅读更多 →
代码评审工程化:用open-code-review构建高效自动化评审体系

代码评审工程化:用open-code-review构建高效自动化评审体系

代码评审这事儿,做了这么多年,我见过太多团队把 Review 做成“走过场”。打开 Pull Request 页面,扫一眼标题,鼠标滚轮滑到最底部,点一下 Approve,完事。你以为你在做质量保障,实际上你只是在给…

2026/10/19 14:19:05 阅读更多 →
Open Code Review:基于Git Diff与本地LLM Agent的可审计代码评审范式

Open Code Review:基于Git Diff与本地LLM Agent的可审计代码评审范式

1. “open-code-review”不是个工具名,而是一类新型代码评审范式的代号最近在几个技术社区里频繁看到“open-code-review”这个词,它不像 git clone 或 npm install 那样指向某个具体命令,也不像 SonarQube、CodeClimate 那样对应一个安装包。…

2026/10/19 14:19:05 阅读更多 →
基于Socket的TCP Linux聊天系统实战:从协议设计到文件传输

基于Socket的TCP Linux聊天系统实战:从协议设计到文件传输

做过Linux网络编程的人,大概率都自己动手写过聊天程序。但很多人的练习项目在“能收发字符串”这一步就停了,再往上基本就是查文档、复制粘贴、能跑就算完。这里想完整分享一个我自己完整搭过的项目,基于Socket的TCP协议Linux聊天系统&#x…

2026/10/19 14:17:57 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →