ContextCompat.getColor()忽略夜间模式

20

TL,DR;

ContextCompat.getColor() should use night colors (values-night/colors.xml) when night mode is enabled but it currently does not. The issue arises when implementing a dark theme for an Android app using AppCompatDelegate.setDefaultNightMode(). Colors change correctly between normal and dark modes based on the values set in values/colors.xml and values-night/colors.xml, but ContextCompat.getColor() continues to use the normal colors instead of the appropriate night colors. This issue may be resolved by adjusting the settings in your build.gradle file.

implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0-beta01'

请问我做错了什么?

PS:我已经看过以下问题,但它没有回答我的问题https://dev59.com/trbna4cB1Zd3GeqPVho4


1
我无法重现它,我正在使用appcompat 1.1.0,并且在强制夜间模式后,来自XML和ContextCompat.getColor()的两种颜色都返回有效值。您能否用代码片段详细说明您的问题(当您更改夜间模式/获取颜色时)? - Pawel
1
你可能正在向getColor传递无效的上下文(例如ApplicationContext)。你能分享一下你是如何调用getColor的吗? - guipivoto
@W0rmH0le 是的,我正在传递一个ApplicationContext,我不知道我需要使用activity才能让它工作!你可以将此作为官方答案给出吗,这样我就可以将你的答案标记为有效答案了吗? - Mathieu de Brito
@Pawel 谢谢你的反馈,它帮助我知道这是我的代码中的一个错误 :) - Mathieu de Brito
很高兴我能帮到你!我曾经遇到过类似的问题……最后,我像你一样使用了应用程序上下文。 - guipivoto
4个回答

28

我在夜间模式方面遇到了类似的问题。有些屏幕是正常的,但其他屏幕仍然使用常规主题。最终我发现,我在使用应用程序上下文而不是当前活动上下文实例化一些视图。由于某种原因,应用程序上下文不能追踪此类信息。

所以,请更新您的代码,使用当前活动的上下文而不是应用程序上下文。

供其他用户参考。请避免:

ContextCompat.getColor(getApplicationContext(), R.id.myColor)

并使用:

// In a Activity
ContextCompat.getColor(this, R.id.myColor)

// In a View
ContextCompat.getColor(getContext(), R.id.myColor)

// In a Fragment (check against null because getContext can trigger a NPE
Context context = getContext()
if (context != null) {
    ContextCompat.getColor(context, R.id.myColor)
}

4
Application 是原始的上下文,它总是引用默认样式的值并忽略任何配置。另一方面,AppCompatActivity 处理适当的配置,并链接到 AppCompatDelegate - Pawel
当暗模式开启时,这个不会变成暗色... - joninx

4

应用程序上下文不知道当前主题或白天/黑夜的任何信息,因此如果您从应用程序上下文获取资源,则会获得默认应用程序主题中的资源。解决此问题的方法是使用活动/片段上下文,但在某些情况下,您可能没有活动或片段,仅具有应用程序上下文。

我创建了一个上下文包装器类,以向应用程序上下文添加日夜主题:

import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat

class ThemedContext(application: Context) {

    private val themedContext: Context

    init {
        val res: Resources = application.resources
        val configuration = Configuration(res.configuration)
        val filter = res.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv()

        configuration.uiMode = when (AppCompatDelegate.getDefaultNightMode()) {
            AppCompatDelegate.MODE_NIGHT_NO -> Configuration.UI_MODE_NIGHT_NO or filter
            AppCompatDelegate.MODE_NIGHT_YES -> Configuration.UI_MODE_NIGHT_YES or filter
            else -> res.configuration.uiMode
        }

        themedContext = application.createConfigurationContext(configuration)
    }

    fun getColor(@ColorRes id: Int) = ContextCompat.getColor(themedContext, id)
    fun getDrawable(@DrawableRes id: Int) = ContextCompat.getDrawable(themedContext, id)
    //todo Add other getter methods as needed...
}

这段代码已经经过测试并且可行。


2

我遇到了和你类似的问题,发现问题的核心是应用程序没有像活动那样的主题包装器。因此,W0rmH0le的答案可以解决这个问题。但是对于我来说,有很多代码无法获取活动或视图的上下文。所以我想为什么不创建一个单例上下文包装器夜间模式主题呢?以下是代码,对我而言它可以正常工作。

Resources res = getApplication().getResources();
Configuration configuration = new Configuration(res.getConfiguration());
int nightNode = AppCompatDelegate.getDefaultNightMode();
if (nightNode == AppCompatDelegate.MODE_NIGHT_NO) {
    configuration.uiMode = Configuration.UI_MODE_NIGHT_NO | (res.getConfiguration().uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
} else if (nightNode == AppCompatDelegate.MODE_NIGHT_YES) {
    configuration.uiMode = Configuration.UI_MODE_NIGHT_YES| (res.getConfiguration().uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
} else {
    configuration.uiMode = res.getConfiguration().uiMode;
}
mThemeContext = getApplication().createConfigurationContext(configuration);

然后您可以使用mThemeContext替换Application,并且您可以在夜间模式后找到正确的颜色。

0

对于那些稍后发现的人。 我稍微调整了解决方案 @ygngy

此答案解决了运行时的配置问题,并在每次调用时保留了实际上下文。它也可以与匕首一起使用,以避免每次生成数百万个对象。

在某些情况下,这是必要的,例如需要在运行时获取颜色的情况,例如从服务器接收数据时的映射器等。

interface ThemeContextProvider {
    fun getColor(@ColorRes id: Int): Int
    fun getDrawable(@DrawableRes id: Int): Drawable?
}

class ThemeContextProviderImpl @Inject constructor(
    private val application: Context
) : ThemedContext {

    private val res: Resources = application.resources
    private val filter by lazy {
        application.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv()
    }
    private lateinit var themedContext: Context
    private var configuration = Configuration(res.configuration)

    init {
        getCurrentContext()
    }

    private fun getCurrentContext() {
        configuration.uiMode = when (AppCompatDelegate.getDefaultNightMode()) {
            AppCompatDelegate.MODE_NIGHT_NO -> Configuration.UI_MODE_NIGHT_NO or filter
            AppCompatDelegate.MODE_NIGHT_YES -> Configuration.UI_MODE_NIGHT_YES or filter
            else -> res.configuration.uiMode
        }
        themedContext = application.createConfigurationContext(configuration)
    }

    override fun getColor(@ColorRes id: Int): Int {
        getCurrentContext()
        return ContextCompat.getColor(themedContext, id)
    }

    override fun getDrawable(@DrawableRes id: Int): Drawable? {
        getCurrentContext()
        return ContextCompat.getDrawable(themedContext, id)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接