webview夜间模式适配小结

做webview的夜间模式,来来回回折腾了好几天,基本上都是copy网上的各种css代码。

1. 一般实现webview夜间模式,要么就是内核自己实现了这个功能,一个api就能搞定(像uc内核:目前没有实现真正的开放;腾讯x5内核:开放,不知道能不能自定义夜间模式的颜色值之类的);

2. 要么就是自定义一个html模板,从后台获取json数据,把数据解析出来,把各个节点的数据通过js写入到这个模板中,相当于自己在本地组装了一个新的html,丢到webview里面,这样web加载起来也比较快,因为都是一些本地的数据;

3. 再者就是像我这样很无奈的,需要通过js代码向原来的web中注入css代码来改变网页的风格了,说白了就是从后台拿到一个url网页地址,把这个url丢给webview,然后在webview加载完成后注入css代码来改变网页的风格。

用3这种方式最主要的问题就是这个css代码都有些啥,世界上的网页多种多样,要尽可能的适配各种标签,但是也未必能覆盖得全面。对于一个从来没有接触过html,js, css的我来说,着实着急了一把。但是最终还是勉勉强强的实现了这个功能。在这里记录一下新路历程。

首先,去网上搜罗了一堆css代码:

Android webview设置字体大小,适配屏幕,夜间模式  这篇文章,对主要的标签节点都配置了背景色,这里贴出具体的css代码,注入方法且看原文:

html,body,header,div,a,span,table,tr,td,th,tbody,p,form,input,ul,ol,li,dl,dt,dd,section,footer,nav,h1,h2,h3,h4,h5,h6,em,pre {
	background: #333 !important;
	color: #616161!important;
	border-color: #454530!important;
	text-shadow: 0!important;
	-webkit-text-fill-color: none!important;
}

html a,html a * {
	color: #5a8498!important;
	text-decoration: underline!important;
}

html a:visited,html a:visited *,html a:active,html a:active * {
	color: #505f64!important;
}

html a:hover,html a:hover * {
	color: #cef!important;
}

html input,html select,html button,html textarea {
	background: #4d4c40!important;
	border: 1px solid #5c5a46!important;
	border-top-color: #494533!important;
	border-bottom-color: #494533!important;
}

html input[type=button],html input[type=submit],html input[type=reset],html input[type=image],html button {
	border-top-color: #494533!important;
	border-bottom-color: #494533!important;
}

html input:focus,html select:focus,html option:focus,html button:focus,html textarea:focus {
	background: #5c5b3e!important;
	color: #fff!important;
	border-color: #494100 #635d00 #474531!important;
	outline: 1px solid #041d29!important;
}

html input[type=button]:focus,html input[type=submit]:focus,html input[type=reset]:focus,html input[type=image]:focus,html button:focus {
	border-color: #494533 #635d00 #474100!important;
}

html input[type=radio] {
	background: none!important;
	border-color: #333!important;
	border-width: 0!important;
}

html img[src],html input[type=image] {
	opacity: .5;
}

html img[src]:hover,html input[type=image]:hover {
	opacity: 1;
}

html,html body {
	scrollbar-base-color: #4d4c40 !important;
	scrollbar-face-color: #5a5b3c !important;
	scrollbar-shadow-color: #5a5b3c !important;
	scrollbar-highlight-color: #5c5b3c !important;
	scrollbar-dlight-color: #5c5b3c !important;
	scrollbar-darkshadow-color: #474531 !important;
	scrollbar-track-color: #4d4c40 !important;
	scrollbar-arrow-color: #000 !important;
	scrollbar-3dlight-color: #6a6957 !important;
}

dt a {
	background-color: #333 !important;
}

原文里的代码直接是字符串的,这里使用了
在线格式化代码格式了一下,这样看起来比较清晰,我自己实现的是新建了一个css文件,把css代码写入这个文件中,然后从文件里面解析出这个css代码(这个后面会给出)。

以上代码主要是设置了各个标签的backgroud和color, 我自己的理解是backgroud是背景色,color是作用到文本上的。但是在我实际使用的过程中,backgroud会把这个标签下的图片隐藏掉,如果我设置了div{backgroud:#颜色} 这个的话,这个article-top-image这个div下的图片就不显示了,猜想可能是它的style=“backgroud”原来应该加载一个图片出来,结果我给设置了颜色值,所以就显示不出来了。解决的办法就是把backgroud换成backgroud-color,也就是只作用backgroud-color。

另外这篇文章也是大同小异:android webView使用js/css实现夜间模式 长按识别图片以及二维码,网页可以上传图片

html,body,applet,object,h1,h2,h3,h4,h5,h6,blockquote,pre,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,p,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,th,td {
	background: rgba(0,0,0,0) !important;
	color: #fff !important;
	border-color: #A0A0A0 !important;
}

div,input,button,textarea,select,option,optgroup {
	background-color: #000 !important;
	color: #fff !important;
	border-color: #A0A0A0 !important;
}

a,a * {
	color: #ffffff !important;
	text-decoration: none !important;
	font-weight: bold !important;
	background-color: rgba(0,0,0,0) !important;
}

a:active,a:hover,a:active *,a:hover * {
	color: #1F72D0 !important;
	background-color: rgba(0,0,0,0) !important;
}

p,span {
	font color: #FF0000 !important;
	color: #ffffff !important;
	background-color: rgba(0,0,0,0) !important;
}

html {
	-webkit-filter: contrast(50%);
}

看这里的大多数节点就是使用了backgroud-color属性,并且使用rgba颜色值。对,就是这个rgba中的a解决了我的另一个问题。a代表了透明度。在我的是一个适配的页面中

https://www.thestartmagazine.com/article/f0195498-9a5e-4131-a5e1-67136f192bed?ref=TWVpWnUtU0RLJSQlSHlYNFBrZmVIRmRtMUNoN1lGNEw3MTBod004ODU5N0olJCUxMzU3OTg2NDI%3D&recommendationId=TIME_BL&theme=template6&userId=d7451302-3494-4e9c-945c-66d0de7adea4

在网页最后有一个资讯列表:

最开始了我设置:

div {
	background-color: #201f26 !important;
}

结果右边的图片就没有了:(为了方便看,我这里设置了另一个颜色:
#e4d5d5)

其实看其源码,就是一个简单的

节点:

目前尚不清楚为何会出现这个现象。后来无意中把颜色值调整成rgba模式,结果就能显示图片了:

backgroud-color:rgba(228, 213, 213, 0.27)


这里顺便提一下,我们在开发手机网页的时候,可以在chrome中模拟,载入网页的时候,按F12会进入审查元素,右边可以看到html整个网页的源码信息,选择toggle device toolbar可以选择需要的设备信息,然后刷新一下页面就能载入和手机上显示的一致的网页了。


一般会在源码中添加一些css元素来改变网页的效果,即改即显,还是比较方便的。

在调试的过程中,还实验了另外一种方式:

利用invert属性翻转网页颜色

html {
    background-color : #2e2e2e !important;
    zoom             : 1.055;
  }
body {
    text-rendering             : optimizeSpeed;
    image-rendering            : optimizeSpeed;
    -webkit-image-rendering    : optimizeSpeed;

    text-shadow            : 0 0 0 #000;
    -webkit-font-smoothing : antialiased;

    -webkit-filter         : invert(1)hue-rotate(180deg);
    -ms-filter             : invert(1)hue-rotate(180deg);
    filter                 : invert(1)hue-rotate(180deg);
}

input,textarea,select{color:purple}

img, video, iframe, canvas, svg,
embed[type='application/x-shockwave-flash'],
object[type='application/x-shockwave-flash'],
*[style*='url('] {
    -webkit-filter : invert(1)hue-rotate(180deg) !important;
    -ms-filter     : invert(1)hue-rotate(180deg) !important;
    filter         : invert(1)hue-rotate(180deg) !important;
}

也就是利用css的filter滤镜效果来实现,filter滤镜有各种效果可实现,具体可参考:

CSS3 filter滤镜

上面的代码主要是利用invert反色和hue-rotate色调来实现,但是实际效果不是很理想。也就是作用于整个页面,把白的地方变成黑的,其他颜色值都进行了一定的反转,像上面那种操作,只是反转了颜色,并不能使用自己想要的颜色,也就是说反转后的颜色值依赖于原网页的颜色值,这并不是想要的结果。

当时就在想,filter就不能指定我需要的颜色吗?

其实在色彩界,有三种表示颜色的方式:

RGB、HEX、HSL

我开始使用的是HEX,就是这种形式:#232323,三种颜色之间可以自由转换:

转换器

所以我就想把之前的hex模式的颜色转换成hsl的,正好,filter的滤镜可以来表示这个色值:

filter的hue-rotate就代表了H色相,saturate代表了S饱和度,brightness代表了L亮度
所以通过转换之后得到了:


然后打算这么使用:

-webkit-filter : hue-rotate(149deg)saturate(10.1%)brightness(13.5%);

实际效果简直惨不忍睹,不经颜色值不对,图片失真,文字失真。

回想一下,不失真才怪呢,filter是把整个页面做了一次滤镜效果,肯定就是在原来的图像上覆盖一层滤镜,毕竟我只是想简单的改变一下背景,在改变一下文字效果而已。遂放弃这种方式。

最后,终极方式来了:

/*所有元素的背景色都设置为黑夜模式,原来使用#201f26(rgb)会影响celltick资讯详情页里列表项的图片显示,这里改用rgba,有0.8的透明度*/
*, *:before, *:after {
    box-sizing: inherit;
    background-color: rgba(32, 31, 38, 0.8) !important;
}

/*背景颜色和一般字体颜色*/
div,h1,h2,h3,h4,h5,h6,p,body,em,html,link,textarea,form,select,input,span,button,em,menu,aside,table,tr,td,nav,dl,dt,dd,amp-iframe,main{
    /*background-color: rgba(32, 31, 38, 0.8) !important;*/
    color: #888888!important;
    border-color: #555555 !important;
    text-shadow            : 0 0 0 #000; /*去掉文本的阴影效果*/
}

/*超链接*/
a{
    color:#3c5180 !important;
}

strong {
	display: block;
}

img, video, iframe, canvas, svg,amp-social-share,
embed[type='application/x-shockwave-flash'],
object[type='application/x-shockwave-flash'],
*[style*='url('] {
    -webkit-filter : opacity(50%);
    -ms-filter     : opacity(50%);
    filter         : opacity(50%);
}

实现方式还是修改backgroud-color属性,使用rgba模式设置。

对一些特殊的标签,比如img,视频,动画,广告之类的进行了透明度的改变。整体效果下来非常不错了。
目前的话,还有一些个别的标签没有适配到,后期再慢慢优化了。

网上都说在webview的onPageFinished回调的时候再去执行css的注入,但是在实际使用过程中,某些个网页并不会回调这个方法,并且在加载前会显示原来网页的颜色。所以为了能一开始加载网页 的时候就显示夜间模式,我在几个地方都调用了注入方法:

webviewClient的onPageStarted,onPageFinished,

webviewChromeClient的onProgressChanged

这几个回调的时候都去执行注入方法:

webView.loadUrl("javascript:(function() {" + "var parent = document.getElementsByTagName('head').item(0);" + "var style = document.createElement('style');" + "style.type = 'text/css';" + "style.innerHTML = window.atob('" + code + "');" + "parent.appendChild(style)" + "})();");

其中code是个String类型,是css文件转换后的字符串。

InputStream is = AppContextUtils.getAppContext().getResources().openRawResource(R.raw.night);
                    byte[] buffer = new byte[0];
                    try {
                        buffer = new byte[is.available()];
                        is.read(buffer);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            is.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    nightCode = Base64.encodeToString(buffer, Base64.NO_WRAP);

R.raw.night是存放在工程raw文件加下的css文件,里面的内容就是终极版本的css代码。

这里存放到文件中是方便以后修改,然后在应用初始化的时候去加载这个css文件,转换成String类型的nightCode,在需要使用夜间模式的时候,就把这个code注入到webview中了。

夜间模式适配,基本上告一段落了。

总结了几点:

1. 需要熟悉js html css,这样才能排坑

2. 使用chrome先调好,再在代码中实验,一般chrome调好的样式在代码中有同样的效果。