【Android】细说网页截图

在开发浏览器时,需要实现屏幕截图功能。具体需求就是对Android WebView进行截图。
由于浏览器的开发是基于原生Browser进行开发,而原生Browser已经实现了这个功能,所以可以直接使用。
原生Browser的仓库地址:https://android.googlesource.com/platform/packages/apps/Browser/

1. 对WebView进行截图

com/android/browser/Controller.java里,实现了对WebView的简单截图:

	static Bitmap createScreenshot(WebView view, int width, int height) {
        if (view == null || view.getContentHeight() == 0
                || view.getContentWidth() == 0) {
            return null;
        }
        // We render to a bitmap 2x the desired size so that we can then
        // re-scale it with filtering since canvas.scale doesn't filter
        // This helps reduce aliasing at the cost of being slightly blurry
        final int filter_scale = 2;
        int scaledWidth = width * filter_scale;
        int scaledHeight = height * filter_scale;
        if (sThumbnailBitmap == null || sThumbnailBitmap.getWidth() != scaledWidth
                || sThumbnailBitmap.getHeight() != scaledHeight) {
            if (sThumbnailBitmap != null) {
                sThumbnailBitmap.recycle();
                sThumbnailBitmap = null;
            }
            sThumbnailBitmap =
                    Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.RGB_565);
        }
        Canvas canvas = new Canvas(sThumbnailBitmap);
        int contentWidth = view.getContentWidth();
        float overviewScale = scaledWidth / (view.getScale() * contentWidth);
        if (view instanceof BrowserWebView) {
            int dy = -((BrowserWebView)view).getTitleHeight();
            canvas.translate(0, dy * overviewScale);
        }
        canvas.scale(overviewScale, overviewScale);
        if (view instanceof BrowserWebView) {
            ((BrowserWebView)view).drawContent(canvas);
        } else {
            view.draw(canvas);
        }
        Bitmap ret = Bitmap.createScaledBitmap(sThumbnailBitmap,
                width, height, true);
        canvas.setBitmap(null);
        return ret;
    }

这里它先创建了一张截图尺寸2倍于所需大小的位图Bitmap,将网页内容画在位图上,再将位图缩小至所需尺寸并返回,这么做的目的是为了减少锯齿。其实这一段代码最关键的部分只有这么一句:

	((BrowserWebView)view).drawContent(canvas);

这里BrowserWebView继承于WebView,而drawContent的实现也很简单:

	public void drawContent(Canvas c) {
        onDraw(c);
    }

onDrawView里的方法,WebView对这个方法进行了重写。onDraw方法的定义如下:

	/**
     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
    protected void onDraw(Canvas canvas) {
    }

原始的onDraw是个空方法,靠子类去实现该方法。该方法的作用在于,将此视图(及其所有子视图)渲染到给定的Canvas。至于WebView如何实现该方法,在此不做深究。
在上述createScreenshot方法中,如果view不是BrowserWebView的实例,则直接执行draw方法。draw方法同样是View的方法,翻查该方法的定义可知,View的子类应该去实现onDraw方法,而不是重写draw方法。也就是说,外部应该考虑调用onDraw方法,除非没有实现该方法才调用draw方法。
另外,上述createScreenshot方法,会从WebView的顶部开始截图,如果网页内容已经往上滑动,则截出来的图片上部会有空白,甚至整张图片都是空白。这是因为从Android 5.0开始,WebView只渲染网页的显示部分,如果需要全部渲染,需要提前调用WebView.enableSlowWholeDocumentDraw()方法。不过这并不是我们截图的需求,我们需要截的是当前显示的部分,所以需要先对画布进行平移:

	canvas.translate(0, -view.getScrollY() * overviewScale);

参考:WebView.enableSlowWholeDocumentDraw
另见:对Canvas, Bitmap, Drawable的简单理解

2. 将截图显示在界面上

通过上述方法获得一张bitmap之后,需要将其展示在浏览器应用的右上角。
由于截图对于浏览器来说并非高频功能,而且图片需要显示在浏览器主界面,因此用ViewStub延迟加载显示截图的布局。这里的ScreenshotThumbnail为自定义布局。

    <ViewStub
        android:id="@+id/screenshot_thumbnail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:layout_marginTop="28dp"
        android:layout_marginEnd="-2dp"
        android:layout="@layout/screenshot_thumbnail" />

screenshot_thumbnail.xml:
<?xml version="1.0" encoding="utf-8"?>
<com.android.browser.ScreenshotThumbnail xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/screenshot_bg"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/screenshot"
        android:layout_width="96dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginTop="4dp"
        android:layout_marginEnd="4dp"
        android:adjustViewBounds="true" />

	……
</com.android.browser.ScreenshotThumbnail>

截图完成后再加载该布局,并显示图片:

	private ScreenshotThumbnail mScreenshotThumbnail;
	
    public void showScreenshot(Screenshot screenshot) {
        if (mScreenshotThumbnail == null) {
            ViewStub stub = mActivity.findViewById(R.id.screenshot_thumbnail);
            mScreenshotThumbnail = (ScreenshotThumbnail) stub.inflate();
        }
        mScreenshotThumbnail.fill(screenshot);
        mScreenshotThumbnail.show();
    }

参考:视图加载延迟 | Android 开发者 | Android Developers

3. 截图展示布局消失

截图展示后,其显示布局在5秒钟之后需要自动向右滑出屏幕。为实现滑出逻辑,这里用到属性动画。

	private void dismiss(int duration) {
        float translationX = getResources().getDisplayMetrics().widthPixels - getLeft();
        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "translationX", translationX);
        animator.setDuration(duration);
        animator.start();
    }

由于这里只是将布局移出屏幕,而并非移除,所以再次展示时,调用setTranslationX(0);即可。
关于属性动画,另见对属性动画的简单理解

4. 保存截图

首先,保存截图属于I/O操作,需要在子线程内完成,比如放在线程池里运行。
(待续……)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值