Webview优化 | 优雅的在手机上阅读代码
手机阅读代码之痛
在手机上阅读代码体验肯定不会在电脑上方便,尤其是一些冗长的代码,可能会有几行代码显得很长,又没有做换行处理。正常来说,一般人的阅读应该是从左到右读完一行,然后从上至下读下一行。虽然经过pre
code
html标签处理的代码可以左右滑动代码阅读,但是对于一些记性不太好的朋友通常在滑动读完第一行,继续左右滑动阅读第二三行时,可能就忘了第一行,又得滑动看看第一行。比如如下代码:
这是一段很长的代码~~~~~!!!~~~代码中间~~~~~~~~~~~~~~~~~啦啦啦第一行结尾
System.out.println("Hello World..............+++++++............................end")
第二行代码--------------------------------中间~~~~~~~第二行结尾1=1
//~~~~~~~~~~~~~
总的来说,代码读起来不流程,完整性不好。因为手机屏幕的原因,导致我们必须得滑动读。当然我们也可以进行通过换行处理代码提升体验,可我们不能保证所有的文章都做了这么处理,而且有些代码做换行处理也不会很理想。
优雅阅读代码
那么应该怎样提升阅读代码的体验呢?在微信阅读app中,它将代码裁剪成几个片段,点击时又可以显示完整对代码图片。事实上,在现在这种大屏手机时代,一张完整代码图片是可以阅读的,读起来更加完整,流畅。
为了在WebView
上能更好阅读代码,做了代码图片展示
功能,能让我们更优雅的在手机上阅读代码(见下图)。
代码1 | 代码2 |
---|
较微信阅读,新增横屏模式,更清楚的阅读代码。
如何实现
首先想到的是要对应pre
或code
标签下的dom节点转成图片。在github上,最终找到了dom-to-image,它就是一款能够将任意的dom节点转成图片的js库。
webview加载dom-to-image
直接将dom-to-image.js源码保存至assets
目录下。在WebView
加载完成网页时(onPageFinished
)时,载入dom-to-image
的代码。
object FileUtil {
...
fun readStringInAssets(path: String): String {
val input = App.instance.assets.open(path)
val len = input.available();
val buffer = ByteArray(len)
input.read(buffer)
return String(buffer, Charset.forName("utf-8"))
}
}
//在webview onPageFinished调用
val dom2ImageScript = FileUtil.readStringInAssets("dom-to-image.js")
webView.evaluateJavascript(dom2ImageScript) {
"result:$it".logE()
}
然后我们就能找到domtoimage
这个对象了
我们通过
chrome://inspect/
地址,在Console
下编写调试代码。我们加入下面的代码:
var pre = document.getElementsByTagName("pre");
domtoimage.toPng(pre)
.then(function(data){
console.log(data);
});
image.png
我们将上面的
base64
形式的图片数据拷贝复制到浏览器上,得到了以下图片image.png
可以发现图片并不是完整的。我们需要将宽度增大到实际可滚动的宽度。我们在
toPng
方法中加入width
属性,不同站点对于代码的处理方式是不一样的,有点是在pre
内滚动,有的则是在code
内部滚动。玩Android站点是在code
内部滚动的,所以基于code保存图片
var code = pre.children[0];
domtoimage.toPng(code,{width:code.scrollWidth})
.then(function(data){
console.log(data);
});
最终图片如下:
image.png
图片模糊?
最终生成的代码图片我们发现代码有些模糊的,图片大小像素并不匹配手机的分辨率。因为WebView
中的px
并不匹配手机实际的像素。所以我们要针对手机实际的像素大小进行放大。在css中我们可以通过transform:scale(1.5)
之类的代码将dom节点缩放,同时dom-to-image
是支持自定义style
的。因此可以实现代码图片真正像素显示。
在次之前,要先得到放大比例,然后给每个pre
添加点击事件:
val ww = webView.width
val script = """
var ww = ${ww}.0;
var scale = ww/outerWidth;
//以下为JavaScript代码见下一个
//pre点击事件代码
""".trimIndent()
webView.evaluateJavascript(script){
"result:$it".logE();
}
下面是对pre
要添加点击事件的代码,不同站点对于代码展示处理做了兼容。
//pre点击事件代码
var pres = document.getElementsByTagName("pre");
for(var i=0;i<pres.length;i++){
pres[i].onclick = function(e){
var imgWidth = this.scrollWidth;
var node = this;
for(var n=0;n<this.childElementCount;n++){
var child = this.children[n];
if(child.tagName=="CODE"){
var cw = child.scrollWidth;
if(cw>imgWidth){
imgWidth = cw;
node = child;
}
}
}
imgWidth = imgWidth+3;
var imgHeight = node.offsetHeight;
var rect = node.getBoundingClientRect();
console.log(node.tagName);
domtoimage.toPng(node,{width:imgWidth*scale,height:imageHeight*scale,
style: {
transform: "scale(" + scale + ")",
transformOrigin: "top left",
width: imgWidth + "px",
height: rect.height + "px"
}
})
.then(function(data){
console.log(data);
android.showImage(data,rect.x,rect.y,imgWidth,rect.height,outerWidth);
});
};
}
最终通过android.showImage
回调给Android端显示图片,具体图片展示可以见前一篇Glide实现WebView离线图片的酷炫展示效果。因为Glide
是支持Base64
字符串图片展示的,因此能之前写的图片展示也不用怎么改。不过要注意的是Intent
传递数据是有大小限制的,而我们的Base64
字符串可能会很大,需要避免用Intent
传递数据,一种方法是用共享变量传递。
val activity = iv.getActivity()
activity?.let {
val pair: Pair<View, String> = Pair(iv, "image")
val option =
ActivityOptionsCompat.makeSceneTransitionAnimation(
it,
pair
)
val intent = Intent(it, ImageShowActivity::class.java)
Constants.sharedUrl = url
it.startActivityForResult(intent, 1, option.toBundle())
}