preface

Since I handed over my apartment, I spent most of my time on the subway and bus except for going to work every day. However, with these time, you can take a good look at the article to lay a foundation for a new environment. Playing Android is a collection of articles worth reading, and good articles need to be read a few times to reap the benefits. However, there are some things that affect reading experience on mobile phones, such as advertising, such as clicking to unfold to expand the article. Both have been optimized in the Wandroid client, and the article content has been changed to a dark color mode. Overall, the reading experience is much improved. When you get to certain sections in the subway, the network signal is poor, and the web page often doesn’t load. So offline reading became very important to me. Therefore, during the Dragon Boat Festival, I added the offline reading function, and added the picture display function in order to better view the pictures in the article. Specific results can be seen as follows:

WebView web page save

In order to achieve offline reading of articles, you need to save the entire web page. Our main focus is on HTML content and related image Gif resources. The Chromium-based WebView itself also caches the resources of the page (CSS/JS/images, etc.) when the page loads. In order to facilitate picture control display, we choose to cache pictures and Gif resources in WebView by Glide.

Save text

Through the document. The documentElement. OuterHTML can get HTML pages can be used in the webview through addJavascriptInterface method was introduced into to js layer invokes the Java objects such as android. So we can save web content by:

 private fun downloadHtml(a) {
        val script = """ javascript:(function(){ var url = document.URL.toString(); var html = document.documentElement.outerHTML; android.saveHtml(url,html); }) (); "" ".trimIndent()
        webView.loadUrl(script)
    }
Copy the code

Pay special attention to do not write comments in the JS code, otherwise the load will fail. Loading JAVASCRIPT scripts in WebView is difficult to debug, so you can use the Console Console in Chrome ://inspect to debug the correct code.

The corresponding addJavascriptInterface object needs the following methods: We can save the HTML content to the SD card or /data/data/${application} directory

/ / @javascriptInterface fun saveHtml(url: String, HTML: String) {load.postvalue (true) 
        Constants.IO.execute {
            FileUtil.saveHtml(url, html)
            msg.postValue("Download successful")
            loading.postValue(false)}}Copy the code

Image cache

In order to conveniently control the pictures in webView and ensure the smoothness of the pictures in the display function of zooming and clicking, we put the picture resources into Glide cache. So the images in the WebView can be loaded using Glide, and when you click on the image display and load it using Glide, you can share the cache resources. We can redirect some resource requests by overriding the WebViewClient class’s shouldInterceptRequest. However, the urls of some image resources are not in strict. JPG/PNG/GIF format, so it is impossible to determine whether some urls are image resources. So you need to get the Content-Type via the head request. You also need to save the results (for offline use, OKHTTP does not support caching of HEAD requests).

private val typeDao = AppDataBase.get().urlTypeDao()
fun head(url: String?).: String {
	val md5 = MD5Utils.stringToMD5(url)
	val value = typeDao.getType(md5)
	if (value == null) {
	    val client = OkHttpClient.Builder()
	        .addNetworkInterceptor(CacheInterceptor())
	        .build()
	    val request = Request.Builder()
	        .url(url)
	        .head()
	        .build()
	    val res = client.newCall(request).execute()
	    val type = res.header("content-type")
	    valresult = type ? :""
	    typeDao.insert(UrlTypeVO(md5, result))
	    return result
	}
	return value
}
Copy the code

Therefore, the shouldInterceptRequest method can redirect requests for image types.

val head = Wget.head(url)
if (head.startsWith("image")) {
    val bytes = GlideUtil.syncLoad(url, head)
    if(bytes ! =null) {
        return WebResourceResponse(
            head,
            "utf-8",
            ByteArrayInputStream(bytes)
        )
    }
}
Copy the code

Here we need to Glide to synchronize the byte[] data of the image, and also distinguish the image from the GIF.

public class GlideUtil {
    public static byte[] syncLoad(String url, String type) {
        boolean isGif = type.endsWith("gif");
        if (isGif) {
            try {
                FutureTarget<byte[]> target = Glide.with(App.instance)
                        .as(byte[].class)
                        .load(url)
                        .decode(GifDrawable.class).submit();
                return target.get();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
        FutureTarget<Bitmap> target = Glide.with(App.instance)
                .asBitmap().load(url).submit();
        try {
            Bitmap bitmap = target.get();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null; }}Copy the code

Glide is used to load images in the WebView, and images are cached in memory and on disk.

Pictures show

For the top rendering, you need to add the click event for the image and know the position and size of the image on the screen (for the transition effect).

Add click event to get image location

After the WebView loads the page, we add an onclick event to each IMG to retrieve the image address, size, and location. Some sites (such as wechat) are lazy to load images, and the images cannot be loaded in offline mode due to cross-domain problems. So you need to retrieve the URL from the dataset and reset it. There are also some sites (CSDN) have click display effect, need stopPropagation to prevent event bubble shielding.

var imgs = document.getElementsByTagName("img");
for(var i=0; i<imgs.length; i++){var dataset = imgs[i].dataset;
    if(dataset && dataset.src && dataset.src! =imgs[i].src){ imgs[i].src = dataset.src; } imgs[i].onclick =function(e){
        var target = e.target;
        var rect = target.getBoundingClientRect();
        android.showImage(target.src,rect.x,rect.y,rect.width,rect.height,outerWidth);
        e.stopPropagation();
    };
}
Copy the code

OuterWidth: The width of the phone’s screen is not the same as the width of the phone’s screen, but the width we get from getBoundingClientRect is not the same unit. OuterWidth is used to calculate the actual size of the ImageView on Android.

Image sharing element transition effect

After the page loads, we manually inject the JS code that sets the image click event. When you click on an image, you can get the IMAGE URL, size and location information. On Android, transitions can be implemented using shared elements. Again, we need to add the ImageView to the layout file where the WebView is located

 <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <io.github.iamyours.wandroid.widget.WanWebView
            android:id="@+id/webView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:overScrollMode="never" />
    <io.github.iamyours.wandroid.widget.TouchImageView
            android:id="@+id/showImage"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:visibility="invisible"
            app:showImage="@{vm.image}" />
</FrameLayout>
Copy the code

Shared element transitions are implemented by binding custom attributes in DataBinding.

@BindingAdapter(value = ["showImage"])
fun bindImage(iv: ImageView, showImage: PositionImage?).{ showImage? .run {val lp = iv.layoutParams as ViewGroup.MarginLayoutParams
        val parentWidth = iv.context.resources.displayMetrics.widthPixels
        val scale = parentWidth / clientWidth
        lp.width = (width * scale).toInt()
        lp.height = (height * scale).toInt()
        lp.leftMargin = (x * scale).toInt()
        lp.topMargin = (y * scale).toInt()
        iv.layoutParams = lp
        iv.requestLayout()
        iv.displayWithUrl(url, lp.width, lp.height) {
            iv.postDelayed({
                valactivity = 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)
                    intent.putExtra("url", url)
                    it.startActivityForResult(intent, 1, option.toBundle())
                }
            }, 200)}}}Copy the code

The project address

Github.com/iamyours/Wa…

  • The diablo series
  • The only adaptation of the whole network nuggets/simple book /CSDN/ public number/play Android article night mode
  • No ads, no click to expand
  • Image display, zoom support, shared elements seamless transition
  • Offline reading is supported, and reading on the subway is more convenient

Download at v1.1.0

Follow-up function

  • Code picture display (under development, now support digging gold, simple book)
  • Article classification collection