在某些特殊场合需要对View进行截图,使用View里面的getDrawingCache()方法,返回一个Bitmap对象,就可以实现截图的功能。
我们先看一个简单的示例,分别点击三个按钮进行截图,获取到的Bitmap放到下面一个ImageView上面显示,效果图如下:
1.原图
2.对LinearLayout里面的内容(ImageView+TextView)截图
3.对ImageView截图
4.对TextView截图
代码比较简单:
package com.li.testsnapshot;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements View.OnClickListener{
private LinearLayout llContainer;
private ImageView ivTest; //示例测试图片
private TextView tvHint; //测试文本
private Button btnSnap;
private Button btnImgSnap;
private Button btnTVSnap;
private ImageView ivShow;
private Button btnReset;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
llContainer = (LinearLayout) findViewById(R.id.llContainer);
ivTest = (ImageView) findViewById(R.id.ivTest);
tvHint = findViewById(R.id.tvHint);
btnSnap = (Button) findViewById(R.id.btnSnap);
btnImgSnap = (Button) findViewById(R.id.btnImgSnap);
btnTVSnap = (Button) findViewById(R.id.btnTVSnap);
ivShow = (ImageView) findViewById(R.id.ivShow);
btnReset = (Button) findViewById(R.id.btnReset);
btnSnap.setOnClickListener(this);
btnImgSnap.setOnClickListener(this);
btnTVSnap.setOnClickListener(this);
btnReset.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btnSnap:
// 针对Layout整体(ImageView + TextView)截图
testViewSnapshot(llContainer);
break;
case R.id.btnImgSnap:
// 针对ImageView截图
testViewSnapshot(ivTest);
break;
case R.id.btnTVSnap:
// 针对TextView截图
testViewSnapshot(tvHint);
break;
case R.id.btnReset:
// 清除
reset();
break;
default:
break;
}
}
/**
* 对View进行截图
*/
private void testViewSnapshot(View view) {
//使控件可以进行缓存
view.setDrawingCacheEnabled(true);
//获取缓存的 Bitmap
**Bitmap drawingCache = view.getDrawingCache();**
//复制获取的 Bitmap
drawingCache = Bitmap.createBitmap(drawingCache);
//关闭视图的缓存
view.setDrawingCacheEnabled(false);
if (drawingCache != null) {
ivShow.setImageBitmap(drawingCache);
Toast.makeText(this, "成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "失败", Toast.LENGTH_SHORT).show();
}
}
/**
* 设置默认显示图片
*/
private void reset() {
ivShow.setImageResource(R.mipmap.ic_launcher);
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.li.testsnapshot.MainActivity">
<LinearLayout
android:id="@+id/llContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<ImageView
android:id="@+id/ivTest"
android:layout_width="150dp"
android:layout_height="150dp"
android:src="@mipmap/money"/>
<TextView
android:id="@+id/tvHint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试截图"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:gravity="center_horizontal"
android:layout_gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/btnSnap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="整体截图"/>
<Button
android:id="@+id/btnImgSnap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="图片截图"/>
<Button
android:id="@+id/btnTVSnap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="文本截图"/>
</LinearLayout>
<ImageView
android:id="@+id/ivShow"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="15dp"
android:layout_gravity="center_horizontal"
android:src="@mipmap/ic_launcher"/>
<Button
android:id="@+id/btnReset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:layout_gravity="center_horizontal"
android:text="清除"/>
</LinearLayout>
这些截图针对的只是一些普通的控件,至于ScrollView,WebView或者SurfaceView等比较复杂的控件,感兴趣的同学可以自己测试一下。
核心是获取缓存的Bitmap。对于getDrawingCache()方法,会调用到View里面的buildDrawingCacheImpl(boolean autoScale)方法,这段代码是核心代码。
/**
* private, internal implementation of buildDrawingCache, used to enable tracing
*/
private void buildDrawingCacheImpl(boolean autoScale) {
mCachingFailed = false;
int width = mRight - mLeft;
int height = mBottom - mTop;
final AttachInfo attachInfo = mAttachInfo;
final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;
if (autoScale && scalingRequired) {
width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
}
final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;
final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
final long drawingCacheSize =
ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
if (width > 0 && height > 0) {
Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is"
+ " too large to fit into a software layer (or drawing cache), needs "
+ projectedBitmapSize + " bytes, only "
+ drawingCacheSize + " available");
}
destroyDrawingCache();
mCachingFailed = true;
return;
}
boolean clear = true;
Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
Bitmap.Config quality;
if (!opaque) {
// Never pick ARGB_4444 because it looks awful
// Keep the DRAWING_CACHE_QUALITY_LOW flag just in case
switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
case DRAWING_CACHE_QUALITY_AUTO:
case DRAWING_CACHE_QUALITY_LOW:
case DRAWING_CACHE_QUALITY_HIGH:
default:
quality = Bitmap.Config.ARGB_8888;
break;
}
} else {
// Optimization for translucent windows
// If the window is translucent, use a 32 bits bitmap to benefit from memcpy()
quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
}
// Try to cleanup memory
if (bitmap != null) bitmap.recycle();
try {
bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
width, height, quality);
bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
if (autoScale) {
mDrawingCache = bitmap;
} else {
mUnscaledDrawingCache = bitmap;
}
if (opaque && use32BitCache) bitmap.setHasAlpha(false);
} catch (OutOfMemoryError e) {
// If there is not enough memory to create the bitmap cache, just
// ignore the issue as bitmap caches are not required to draw the
// view hierarchy
if (autoScale) {
mDrawingCache = null;
} else {
mUnscaledDrawingCache = null;
}
mCachingFailed = true;
return;
}
clear = drawingCacheBackgroundColor != 0;
}
Canvas canvas;
if (attachInfo != null) {
canvas = attachInfo.mCanvas;
if (canvas == null) {
canvas = new Canvas();
}
canvas.setBitmap(bitmap);
// Temporarily clobber the cached Canvas in case one of our children
// is also using a drawing cache. Without this, the children would
// steal the canvas by attaching their own bitmap to it and bad, bad
// thing would happen (invisible views, corrupted drawings, etc.)
attachInfo.mCanvas = null;
} else {
// This case should hopefully never or seldom happen
canvas = new Canvas(bitmap);
}
if (clear) {
bitmap.eraseColor(drawingCacheBackgroundColor);
}
computeScroll();
final int restoreCount = canvas.save();
if (autoScale && scalingRequired) {
final float scale = attachInfo.mApplicationScale;
canvas.scale(scale, scale);
}
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN;
if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
}
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
} else {
draw(canvas);
}
canvas.restoreToCount(restoreCount);
canvas.setBitmap(null);
if (attachInfo != null) {
// Restore the cached Canvas for our siblings
attachInfo.mCanvas = canvas;
}
}
这段代码的主要作用如下:
1.获取视图的宽、高、背景色等信息。
2.计算所需的cache大小,如果宽高小于或者等于0,cache大小超过系统限制的大小,调用destroyDrawingCache(),清空缓存,返回null。
3.判断标识autoScale,获得不同的Bitmap,配置Bitmap的图像质量,RGB格式等信息。
4.根据上一步Bitmap配置信息,调用canvas.setBitmap(bitmap)或者canvas = new Canvas(bitmap),设置Canvas。
5.Canvas调用dispatchDraw()或者draw()方法绘制,Bitmap保存绘制信息。
最后,getDrawingCache()返回缓存的Bitmap对象mUnscaledDrawingCache。
需要注意的是,view设置的宽高大于手机分辨率的时候,会返回null,产生空指针的问题。可以根据view的宽高,用Canvas绘制Bitmap。示例代码如下:
public Bitmap getBitmapFromView(View view){
Bitmap bitmap = null;
try {
bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
有什么好的意见或建议,欢迎讨论。