做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)
其实看其源码,就是一个简单的<div>节点:
目前尚不清楚为何会出现这个现象。后来无意中把颜色值调整成rgba模式,结果就能显示图片了:
backgroud-color:rgba(228, 213, 213, 0.27)
这里顺便提一下,我们在开发手机网页的时候,可以在chrome中模拟,载入网页的时候,按F12会进入审查元素,右边可以看到html整个网页的源码信息,选择toggle device toolbar可以选择需要的设备信息,然后刷新一下页面就能载入和手机上显示的一致的网页了。
一般会在源码中添加一些css元素来改变网页的效果,即改即显,还是比较方便的。
在调试的过程中,还实验了另外一种方式:
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滤镜有各种效果可实现,具体可参考:
上面的代码主要是利用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调好的样式在代码中有同样的效果。