让安卓上的WebView与prefers-color-scheme: dark一起工作

39

我有一个使用webview的Android应用程序,最近我在尝试通过使用新的@media (prefers-color-scheme: dark)CSS语法来添加暗黑主题。我已经在我的页面上编写了正确的CSS,如果我在Chrome中打开,并启用Chrome的暗模式,它就可以正常工作。我还让我的AppTheme继承了Theme.AppCompat.DayNight,当我在设备上启用整个操作系统的暗模式时,我的应用会显示暗色加载对话框等。甚至<input>元素的自动完成选项也变成了暗色。但是,使用webview加载的网页仍然不会变暗。根据这个页面所说,webview应该支持这个功能,但我就是无法让它起作用。我错过了什么吗?

我还发现在API 29中有这个WebSettings.setForceDark()方法;这可能是我正在寻找的东西吗?虽然我希望能找到适用于较低API级别的解决方案。

顺便说一下,我的当前解决方法是注入JavaScript接口,像这样:

webView.addJavascriptInterface(new JSInterface(), "jsInterface");

...

public class JSInterface {
    @JavascriptInterface
    public boolean isNightMode() {
        int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
        return nightModeFlags == Configuration.UI_MODE_NIGHT_YES;
    }
}

然后在我的网页中调用jsInterface.isNightMode()方法,并根据结果动态加载不同的CSS文件。它肯定会按照预期响应全局暗模式设置,但我仍然想知道是否可以让prefers-color-scheme起作用。


我的深色模式样式没有被应用。我正在使用Cordova,并在一个带有inappbrowser插件的ASP.NET MVC应用程序中运行。如何禁用cordova应用程序中的深色模式,包括in app浏览器,或使我的MVC应用程序中的prefers-color-scheme: dark设置起作用? - Inês Borges
我在我的 Cordova 应用程序中禁用了暗模式,包括 MVC 中的 inappbrowser webview 应用程序,在所有网页布局的 <head> 标签中添加了 <meta name="color-scheme" content="light dark"> - Inês Borges
6个回答

61

更新 2022:AndroidX Webkit 1.5.0修复了webview处理暗黑模式的许多怪异问题。

为使其正常工作,您需要:

  • 向项目添加最新版本的webkit(至少为1.5.0):
    • implementation 'androidx.webkit:webkit:1.5.0'
  • 将应用程序的目标版本设置为33或更高版本
    • targetSdkVersion 33
  • 确保设备上安装了Android系统Webview v100或更高版本

通过此设置,webview将根据应用程序主题进行适当调整,无需进一步调整任何设置。 prefers-color-scheme CSS查询值将自动根据当前活动/片段运行的主题类型(浅色/深色)调整为浅色或深色。(基于主题或其父主题的isLightTheme属性)

如果您的应用程序targetSdkVersion设置为33或更高版本,则调整暗黑模式的pre-API 33方法(例如WebViewSettingsCompat.setForceDarkWebViewSettingsCompat.setForceDarkStrategy等)将不起作用,并且可以从代码中删除。

注意:此新方法要求您的应用程序目标为API 33或更高版本,但向后兼容至少到API 29(支持暗黑模式的第一个Android版本)

注意2:在大多数情况下,用户代理变暗(不支持自动暗黑模式的网页的自动暗黑模式)现已默认禁用,但可以使用以下方法启用:

if(WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
    WebSettingsCompat.setAlgorithmicDarkeningAllowed(myWebView.getSettings(), true);
}

这种方法的完整行为描述可以在这里找到。




传统方法(面向API 32或以下的应用程序)

Android Webview在面向API 32及以下的应用程序中处理日/夜模式与其他视图有所不同。 将主题设置为暗色将更改WebView组件(滚动条、缩放按钮等)为深色模式版本,但不会更改其加载的内容。

要更改内容,您需要使用webview设置的setForceDark方法使其也更改其内容。此方法的兼容版本可在AndroidX Webkit包中找到。

将以下依赖项添加到gradle构建中:

implementation 'androidx.webkit:webkit:1.3.0'

(1.3.0是此软件包所需的最低版本。但更高的版本也应该可以使用。)

并将以下代码添加到您的Webview初始化中:

if(WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
    WebSettingsCompat.setForceDark(myWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
}

检查 isFeatureSupported 的存在是为了确保用户设备上安装的 Android System WebView 版本支持暗模式(因为这可以通过 Google Play 独立更新或降级,与 Android 版本无关)。

注意:要在运行设备上安装 Android System WebView v76 或更高版本才能使用 setForceDark 功能。

WebView 内容的强制暗模式功能有两种所谓的策略:

  • 用户代理暗化:WebView 将通过自动反转或变暗其内容的颜色将其内容设置为暗模式。

  • 基于主题的暗化:WebView 将根据内容的主题(包括 @media (prefers-color-scheme: dark) 查询)切换到暗模式。

要设置 WebView 应该使用哪种策略来应用强制暗模式,您可以使用以下代码:

if(WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) {
    WebSettingsCompat.setForceDarkStrategy(myWebView.getSettings(), WebSettingsCompat.DARK_STRATEGY_WEB_THEME_DARKENING_ONLY);
}

注意: 策略选择需要在运行设备上安装 Android System WebView v83 或更高版本。支持 setForceDark 但不支持策略选择的 WebView 版本(从 v76 到 v81)将使用用户代理变暗。

支持的策略选项包括:

  • DARK_STRATEGY_USER_AGENT_DARKENING_ONLY:仅使用用户代理变暗并忽略内容中的任何主题(默认)
  • DARK_STRATEGY_WEB_THEME_DARKENING_ONLY:仅使用内容自身的深色主题来设置页面为暗模式。如果内容没有深色主题,则 WebView 不会应用任何变暗并显示它“原样”。
  • DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING:使用内容自身的深色主题来设置页面为暗模式。如果内容没有深色主题,则使用用户代理变暗。

暗网页的 Javascript 检查是如何工作的?

JavaScript 调用 window.matchMedia('(prefers-color-scheme: dark)') 将在用户代理变暗和网页主题变暗策略中匹配。

我已将我的 webview 设置为 FORCE_DARK_AUTO,我的应用正在运行 DayNight 主题,但不知何故我的 webview 不会根据我的应用主题自动应用暗模式。为什么会发生这种情况?

这是因为 WebView 的 FORCE_DARK_AUTO 设置值不基于主题工作(如 文档 中所述)。它检查 Android 10 强制暗模式 功能(应用的“快速修复”暗模式功能。名称类似,但与 WebView 强制暗模式没有直接关系)。

如果您没有使用强制暗模式而是使用应用程序主题来处理暗模式(建议使用),则必须实现自己的检查以确定何时应用 WebView 的强制暗模式功能。以下是使用 DayNight 主题的示例:

int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
   //Code to enable force dark using FORCE_DARK_ON and select force dark strategy 
}

6
您在这个领域的完整知识和专业能力令人难以置信。尽管您的回答涉及到一个alpha版本的软件包,但它确实按照要求正常工作。 - Mu-Tsun Tsai
1
@Mu-TsunTsai 这是因为一些功能(如策略选择)只在 androidx webkit 包的 1.3.0 版本中才可用,而该版本(连同提到的 Android System Webview 的 83 版本)目前仍处于 alpha/beta 阶段。我已经在答案中添加了相关注释,并在这些包稳定后相应地调整答案。 - Leon Lucardie
1
至于版本检查:如果我在v81 webView上执行isFeatureSupported,则对于setForceDark返回true,对于setForceDarkStrategy返回false。如果我在v83上执行相同的操作,则两次都返回true。这不应该足够了吗,而不是检查Webview组件的特定版本? - derpirscher
@derpirscher 关于版本检查:当我写这个答案时,我仍然遇到了一些问题,特别是在forceDarkStrategyisFeatureSupported检查方面(尤其是在从未发布正式版的v82上)。现在v83已经稳定,isFeatureSupported检查似乎工作正常,我将相应地调整答案。 - Leon Lucardie
我明白了,我想我对于应用程序的“强制黑暗模式”是什么意思感到困惑,我认为它与DayNight和 AppCompatDelegate.setDefaultNightMode有关。我需要进行一些研究,看看这意味着什么,以便找到一种自动为我的深色模式用户设置此功能的方法。 - casolorz
显示剩余14条评论

9
根据你提供的代码,我所做的是基于当前状态进行强制执行。
int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
  webSettings.setForceDark(WebSettings.FORCE_DARK_ON);
}

这样@media (prefers-color-scheme: dark)就能生效,但是@media(prefers-color-scheme: light)仍无作用(在else中尝试使用FORCE_DARK_OFFFORCE_DARK_AUTO)。

2
我检查了Webview并使用window.matchMedia("(prefers-color-scheme: ...)")检查了媒体查询匹配(可能的值为lightdarkno-preference)。当模式最初设置为DARK_ON时,它与dark方案匹配。任何其他模式都与no-preference方案匹配。此外,一旦我将模式设置为DARK_OFFDARK_AUTO,稍后将其设置为DARK_ON无法正确传播到Webview。但是反过来(即首先是DARK_ON,然后是DARK_OFF)可以正常工作。 - derpirscher
2
更加精确地说:getForceDark() 返回的值始终是预期的值(即在上一个设置操作是 DARK_ON 时为 2,而上一个设置操作是 DARK_OFF 时为 0)。但是,在 DARK_OFFDARK_AUTO 被设置后,再设置 DARK_ON 不再有任何可见效果。 - derpirscher

3

这里有两个相关的文档:

https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#force_dark

https://developer.android.com/reference/android/view/View.html#setForceDarkAllowed(boolean)

关键点如下:

  1. WebView必须允许强制暗黑,以及它的所有父元素也必须允许。
  2. 主题必须标记为浅色。

这意味着在WebView中实现FORCE_DARK_AUTO行为有点复杂。将以下内容添加到AppCompat.DayNight主题确实有效,但可能不是最佳解决方案,因为它可能会将Android强制暗黑应用于除WebView之外的视图。

<item name="android:forceDarkAllowed">true</item>
<item name="android:isLightTheme">true</item>

另一种选择是通过根据相关配置更改来手动处理,从而设置FORCE_DARK_ON/OFF。

目前WebView不支持prefers-color-scheme,部分原因是在prefers-color-scheme标准化之前实现了force dark,而且没有办法将其合理地(无闪烁)与force dark集成。打算通过androidx.webkit提供能够选择force dark或prefers-color-scheme的功能。


不错。它也适用于日夜主题。此答案应标记为已解决! - dphans

2
implementation 'androidx.webkit:webkit:1.4.0'


WebSettings settings = webView.getSettings();

if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
   if (isNightMode) {
      WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_ON);
   } else {
      WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_OFF);
   }
}

1

我认为WebView目前还没有支持prefers-color-scheme CSS媒体查询的功能。

setForceDark的新API有三种状态:开启、关闭或自动。

开启-您的内容将始终以较暗的色调呈现。

关闭-您的内容将始终以较亮的色调呈现。

自动-如果您的应用程序主题较暗或设备操作系统处于深色模式,因为用户切换了操作系统级别开关或打开了省电模式,则内容将以较暗的色调呈现。

我相信,通过AndroidX很快就会支持旧版本的Android和控制是否使用prefers-color-scheme而不是WebView的强制暗黑模式。截至目前,到期日未知。

目前,我建议将WebView设置为setForceDark Auto。这将适用于Android Q及以上版本。

我会密切关注AndroidX发布说明,以获取您在Android P及以下设备上所需的其他支持。


不幸的是,即使在针对运行于Android Q上的目标进行编译时,AUTO似乎总是解析为prefers-color-scheme:no-preference。因此,即使原生应用程序可以响应系统切换到深色模式,Webview也无法响应。 - derpirscher
嗨,derpirscher。我不确定我的理解是否正确。如果您将webview设置为“自动”,它应该遵循Android操作系统级别的深色主题设置。因此,如果您在Android设备上切换到深色主题,则webView应该强制Web内容变为深色。这不是这种情况吗? - cmd
1
这对我来说并不是这样。我将应用程序的主题设置为AppCompat.DayNight,我可以看到应用程序遵循暗模式的操作系统设置。但是,即使我设置了websettings.setForceDark(FORCE_DARK_AUTO),当我切换操作系统设置时,Webview也不会进入暗模式。请参阅我在此线程中对其他答案的评论。 - derpirscher
但也许我只是漏掉了什么。我在一个最小的示例中所做的事情是:1)AppTheme = AppCompat.DayNight,2)我有values/colors.xmlvalues-night/colors.xml。3)在onCreate中,我设置了AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_FOLLOW_SYSTEM),4)我设置了websettings.setForceDark(FORCE_DARK_AUTO)。我可以看到应用程序遵循深色/浅色模式切换(状态栏更改颜色),但是Web视图没有。它始终显示像在@media (prefers-color-scheme: no-preference)下定义的颜色,无论是否设置了深色或浅色模式。 - derpirscher
1
@diAz 你也可以使用window.matchMedia在JavaScript中进行检查。例如,如果prefers-color-scheme设置为light,则window.matchMedia("(prefers-color-scheme: light)").matches将返回true - derpirscher
显示剩余3条评论

0

对于Webview v93及以下版本(implementation 'androidx.webkit:webkit:1.4.0'),标志DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING在我使用以下代码之前无法正常工作:

if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
            if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)){            
            WebSettingsCompat.setForceDarkStrategy(
                webView.settings,
                WebSettingsCompat.DARK_STRATEGY_WEB_THEME_DARKENING_ONLY
            )
        }
        WebSettingsCompat.setForceDark(webView.settings, WebSettingsCompat.FORCE_DARK_ON)
    }    
webView.evaluateJavascript("document.documentElement.setAttribute('data-color-mode', 'dark');", null)

该请求网站的暗色主题,但不强制颜色反转。


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