在某些特殊場合需要對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;
}
有什麼好的意見或建議,歡迎討論。