You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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布局截图

Preference布局截图


解决方案

问题一:主题切换无反应的修复

核心问题

  1. SharedPreferences存储不一致:自定义的MyPrefs存储文件和SwitchPreferenceCompat默认使用的系统偏好存储不是同一个,导致开关设置无法被Activity读取到。
  2. 冗余写入操作: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也能立即应用新主题,可通过广播实现:

  1. 在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
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 05:04:53