Android技术问题:用SwitchPreference实现多Activity明暗主题切换及故障排查
问题描述
我正在开发一款对接Firestore的应用,支持用户从RecyclerView列表添加电视节目数据。目前尝试用SharedPreferences结合SwitchPreference实现明暗主题切换设置,但遇到两个问题:
- 开启主题切换设置后无任何反应
- 不清楚如何在多Activity场景下实现该功能
已查阅Google官方文档及线上教学视频,但因实现方式繁多无法定位问题。以下是相关代码:
1. RecyclerView列表Activity部分代码(含SharedPreferences逻辑)
override fun onResume(){ super.onResume() val sharedPreferences = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE) val editor = sharedPreferences.edit() val isDarkTheme = sharedPreferences.getBoolean("night",false) if (!isDarkTheme) { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) editor.putBoolean("night",false) editor.apply() } else { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) editor.putBoolean("night",true) editor.apply() } recyclerViewTV = findViewById(R.id.recyclerViewShows) recyclerViewTV.layoutManager = LinearLayoutManager(this) data = ArrayList<TV>() tvAdapter = TVAdapter(data) recyclerViewTV.adapter = tvAdapter db = FirebaseFirestore.getInstance() db.collection("ProjectTVShows") .get() .addOnSuccessListener { result -> for (document in result) { var show = document.getString("name") var direct = document.getString("director") var view = document.getString("viewership") data.add(TV(show, direct, view)) Log.d("MY DEBUG", "${document.id} => ${document.data}") Log.d("MY DEBUG", "show => $show") Log.d("MY DEBUG", "direct => $direct") Log.d("MY DEBUG", "view => $view") } tvAdapter.notifyDataSetChanged() } .addOnFailureListener { exception -> Log.w("MY DEBUG", "Error getting documents.", exception) } } override fun onCreateOptionsMenu(menu: Menu?): Boolean { val inflater = menuInflater inflater.inflate(R.menu.toolmenu, menu) return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.getItemId()) { R.id.add_shows-> { val intent = Intent(this@TVShowListActivity, AddShowActivity::class.java) startActivity(intent) true } R.id.settings->{ val intent = Intent(this@TVShowListActivity, SettingsActivity::class.java) startActivity(intent) true } else -> super.onOptionsItemSelected(item) } }
2. UserPreferenceFragment代码
class UserPreferenceFragment: PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences, rootKey) } }
3. 明暗主题设置的Preference布局XML代码
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <SwitchPreferenceCompat app:defaultValue="false" app:key="night" app:summary="Toggle between light and dark mode" app:title="@string/light_settings" /> </PreferenceScreen>
Preference布局截图

解决方案
问题一:主题切换无反应的修复
核心问题
- SharedPreferences存储不一致:自定义的
MyPrefs存储文件和SwitchPreferenceCompat默认使用的系统偏好存储不是同一个,导致开关设置无法被Activity读取到。 - 冗余写入操作:
onResume中重复写入读取到的主题值,完全没必要且可能引发逻辑混乱。
修复步骤
1. 统一SharedPreferences实例
修改TVShowListActivity的onResume代码,使用系统默认偏好存储和SwitchPreferenceCompat保持一致:
override fun onResume(){ super.onResume() // 使用系统默认偏好存储,和SwitchPreferenceCompat对齐 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) val isDarkTheme = sharedPreferences.getBoolean("night", false) // 直接根据设置切换主题,移除冗余写入操作 AppCompatDelegate.setDefaultNightMode( if (isDarkTheme) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO ) // RecyclerView和Firestore相关代码保持不变... }
2. 添加Preference变化监听,实时切换主题
在UserPreferenceFragment中监听开关状态,切换时立即生效:
class UserPreferenceFragment: PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences, rootKey) // 获取主题开关并添加监听 val nightModePref = findPreference<SwitchPreferenceCompat>("night") nightModePref?.setOnPreferenceChangeListener { _, newValue -> val isDarkMode = newValue as Boolean AppCompatDelegate.setDefaultNightMode( if (isDarkMode) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO ) true // 允许保存设置 } } }
问题二:多Activity场景下的主题统一实现
实现思路
创建一个BaseActivity作为所有业务Activity的父类,在BaseActivity中统一处理主题应用逻辑,避免重复代码。
具体实现
1. 编写BaseActivity
abstract class BaseActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { // 在super.onCreate前应用主题,避免界面闪烁 applyTheme() super.onCreate(savedInstanceState) } private fun applyTheme() { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) val isDarkTheme = sharedPreferences.getBoolean("night", false) AppCompatDelegate.setDefaultNightMode( if (isDarkTheme) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO ) } }
2. 所有业务Activity继承BaseActivity
比如TVShowListActivity、AddShowActivity、SettingsActivity:
class TVShowListActivity : BaseActivity() { // 原有业务代码保持不变,移除之前onResume中的主题切换逻辑 }
额外优化:切换主题后实时刷新所有Activity
如果希望已打开的Activity也能立即应用新主题,可通过广播实现:
- 在UserPreferenceFragment的监听中发送广播:
nightModePref?.setOnPreferenceChangeListener { _, newValue -> val isDarkMode = newValue as Boolean AppCompatDelegate.setDefaultNightMode( if (isDarkMode) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO ) // 发送广播通知所有Activity重启 sendBroadcast(Intent("ACTION_THEME_CHANGED")) true }
- 在BaseActivity中注册广播接收器:
private lateinit var themeChangeReceiver: BroadcastReceiver override fun onCreate(savedInstanceState: Bundle?) { applyTheme() super.onCreate(savedInstanceState) // 注册主题变化广播接收器 themeChangeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { recreate() // 重启当前Activity应用新主题 } } registerReceiver(themeChangeReceiver, IntentFilter("ACTION_THEME_CHANGED")) } override fun onDestroy() { super.onDestroy() unregisterReceiver(themeChangeReceiver) // 注销接收器 }
内容的提问来源于stack exchange,提问作者SRBeginnerDev
相关产品推荐
相关产品推荐

