Webview优化 | 优雅的在手机上阅读代码

3,867 阅读3分钟

手机阅读代码之痛

在手机上阅读代码体验肯定不会在电脑上方便,尤其是一些冗长的代码,可能会有几行代码显得很长,又没有做换行处理。正常来说,一般人的阅读应该是从左到右读完一行,然后从上至下读下一行。虽然经过pre codehtml标签处理的代码可以左右滑动代码阅读,但是对于一些记性不太好的朋友通常在滑动读完第一行,继续左右滑动阅读第二三行时,可能就忘了第一行,又得滑动看看第一行。比如在手机上阅读如下代码:

这是一段很长的代码~~~~~!!!~~~代码中间~~~~~~~呵呵000000~~~~~~~~~~啦啦啦第一行结尾
System.out.println("Hello World..............+++++++............................end")
第二行代码--------------------------------中间~~~~~~~第二行结尾1=1
//~~~~~~~~~~~~~

总的来说,代码读起来不流程,完整性不好。因为手机屏幕的原因,导致我们必须得滑动读。当然我们也可以进行通过换行处理代码提升体验,可我们不能保证所有的文章都做了这么处理,而且有些代码做换行处理也不会很理想。

优雅阅读代码

那么应该怎样提升阅读代码的体验呢?在微信阅读app中,它将代码裁剪成几个片段,点击时又可以显示完整对代码图片。事实上,在现在这种大屏手机时代,一张完整代码图片是可以阅读的,读起来更加完整,流畅。 为了在WebView上能更好阅读代码,做了代码图片展示功能,能让我们更优雅的在手机上阅读代码(见下图)。

代码1
代码2

较微信阅读,新增横屏模式,更清楚的阅读代码。

如何实现

首先想到的是要对应precode标签下的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这个对象了

image.png
我们通过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())
            }

项目地址

github.com/iamyours/Wa…
最新apk地址
版本历史