在开发浏览器时,需要实现屏幕截图功能。具体需求就是对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);
}
onDraw
是View
里的方法,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操作,需要在子线程内完成,比如放在线程池里运行。
(待续……)