Android對View進行截圖

在某些特殊場合需要對View進行截圖,使用View裏面的getDrawingCache()方法,返回一個Bitmap對象,就可以實現截圖的功能。

我們先看一個簡單的示例,分別點擊三個按鈕進行截圖,獲取到的Bitmap放到下面一個ImageView上面顯示,效果圖如下:
1.原圖
原圖
2.對LinearLayout裏面的內容(ImageView+TextView)截圖
LinearLayout裏面的內容截圖
3.對ImageView截圖
對ImageView截圖
4.對TextView截圖
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;
    }

有什麼好的意見或建議,歡迎討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章