Android 截圖工具類

DevUtils Github

CapturePictureUtils 截圖工具類
支持 View、Activity、FrameLayout、RelativeLayout、LinearLayout、ListView、GridView、ScrollView、HorizontalScrollView、NestedScrollView、WebView、RecyclerView(GridLayoutManager、LinearLayoutManager、StaggeredGridLayoutManager)
方法 註釋
setBitmapConfig 設置 Bitmap Config
setBackgroundColor 設置 Canvas 背景色
setPaint 設置畫筆
snapshotWithStatusBar 獲取當前屏幕截圖, 包含狀態欄 ( 頂部灰色 TitleBar 高度, 沒有設置 android:theme 的 NoTitleBar 時會顯示 )
snapshotWithoutStatusBar 獲取當前屏幕截圖, 不包含狀態欄 ( 如果 android:theme 全屏, 則截圖無狀態欄 )
enableSlowWholeDocumentDraw 關閉 WebView 優化
snapshotByWebView 截圖 WebView
snapshotByView 通過 View 繪製爲 Bitmap
snapshotByViewCache 通過 View Cache 繪製爲 Bitmap
snapshotByLinearLayout 通過 LinearLayout 繪製爲 Bitmap
snapshotByFrameLayout 通過 FrameLayout 繪製爲 Bitmap
snapshotByRelativeLayout 通過 RelativeLayout 繪製爲 Bitmap
snapshotByScrollView 通過 ScrollView 繪製爲 Bitmap
snapshotByHorizontalScrollView 通過 HorizontalScrollView 繪製爲 Bitmap
snapshotByNestedScrollView 通過 NestedScrollView 繪製爲 Bitmap
snapshotByListView 通過 ListView 繪製爲 Bitmap
snapshotByGridView 通過 GridView 繪製爲 Bitmap
snapshotByRecyclerView 通過 RecyclerView 繪製爲 Bitmap
package dev.utils.app;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.v4.widget.NestedScrollView;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.WebView;
import android.widget.FrameLayout;
import android.widget.GridView;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.ScrollView;

import dev.DevUtils;
import dev.utils.LogPrintUtils;

/**
 * detail: 截圖工具類
 * @author Ttt
 * <pre>
 *     截圖
 *     @see <a href="https://www.cnblogs.com/angel88/p/7933437.html"/>
 *     WebView 截長圖解決方案
 *     @see <a href="https://www.jianshu.com/p/0faa70e88441"/>
 *     X5 WebView 使用 snapshotWholePage 方法清晰截圖
 *     @see <a href="https://www.v2ex.com/t/583020"/>
 * </pre>
 */
public final class CapturePictureUtils {

    private CapturePictureUtils() {
    }

    // 日誌 TAG
    private static final String TAG = CapturePictureUtils.class.getSimpleName();
    // Bitmap Config
    private static Bitmap.Config BITMAP_CONFIG = Bitmap.Config.RGB_565;
    // Canvas 背景色
    private static int BACKGROUND_COLOR = Color.TRANSPARENT;
    // 畫筆
    private static Paint PAINT = new Paint();

    // ============
    // = 配置相關 =
    // ============

    /**
     * 設置 Bitmap Config
     * @param config {@link Bitmap.Config}
     */
    public static void setBitmapConfig(final Bitmap.Config config) {
        if (config == null) return;
        BITMAP_CONFIG = config;
    }

    /**
     * 設置 Canvas 背景色
     * @param backgroundColor 背景色
     */
    public static void setBackgroundColor(@ColorInt final int backgroundColor) {
        BACKGROUND_COLOR = backgroundColor;
    }

    /**
     * 設置畫筆
     * @param paint {@link Paint}
     */
    public static void setPaint(final Paint paint) {
        if (paint == null) return;
        PAINT = paint;
    }

    // ========
    // = 截圖 =
    // ========

    // ============
    // = Activity =
    // ============

    /**
     * 獲取當前屏幕截圖, 包含狀態欄 ( 頂部灰色 TitleBar 高度, 沒有設置 android:theme 的 NoTitleBar 時會顯示 )
     * @param activity {@link Activity}
     * @return 當前屏幕截圖, 包含狀態欄
     */
    public static Bitmap snapshotWithStatusBar(final Activity activity) {
        try {
            View view = activity.getWindow().getDecorView();
            view.setDrawingCacheEnabled(true);
            // 重新創建繪圖緩存, 此時的背景色是黑色
            view.buildDrawingCache();
            // 獲取繪圖緩存, 注意這裏得到的只是一個圖像的引用
            Bitmap cacheBitmap = view.getDrawingCache();
            if (cacheBitmap == null) return null;
            // 獲取屏幕寬度
            int[] widthHeight = getScreenWidthHeight();

            Rect frame = new Rect();
            activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
            // 創建新的圖片
            Bitmap bitmap = Bitmap.createBitmap(cacheBitmap, 0, 0, widthHeight[0], widthHeight[1]);
            // 釋放繪圖資源所使用的緩存
            view.destroyDrawingCache();
            return bitmap;
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotWithStatusBar");
        }
        return null;
    }

    /**
     * 獲取當前屏幕截圖, 不包含狀態欄 ( 如果 android:theme 全屏, 則截圖無狀態欄 )
     * @param activity {@link Activity}
     * @return 當前屏幕截圖, 不包含狀態欄
     */
    public static Bitmap snapshotWithoutStatusBar(final Activity activity) {
        try {
            View view = activity.getWindow().getDecorView();
            view.setDrawingCacheEnabled(true);
            // 重新創建繪圖緩存, 此時的背景色是黑色
            view.buildDrawingCache();
            // 獲取繪圖緩存, 注意這裏得到的只是一個圖像的引用
            Bitmap cacheBitmap = view.getDrawingCache();
            if (cacheBitmap == null) return null;
            // 獲取屏幕寬度
            int[] widthHeight = getScreenWidthHeight();
            // 獲取狀態欄高度
            int statusBarHeight = getStatusBarHeight(activity);

            Rect frame = new Rect();
            activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
            // 創建新的圖片
            Bitmap bitmap = Bitmap.createBitmap(cacheBitmap, 0, statusBarHeight, widthHeight[0], widthHeight[1] - statusBarHeight);
            // 釋放繪圖資源所使用的緩存
            view.destroyDrawingCache();
            return bitmap;
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotWithoutStatusBar");
        }
        return null;
    }

    // ===========
    // = WebView =
    // ===========

    /**
     * 關閉 WebView 優化
     * <pre>
     *     推薦在 setContentView 前調用
     *     {@link CapturePictureUtils#snapshotByWebView}
     * </pre>
     */
    public static void enableSlowWholeDocumentDraw() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            android.webkit.WebView.enableSlowWholeDocumentDraw();
        }
    }

    /**
     * 截圖 WebView
     * @param webView {@link WebView}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByWebView(final WebView webView) {
        return snapshotByWebView(webView, Integer.MAX_VALUE, BITMAP_CONFIG, 0f);
    }

    /**
     * 截圖 WebView
     * @param webView   {@link WebView}
     * @param maxHeight 最大高度
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByWebView(final WebView webView, final int maxHeight) {
        return snapshotByWebView(webView, maxHeight, BITMAP_CONFIG, 0f);
    }

    /**
     * 截圖 WebView
     * @param webView {@link WebView}
     * @param scale   縮放比例
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByWebView(final WebView webView, final float scale) {
        return snapshotByWebView(webView, Integer.MAX_VALUE, BITMAP_CONFIG, scale);
    }

    /**
     * 截圖 WebView
     * @param webView   {@link WebView}
     * @param maxHeight 最大高度
     * @param config    {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByWebView(final WebView webView, final int maxHeight, final Bitmap.Config config) {
        return snapshotByWebView(webView, maxHeight, config, 0f);
    }

    /**
     * 截圖 WebView
     * <pre>
     *     TODO 在 Android 5.0 及以上版本, Android 對 WebView 進行了優化, 爲了減少內存使用和提高性能
     *     TODO 使用 WebView 加載網頁時只繪製顯示部分, 如果我們不做處理, 就會出現只截到屏幕內顯示的 WebView 內容, 其它部分是空白的情況
     *     TODO 通過調用 WebView.enableSlowWholeDocumentDraw() 方法可以關閉這種優化, 但要注意的是, 該方法需要在 WebView 實例被創建前就要調用,
     *     TODO 否則沒有效果, 所以我們在 WebView 實例被創建前加入代碼
     *     {@link CapturePictureUtils#enableSlowWholeDocumentDraw}
     * </pre>
     * @param webView   {@link WebView}
     * @param maxHeight 最大高度
     * @param config    {@link Bitmap.Config}
     * @param scale     縮放比例
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByWebView(final WebView webView, final int maxHeight,
                                           final Bitmap.Config config, final float scale) {
        if (webView != null && config != null) {
            // Android 5.0 以上
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                try {
                    float newScale = scale;
                    if (newScale <= 0) {
                        // 該方法已拋棄, 可通過 setWebViewClient
                        // onScaleChanged(WebView view, float oldScale, float newScale)
                        // 存儲並傳入 newScale
                        newScale = webView.getScale();
                    }
                    int width = webView.getWidth();
                    int height = (int) (webView.getContentHeight() * newScale + 0.5);
                    // 重新設置高度
                    height = (height > maxHeight) ? maxHeight : height;
                    // 創建位圖
                    Bitmap bitmap = Bitmap.createBitmap(width, height, config);
                    Canvas canvas = new Canvas(bitmap);
                    canvas.drawColor(BACKGROUND_COLOR);
                    webView.draw(canvas);
                    return bitmap;
                } catch (Exception e) {
                    LogPrintUtils.eTag(TAG, e, "snapshotByWebView - SDK_INT >= 21(5.0)");
                }
            } else {
                try {
                    Picture picture = webView.capturePicture();
                    int width = picture.getWidth();
                    int height = picture.getHeight();
                    if (width > 0 && height > 0) {
                        // 重新設置高度
                        height = (height > maxHeight) ? maxHeight : height;
                        // 創建位圖
                        Bitmap bitmap = Bitmap.createBitmap(width, height, config);
                        Canvas canvas = new Canvas(bitmap);
                        canvas.drawColor(BACKGROUND_COLOR);
                        picture.draw(canvas);
                        return bitmap;
                    }
                } catch (Exception e) {
                    LogPrintUtils.eTag(TAG, e, "snapshotByWebView - SDK_INT < 21(5.0)");
                }
            }
        }
        return null;
    }

    // ========
    // = View =
    // ========

    /**
     * 通過 View 繪製爲 Bitmap
     * @param view {@link View}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByView(final View view) {
        return snapshotByView(view, BITMAP_CONFIG);
    }

    /**
     * 通過 View 繪製爲 Bitmap
     * @param view   {@link View}
     * @param config {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByView(final View view, final Bitmap.Config config) {
        if (view == null || config == null) return null;
        try {
            Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), config);
            Canvas canvas = new Canvas(bitmap);
            canvas.drawColor(BACKGROUND_COLOR);
            view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
            view.draw(canvas);
            return bitmap;
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotByView");
        }
        return null;
    }

    /**
     * 通過 View Cache 繪製爲 Bitmap
     * @param view {@link View}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByViewCache(final View view) {
        if (view == null) return null;
        try {
            // 清除視圖焦點
            view.clearFocus();
            // 將視圖設爲不可點擊
            view.setPressed(false);

            // 獲取視圖是否可以保存畫圖緩存
            boolean willNotCache = view.willNotCacheDrawing();
            view.setWillNotCacheDrawing(false);

            // 獲取繪製緩存位圖的背景顏色
            int color = view.getDrawingCacheBackgroundColor();
            // 設置繪圖背景顏色
            view.setDrawingCacheBackgroundColor(0);
            if (color != 0) { // 獲取的背景不是黑色的則釋放以前的繪圖緩存
                view.destroyDrawingCache(); // 釋放繪圖資源所使用的緩存
            }

            // 重新創建繪圖緩存, 此時的背景色是黑色
            view.buildDrawingCache();
            // 獲取繪圖緩存, 注意這裏得到的只是一個圖像的引用
            Bitmap cacheBitmap = view.getDrawingCache();
            if (cacheBitmap == null) return null;

            Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
            // 釋放位圖內存
            view.destroyDrawingCache();
            // 回滾以前的緩存設置、緩存顏色設置
            view.setWillNotCacheDrawing(willNotCache);
            view.setDrawingCacheBackgroundColor(color);
            return bitmap;
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotByViewCache");
        }
        return null;
    }

    // ================
    // = LinearLayout =
    // ================

    /**
     * 通過 LinearLayout 繪製爲 Bitmap
     * @param linearLayout {@link LinearLayout}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByLinearLayout(final LinearLayout linearLayout) {
        return snapshotByLinearLayout(linearLayout, BITMAP_CONFIG);
    }

    /**
     * 通過 LinearLayout 繪製爲 Bitmap
     * <pre>
     *     LinearLayout 容器中不能有諸如 ListView、GridView、WebView 這樣的高度可變的控件
     * </pre>
     * @param linearLayout {@link LinearLayout}
     * @param config       {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByLinearLayout(final LinearLayout linearLayout, final Bitmap.Config config) {
        return snapshotByView(linearLayout, config);
    }

    // ===============
    // = FrameLayout =
    // ===============

    /**
     * 通過 FrameLayout 繪製爲 Bitmap
     * @param frameLayout {@link FrameLayout}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByFrameLayout(final FrameLayout frameLayout) {
        return snapshotByFrameLayout(frameLayout, BITMAP_CONFIG);
    }

    /**
     * 通過 FrameLayout 繪製爲 Bitmap
     * <pre>
     *     FrameLayout 容器中不能有諸如 ListView、GridView、WebView 這樣的高度可變的控件
     * </pre>
     * @param frameLayout {@link FrameLayout}
     * @param config      {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByFrameLayout(final FrameLayout frameLayout, final Bitmap.Config config) {
        return snapshotByView(frameLayout, config);
    }

    // ==================
    // = RelativeLayout =
    // ==================

    /**
     * 通過 RelativeLayout 繪製爲 Bitmap
     * @param relativeLayout {@link RelativeLayout}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByRelativeLayout(final RelativeLayout relativeLayout) {
        return snapshotByRelativeLayout(relativeLayout, BITMAP_CONFIG);
    }

    /**
     * 通過 RelativeLayout 繪製爲 Bitmap
     * <pre>
     *     RelativeLayout 容器中不能有諸如 ListView、GridView、WebView 這樣的高度可變的控件
     * </pre>
     * @param relativeLayout {@link RelativeLayout}
     * @param config         {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByRelativeLayout(final RelativeLayout relativeLayout, final Bitmap.Config config) {
        return snapshotByView(relativeLayout, config);
    }

    // ==============
    // = ScrollView =
    // ==============

    /**
     * 通過 ScrollView 繪製爲 Bitmap
     * @param scrollView {@link ScrollView}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByScrollView(final ScrollView scrollView) {
        return snapshotByScrollView(scrollView, BITMAP_CONFIG);
    }

    /**
     * 通過 ScrollView 繪製爲 Bitmap
     * <pre>
     *     ScrollView 容器中不能有諸如 ListView、GridView、WebView 這樣的高度可變的控件
     * </pre>
     * @param scrollView {@link ScrollView}
     * @param config     {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByScrollView(final ScrollView scrollView, final Bitmap.Config config) {
        if (scrollView == null || config == null) return null;
        try {
            View view = scrollView.getChildAt(0);
            int width = view.getWidth();
            int height = view.getHeight();

            Bitmap bitmap = Bitmap.createBitmap(width, height, config);
            Canvas canvas = new Canvas(bitmap);
            canvas.drawColor(BACKGROUND_COLOR);
            scrollView.layout(0, 0, scrollView.getMeasuredWidth(),
                    scrollView.getMeasuredHeight());
            scrollView.draw(canvas);
            return bitmap;
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotByScrollView");
        }
        return null;
    }

    // ========================
    // = HorizontalScrollView =
    // ========================

    /**
     * 通過 HorizontalScrollView 繪製爲 Bitmap
     * @param scrollView {@link HorizontalScrollView}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByHorizontalScrollView(final HorizontalScrollView scrollView) {
        return snapshotByHorizontalScrollView(scrollView, BITMAP_CONFIG);
    }

    /**
     * 通過 HorizontalScrollView 繪製爲 Bitmap
     * @param scrollView {@link HorizontalScrollView}
     * @param config     {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByHorizontalScrollView(final HorizontalScrollView scrollView, final Bitmap.Config config) {
        if (scrollView == null || config == null) return null;
        try {
            View view = scrollView.getChildAt(0);
            int width = view.getWidth();
            int height = view.getHeight();

            Bitmap bitmap = Bitmap.createBitmap(width, height, config);
            Canvas canvas = new Canvas(bitmap);
            canvas.drawColor(BACKGROUND_COLOR);
            scrollView.layout(0, 0, scrollView.getMeasuredWidth(),
                    scrollView.getMeasuredHeight());
            scrollView.draw(canvas);
            return bitmap;
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotByHorizontalScrollView");
        }
        return null;
    }

    // ====================
    // = NestedScrollView =
    // ====================

    /**
     * 通過 NestedScrollView 繪製爲 Bitmap
     * @param scrollView {@link NestedScrollView}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByNestedScrollView(final NestedScrollView scrollView) {
        return snapshotByNestedScrollView(scrollView, BITMAP_CONFIG);
    }

    /**
     * 通過 NestedScrollView 繪製爲 Bitmap
     * @param scrollView {@link NestedScrollView}
     * @param config     {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByNestedScrollView(final NestedScrollView scrollView, final Bitmap.Config config) {
        if (scrollView == null || config == null) return null;
        try {
            View view = scrollView.getChildAt(0);
            int width = view.getWidth();
            int height = view.getHeight();

            Bitmap bitmap = Bitmap.createBitmap(width, height, config);
            Canvas canvas = new Canvas(bitmap);
            canvas.drawColor(BACKGROUND_COLOR);
            scrollView.layout(0, 0, scrollView.getMeasuredWidth(),
                    scrollView.getMeasuredHeight());
            scrollView.draw(canvas);
            return bitmap;
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotByNestedScrollView");
        }
        return null;
    }

    // ============
    // = ListView =
    // ============

    /**
     * 通過 ListView 繪製爲 Bitmap
     * @param listView {@link ListView}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByListView(final ListView listView) {
        return snapshotByListView(listView, BITMAP_CONFIG);
    }

    /**
     * 通過 ListView 繪製爲 Bitmap
     * @param listView {@link ListView}
     * @param config   {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByListView(final ListView listView, final Bitmap.Config config) {
        if (listView == null || config == null) return null;
        try {
            // Adapter
            ListAdapter listAdapter = listView.getAdapter();
            // Item 總條數
            int itemCount = listAdapter.getCount();
            // 沒數據則直接跳過
            if (itemCount == 0) return null;
            // 高度
            int height = 0;
            // 獲取子項間分隔符佔用的高度
            int dividerHeight = listView.getDividerHeight();
            // View Bitmaps
            Bitmap[] bitmaps = new Bitmap[itemCount];

            // 循環繪製每個 Item 並保存 Bitmap
            for (int i = 0; i < itemCount; i++) {
                View childView = listAdapter.getView(i, null, listView);
                measureView(childView, listView.getWidth());
                bitmaps[i] = canvasBitmap(childView, config);
                height += childView.getMeasuredHeight();
            }
            // 追加子項間分隔符佔用的高度
            height += (dividerHeight * (itemCount - 1));
            int width = listView.getMeasuredWidth();
            // 創建位圖
            Bitmap bitmap = Bitmap.createBitmap(width, height, config);
            Canvas canvas = new Canvas(bitmap);
            canvas.drawColor(BACKGROUND_COLOR);
            // 累加高度
            int appendHeight = 0;
            for (int i = 0, len = bitmaps.length; i < len; i++) {
                Bitmap bmp = bitmaps[i];
                canvas.drawBitmap(bmp, 0, appendHeight, PAINT);
                appendHeight += (bmp.getHeight() + dividerHeight);
                // 釋放資源
                bmp.recycle();
                bmp = null;
            }
            return bitmap;
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotByListView");
        }
        return null;
    }

    // ============
    // = GridView =
    // ============

    /**
     * 通過 GridView 繪製爲 Bitmap
     * @param gridView {@link GridView}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByGridView(final GridView gridView) {
        return snapshotByGridView(gridView, BITMAP_CONFIG, false);
    }

    /**
     * 通過 GridView 繪製爲 Bitmap
     * @param gridView {@link GridView}
     * @param config   {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByGridView(final GridView gridView, final Bitmap.Config config) {
        return snapshotByGridView(gridView, config, false);
    }

    /**
     * 通過 GridView 繪製爲 Bitmap
     * @param gridView       {@link GridView}
     * @param config         {@link Bitmap.Config}
     * @param listViewEffect 是否保存 ListView 效果 ( 每個 Item 鋪滿 )
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByGridView(final GridView gridView, final Bitmap.Config config, final boolean listViewEffect) {
        if (gridView == null || config == null) return null;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return null;
        try {
            // Adapter
            ListAdapter listAdapter = gridView.getAdapter();
            // Item 總條數
            int itemCount = listAdapter.getCount();
            // 沒數據則直接跳過
            if (itemCount == 0) return null;
            // 高度
            int height = 0;
            // 獲取一共多少列
            int numColumns = gridView.getNumColumns();
            // 每列之間的間隔 |
            int horizontalSpacing = gridView.getHorizontalSpacing();
            // 每行之間的間隔 -
            int verticalSpacing = gridView.getVerticalSpacing();
            // View Bitmaps
            Bitmap[] bitmaps = new Bitmap[itemCount];

            // 效果處理 - ListView 效果 Item 鋪滿
            if (listViewEffect) {
                // 循環繪製每個 Item 並保存 Bitmap
                for (int i = 0; i < itemCount; i++) {
                    View childView = listAdapter.getView(i, null, gridView);
                    measureView(childView, gridView.getWidth());
                    bitmaps[i] = canvasBitmap(childView, config);
                    height += childView.getMeasuredHeight();
                }
                // 追加子項間分隔符佔用的高度
                height += (verticalSpacing * (itemCount - 1));
                int width = gridView.getMeasuredWidth();
                // 創建位圖
                Bitmap bitmap = Bitmap.createBitmap(width, height, config);
                Canvas canvas = new Canvas(bitmap);
                canvas.drawColor(BACKGROUND_COLOR);
                // 累加高度
                int appendHeight = 0;
                for (int i = 0, len = bitmaps.length; i < len; i++) {
                    Bitmap bmp = bitmaps[i];
                    canvas.drawBitmap(bmp, 0, appendHeight, PAINT);
                    appendHeight += (bmp.getHeight() + verticalSpacing);
                    // 釋放資源
                    bmp.recycle();
                    bmp = null;
                }
                return bitmap;
            } else {
                // 獲取倍數 ( 行數 )
                int lineNumber = getMultiple(itemCount, numColumns);
                // 計算總共的寬度 - (GridView 寬度 - 列分割間距 ) / numColumns
                int childWidth = (gridView.getWidth() - (numColumns - 1) * horizontalSpacing) / numColumns;

                // 記錄每行最大高度
                int[] rowHeightArrays = new int[lineNumber];
                // 臨時高度 - 保存行中最高的列高度
                int tempHeight;
                // 循環每一行繪製每個 Item 並保存 Bitmap
                for (int i = 0; i < lineNumber; i++) {
                    // 清空高度
                    tempHeight = 0;
                    // 循環列數
                    for (int j = 0; j < numColumns; j++) {
                        // 獲取對應的索引
                        int position = i * numColumns + j;
                        // 小於總數才處理
                        if (position < itemCount) {
                            View childView = listAdapter.getView(position, null, gridView);
                            measureView(childView, childWidth);
                            bitmaps[position] = canvasBitmap(childView, config);

                            int itemHeight = childView.getMeasuredHeight();
                            // 保留最大高度
                            tempHeight = Math.max(itemHeight, tempHeight);
                        }

                        // 記錄高度並累加
                        if (j == numColumns - 1) {
                            height += tempHeight;
                            rowHeightArrays[i] = tempHeight;
                        }
                    }
                }
                // 追加子項間分隔符佔用的高度
                height += (verticalSpacing * (lineNumber - 1));
                int width = gridView.getMeasuredWidth();
                // 創建位圖
                Bitmap bitmap = Bitmap.createBitmap(width, height, config);
                Canvas canvas = new Canvas(bitmap);
                canvas.drawColor(BACKGROUND_COLOR);
                // 累加高度
                int appendHeight = 0;
                // 循環每一行繪製每個 Item Bitmap
                for (int i = 0; i < lineNumber; i++) {
                    // 獲取每一行最長列的高度
                    int itemHeight = rowHeightArrays[i];
                    // 循環列數
                    for (int j = 0; j < numColumns; j++) {
                        // 獲取對應的索引
                        int position = i * numColumns + j;
                        // 小於總數才處理
                        if (position < itemCount) {
                            Bitmap bmp = bitmaps[position];
                            // 計算邊距
                            int left = j * (horizontalSpacing + childWidth);
                            Matrix matrix = new Matrix();
                            matrix.postTranslate(left, appendHeight);
                            // 繪製到 Bitmap
                            canvas.drawBitmap(bmp, matrix, PAINT);
                            // 釋放資源
                            bmp.recycle();
                            bmp = null;
                        }

                        // 記錄高度並累加
                        if (j == numColumns - 1) {
                            appendHeight += itemHeight + verticalSpacing;
                        }
                    }
                }
                return bitmap;
            }
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotByGridView");
        }
        return null;
    }

    // ================
    // = RecyclerView =
    // ================

    /**
     * 通過 RecyclerView 繪製爲 Bitmap
     * @param recyclerView {@link RecyclerView}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByRecyclerView(final RecyclerView recyclerView) {
        return snapshotByRecyclerView(recyclerView, BITMAP_CONFIG, 0, 0);
    }

    /**
     * 通過 RecyclerView 繪製爲 Bitmap
     * @param recyclerView {@link RecyclerView}
     * @param config       {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByRecyclerView(final RecyclerView recyclerView,
                                                final Bitmap.Config config) {
        return snapshotByRecyclerView(recyclerView, config, 0, 0);
    }

    /**
     * 通過 RecyclerView 繪製爲 Bitmap
     * @param recyclerView {@link RecyclerView}
     * @param spacing      每列之間的間隔 |
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByRecyclerView(final RecyclerView recyclerView, final int spacing) {
        return snapshotByRecyclerView(recyclerView, BITMAP_CONFIG, spacing, spacing);
    }

    /**
     * 通過 RecyclerView 繪製爲 Bitmap
     * @param recyclerView {@link RecyclerView}
     * @param config       {@link Bitmap.Config}
     * @param spacing      每列之間的間隔 |
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByRecyclerView(final RecyclerView recyclerView,
                                                final Bitmap.Config config, final int spacing) {
        return snapshotByRecyclerView(recyclerView, config, spacing, spacing);
    }

    /**
     * 通過 RecyclerView 繪製爲 Bitmap
     * <pre>
     *     不支持含 ItemDecoration 截圖
     *     如果數據太多推薦 copy 代碼, 修改爲保存每個 Item Bitmap 到本地, 並在繪製時獲取繪製
     * </pre>
     * @param recyclerView      {@link RecyclerView}
     * @param config            {@link Bitmap.Config}
     * @param verticalSpacing   每行之間的間隔 -
     * @param horizontalSpacing 每列之間的間隔 |
     * @return {@link Bitmap}
     */
    public static Bitmap snapshotByRecyclerView(final RecyclerView recyclerView, final Bitmap.Config config,
                                                final int verticalSpacing, final int horizontalSpacing) {
        if (recyclerView == null || config == null) return null;
        try {
            // 獲取適配器
            RecyclerView.Adapter adapter = recyclerView.getAdapter();
            // 獲取佈局管理器
            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager != null && adapter != null) {
                // 判斷佈局類型
                if (layoutManager instanceof GridLayoutManager) {
                    return snapshotByRecyclerView_GridLayoutManager(recyclerView,
                            config, verticalSpacing, horizontalSpacing);
                } else if (layoutManager instanceof LinearLayoutManager) {
                    return snapshotByRecyclerView_LinearLayoutManager(recyclerView,
                            config, verticalSpacing, horizontalSpacing);
                } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                    return snapshotByRecyclerView_StaggeredGridLayoutManager(recyclerView,
                            config, verticalSpacing, horizontalSpacing);
                }
                throw new Exception(String.format("Not Supported %s LayoutManager", layoutManager.getClass().getSimpleName()));
            } else {
                throw new Exception("Adapter or LayoutManager is Null");
            }
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotByRecyclerView");
        }
        return null;
    }

    // =================
    // = LayoutManager =
    // =================

    /**
     * 通過 RecyclerView GridLayoutManager 繪製爲 Bitmap
     * @param recyclerView      {@link RecyclerView}
     * @param config            {@link Bitmap.Config}
     * @param verticalSpacing   每行之間的間隔 -
     * @param horizontalSpacing 每列之間的間隔 |
     * @return {@link Bitmap}
     */
    private static Bitmap snapshotByRecyclerView_GridLayoutManager(final RecyclerView recyclerView,
                                                                   final Bitmap.Config config,
                                                                   final int verticalSpacing,
                                                                   final int horizontalSpacing) {
        // 計算思路
        // = 豎屏 =
        // 每個 Item 寬度最大值固定爲 (RecyclerView 寬度 - ( 列數 - 1) * 每列邊距 ) / 列數
        // 循環保存每行最大高度, 並累加每行之間的間隔, 用於 Bitmap 高度, 寬度用 RecyclerView 寬度
        // = 橫屏 =
        // 循環保存每一行寬度以及每一行 ( 橫着一行 ) 最大高度, 並且累加每行、每列之間的間隔
        // 用於 Bitmap 高度, 寬度用 ( 每一行寬度累加值 ) 最大值
        try {
            // 獲取適配器
            RecyclerView.Adapter adapter = recyclerView.getAdapter();
            // Item 總條數
            int itemCount = adapter.getItemCount();
            // 沒數據則直接跳過
            if (itemCount == 0) return null;
            // 寬高
            int width = 0, height = 0;
            // View Bitmaps
            Bitmap[] bitmaps = new Bitmap[itemCount];
            // 獲取佈局管理器 - 判斷橫豎佈局
            GridLayoutManager gridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
            boolean vertical = (gridLayoutManager.getOrientation() == 1);
            // 獲取一共多少列
            int spanCount = gridLayoutManager.getSpanCount();
            // 獲取倍數 ( 行數 )
            int lineNumber = getMultiple(itemCount, spanCount);
            if (vertical) {

                // ============
                // = 豎向滑動 =
                // ============

                // 計算總共的寬度 - (GridView 寬度 - 列分割間距 ) / spanCount
                int childWidth = (recyclerView.getWidth() - (spanCount - 1) * horizontalSpacing) / spanCount;
                // 記錄每行最大高度
                int[] rowHeightArrays = new int[lineNumber];
                // 臨時高度 - 保存行中最高的列高度
                int tempHeight;
                for (int i = 0; i < lineNumber; i++) {
                    // 清空高度
                    tempHeight = 0;
                    // 循環列數
                    for (int j = 0; j < spanCount; j++) {
                        // 獲取對應的索引
                        int position = i * spanCount + j;
                        // 小於總數才處理
                        if (position < itemCount) {
                            RecyclerView.ViewHolder holder = adapter.createViewHolder(recyclerView, adapter.getItemViewType(position));
                            adapter.onBindViewHolder(holder, position);
                            View childView = holder.itemView;
                            measureView(childView, childWidth);
                            bitmaps[position] = canvasBitmap(childView, config);
                            int itemHeight = childView.getMeasuredHeight();
                            // 保留最大高度
                            tempHeight = Math.max(itemHeight, tempHeight);
                        }

                        // 記錄高度並累加
                        if (j == spanCount - 1) {
                            height += tempHeight;
                            rowHeightArrays[i] = tempHeight;
                        }
                    }
                }

                // 追加子項間分隔符佔用的高度
                height += (verticalSpacing * (lineNumber - 1));
                width = recyclerView.getMeasuredWidth();
                // 創建位圖
                Bitmap bitmap = Bitmap.createBitmap(width, height, config);
                Canvas canvas = new Canvas(bitmap);
                canvas.drawColor(BACKGROUND_COLOR);
                // 累加高度
                int appendHeight = 0;
                for (int i = 0; i < lineNumber; i++) {
                    // 獲取每行中最高的列高度
                    int rowHeight = rowHeightArrays[i];
                    // 循環列數
                    for (int j = 0; j < spanCount; j++) {
                        // 獲取對應的索引
                        int position = i * spanCount + j;
                        // 小於總數才處理
                        if (position < itemCount) {
                            Bitmap bmp = bitmaps[position];
                            // 計算邊距
                            int left = j * (horizontalSpacing + childWidth);
                            Matrix matrix = new Matrix();
                            matrix.postTranslate(left, appendHeight);
                            // 繪製到 Bitmap
                            canvas.drawBitmap(bmp, matrix, PAINT);
                            // 釋放資源
                            bmp.recycle();
                            bmp = null;
                        }

                        // 記錄高度並累加
                        if (j == spanCount - 1) {
                            appendHeight += (rowHeight + verticalSpacing);
                        }
                    }
                }
                return bitmap;
            } else {

                // ============
                // = 橫向滑動 =
                // ============

                // 獲取行數
                lineNumber = Math.min(spanCount, itemCount);
                // 記錄每一行寬度
                int[] rowWidthArrays = new int[lineNumber];
                // 記錄每一行高度
                int[] rowHeightArrays = new int[lineNumber];
                // 獲取一共多少列
                int numColumns = getMultiple(itemCount, lineNumber);
                // 臨時高度 - 保存行中最高的列高度
                int tempHeight;
                for (int i = 0; i < lineNumber; i++) {
                    // 清空高度
                    tempHeight = 0;
                    // 循環列數
                    for (int j = 0; j < numColumns; j++) {
                        // 獲取對應的索引
                        int position = j * lineNumber + i;
                        // 小於總數才處理
                        if (position < itemCount) {
                            RecyclerView.ViewHolder holder = adapter.createViewHolder(recyclerView, adapter.getItemViewType(position));
                            adapter.onBindViewHolder(holder, position);
                            View childView = holder.itemView;
                            measureView(childView, 0);
                            bitmaps[position] = canvasBitmap(childView, config);
                            rowWidthArrays[i] += childView.getMeasuredWidth();
                            int itemHeight = childView.getMeasuredHeight();
                            // 保留最大高度
                            tempHeight = Math.max(itemHeight, tempHeight);
                        }

                        // 最後記錄處理
                        if (j == numColumns - 1) {
                            height += tempHeight;
                            width = Math.max(width, rowWidthArrays[i]);
                            rowHeightArrays[i] = tempHeight;
                        }
                    }
                }

                // 追加子項間分隔符佔用的高、寬
                height += (verticalSpacing * (lineNumber - 1));
                width += (horizontalSpacing * (numColumns - 1));
                // 創建位圖
                Bitmap bitmap = Bitmap.createBitmap(width, height, config);
                Canvas canvas = new Canvas(bitmap);
                canvas.drawColor(BACKGROUND_COLOR);
                // 累加寬、高
                int appendWidth = 0, appendHeight = 0;
                for (int i = 0; i < lineNumber; i++) {
                    // 獲取每行中最高的列高度
                    int rowHeight = rowHeightArrays[i];
                    // 循環列數
                    for (int j = 0; j < numColumns; j++) {
                        // 獲取對應的索引
                        int position = j * lineNumber + i;
                        // 小於總數才處理
                        if (position < itemCount) {
                            Bitmap bmp = bitmaps[position];
                            // 計算邊距
                            int left = appendWidth + (j * horizontalSpacing);
                            Matrix matrix = new Matrix();
                            matrix.postTranslate(left, appendHeight);
                            // 繪製到 Bitmap
                            canvas.drawBitmap(bmp, matrix, PAINT);
                            // 累加 Bitmap 寬度
                            appendWidth += bmp.getWidth();
                            // 釋放資源
                            bmp.recycle();
                            bmp = null;
                        }

                        // 記錄高度並累加
                        if (j == numColumns - 1) {
                            appendWidth = 0;
                            appendHeight += (rowHeight + verticalSpacing);
                        }
                    }
                }
                return bitmap;
            }
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotByRecyclerView_GridLayoutManager");
        }
        return null;
    }

    /**
     * 通過 RecyclerView LinearLayoutManager 繪製爲 Bitmap
     * @param recyclerView      {@link RecyclerView}
     * @param config            {@link Bitmap.Config}
     * @param verticalSpacing   每行之間的間隔 -
     * @param horizontalSpacing 每列之間的間隔 |
     * @return {@link Bitmap}
     */
    private static Bitmap snapshotByRecyclerView_LinearLayoutManager(final RecyclerView recyclerView,
                                                                     final Bitmap.Config config,
                                                                     final int verticalSpacing,
                                                                     final int horizontalSpacing) {
        // 計算思路
        // = 豎屏 =
        // 循環保存每一個 Item View 高度, 並累加每行之間的間隔,
        // 用於 Bitmap 高度, 寬度用 RecyclerView 寬度
        // = 橫屏 =
        // 循環保存每一個 Item View 寬度, 並累加每列之間的間隔, 且記錄最高的列
        // 用於 Bitmap 高度, 寬度用累加出來的值
        try {
            // 獲取適配器
            RecyclerView.Adapter adapter = recyclerView.getAdapter();
            // Item 總條數
            int itemCount = adapter.getItemCount();
            // 沒數據則直接跳過
            if (itemCount == 0) return null;
            // 寬高
            int width = 0, height = 0;
            // View Bitmaps
            Bitmap[] bitmaps = new Bitmap[itemCount];
            // 獲取佈局管理器 - 判斷橫豎佈局
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
            boolean vertical = (linearLayoutManager.getOrientation() == 1);
            if (vertical) {

                // ============
                // = 豎向滑動 =
                // ============

                for (int i = 0; i < itemCount; i++) {
                    RecyclerView.ViewHolder holder = adapter.createViewHolder(recyclerView, adapter.getItemViewType(i));
                    adapter.onBindViewHolder(holder, i);
                    View childView = holder.itemView;
                    measureView(childView, recyclerView.getWidth());
                    bitmaps[i] = canvasBitmap(childView, config);
                    height += childView.getMeasuredHeight();
                }

                // 追加子項間分隔符佔用的高度
                height += (verticalSpacing * (itemCount - 1));
                width = recyclerView.getMeasuredWidth();
                // 創建位圖
                Bitmap bitmap = Bitmap.createBitmap(width, height, config);
                Canvas canvas = new Canvas(bitmap);
                canvas.drawColor(BACKGROUND_COLOR);
                // 累加高度
                int appendHeight = 0;
                for (int i = 0; i < itemCount; i++) {
                    Bitmap bmp = bitmaps[i];
                    canvas.drawBitmap(bmp, 0, appendHeight, PAINT);
                    appendHeight += (bmp.getHeight() + verticalSpacing);
                    // 釋放資源
                    bmp.recycle();
                    bmp = null;
                }
                return bitmap;
            } else {

                // ============
                // = 橫向滑動 =
                // ============

                // 臨時高度 - 保存行中最高的列高度
                int tempHeight = 0;
                for (int i = 0; i < itemCount; i++) {
                    RecyclerView.ViewHolder holder = adapter.createViewHolder(recyclerView, adapter.getItemViewType(i));
                    adapter.onBindViewHolder(holder, i);
                    View childView = holder.itemView;
                    measureView(childView, 0);
                    bitmaps[i] = canvasBitmap(childView, config);
                    width += childView.getMeasuredWidth();
                    int itemHeight = childView.getMeasuredHeight();
                    // 保留最大高度
                    tempHeight = Math.max(itemHeight, tempHeight);
                }

                // 追加子項間分隔符佔用的寬度
                width += (horizontalSpacing * (itemCount - 1));
                height = tempHeight;
                // 創建位圖
                Bitmap bitmap = Bitmap.createBitmap(width, height, config);
                Canvas canvas = new Canvas(bitmap);
                canvas.drawColor(BACKGROUND_COLOR);
                // 累加寬度
                int appendWidth = 0;
                for (int i = 0; i < itemCount; i++) {
                    Bitmap bmp = bitmaps[i];
                    canvas.drawBitmap(bmp, appendWidth, 0, PAINT);
                    appendWidth += (bmp.getWidth() + horizontalSpacing);
                    // 釋放資源
                    bmp.recycle();
                    bmp = null;
                }
                return bitmap;
            }
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotByRecyclerView_LinearLayoutManager");
        }
        return null;
    }

    /**
     * 通過 RecyclerView StaggeredGridLayoutManager 繪製爲 Bitmap
     * @param recyclerView      {@link RecyclerView}
     * @param config            {@link Bitmap.Config}
     * @param verticalSpacing   每行之間的間隔 -
     * @param horizontalSpacing 每列之間的間隔 |
     * @return {@link Bitmap}
     */
    private static Bitmap snapshotByRecyclerView_StaggeredGridLayoutManager(final RecyclerView recyclerView,
                                                                            final Bitmap.Config config,
                                                                            final int verticalSpacing,
                                                                            final int horizontalSpacing) {
        // 計算思路
        // = 豎屏 =
        // 每個 Item 寬度最大值固定爲 (RecyclerView 寬度 - ( 列數 - 1) * 每列邊距 ) / 列數
        // 循環保存每一個 Item View 高度, 並創建數組記錄每一列待繪製高度, 實現瀑布流高度補差
        // 並通過該數組 ( 每列待繪製高度數組 ) 獲取最大值, 用做 Bitmap 高度, 繪製則還是按以上規則高度補差累加
        // = 橫屏 =
        // 循環保存每一個 Item View 寬度、高度, 並創建數組記錄每一列待繪製寬度, 實現瀑布流高度補差
        // 並通過該數組 ( 每列待繪製寬度數組 ) 獲取最大值, 用做 Bitmap 高度, 繪製則還是按以上規則寬度補差累加
        try {
            // 獲取適配器
            RecyclerView.Adapter adapter = recyclerView.getAdapter();
            // Item 總條數
            int itemCount = adapter.getItemCount();
            // 沒數據則直接跳過
            if (itemCount == 0) return null;
            // 寬高
            int width = 0, height = 0;
            // View Bitmaps
            Bitmap[] bitmaps = new Bitmap[itemCount];
            // 獲取佈局管理器 - 判斷橫豎佈局
            StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) recyclerView.getLayoutManager();
            boolean vertical = (staggeredGridLayoutManager.getOrientation() == 1);
            // 獲取一共多少列
            int spanCount = staggeredGridLayoutManager.getSpanCount();
            // 獲取倍數 ( 行數 )
            int lineNumber = getMultiple(itemCount, spanCount);
            if (vertical) {

                // ============
                // = 豎向滑動 =
                // ============

                // 計算總共的寬度 - (GridView 寬度 - 列分割間距 ) / spanCount
                int childWidth = (recyclerView.getWidth() - (spanCount - 1) * horizontalSpacing) / spanCount;
                // 記錄每個 Item 高度
                int[] itemHeightArrays = new int[itemCount];
                for (int i = 0; i < itemCount; i++) {
                    RecyclerView.ViewHolder holder = adapter.createViewHolder(recyclerView, adapter.getItemViewType(i));
                    adapter.onBindViewHolder(holder, i);
                    View childView = holder.itemView;
                    measureView(childView, childWidth);
                    bitmaps[i] = canvasBitmap(childView, config);
                    itemHeightArrays[i] = childView.getMeasuredHeight();
                }

                // 記錄每列 Item 個數
                int[] columnsItemNumberArrays = new int[spanCount];
                // 記錄每列總高度
                int[] columnsHeightArrays = new int[spanCount];
                // 循環高度, 計算繪製位置
                for (int i = 0; i < itemCount; i++) {
                    // 獲取最小高度索引
                    int minIndex = getMinimumIndex(columnsHeightArrays);
                    // 累加高度
                    columnsHeightArrays[minIndex] += itemHeightArrays[i];
                    // 累加數量
                    columnsItemNumberArrays[minIndex] += 1;
                }

                // 計算高度 - 追加子項間分隔符佔用的高度
                if (lineNumber >= 2) {
                    // 循環追加子項間分隔符佔用的高度
                    for (int i = 0; i < spanCount; i++) {
                        columnsHeightArrays[i] += (columnsItemNumberArrays[i] - 1) * verticalSpacing;
                    }
                }

                // 獲取列最大高度索引
                int columnsHeightMaxIndex = getMaximumIndex(columnsHeightArrays);
                // 獲取最大高度值
                int maxColumnsHeight = columnsHeightArrays[columnsHeightMaxIndex];
                // 使用最大值
                height = maxColumnsHeight;
                width = recyclerView.getMeasuredWidth();

                // 清空繪製時累加計算
                columnsHeightArrays = new int[spanCount];
                // 創建位圖
                Bitmap bitmap = Bitmap.createBitmap(width, height, config);
                Canvas canvas = new Canvas(bitmap);
                canvas.drawColor(BACKGROUND_COLOR);
                // 循環繪製
                for (int i = 0; i < itemCount; i++) {
                    // 獲取最小高度索引
                    int minIndex = getMinimumIndex(columnsHeightArrays);
                    // 計算邊距
                    int left = minIndex * (horizontalSpacing + childWidth);
                    Matrix matrix = new Matrix();
                    matrix.postTranslate(left, columnsHeightArrays[minIndex]);
                    // 繪製到 Bitmap
                    Bitmap bmp = bitmaps[i];
                    canvas.drawBitmap(bmp, matrix, PAINT);
                    // 累加高度
                    columnsHeightArrays[minIndex] += (itemHeightArrays[i] + verticalSpacing);
                    // 釋放資源
                    bmp.recycle();
                    bmp = null;
                }
                return bitmap;
            } else {

                // ============
                // = 橫向滑動 =
                // ============

                // 獲取行數
                lineNumber = Math.min(spanCount, itemCount);
                // 記錄每個 Item 寬度
                int[] itemWidthArrays = new int[itemCount];
                // 記錄每個 Item 高度
                int[] itemHeightArrays = new int[itemCount];
                for (int i = 0; i < itemCount; i++) {
                    RecyclerView.ViewHolder holder = adapter.createViewHolder(recyclerView, adapter.getItemViewType(i));
                    adapter.onBindViewHolder(holder, i);
                    View childView = holder.itemView;
                    measureView(childView, 0);
                    bitmaps[i] = canvasBitmap(childView, config);
                    itemWidthArrays[i] = childView.getMeasuredWidth();
                    itemHeightArrays[i] = childView.getMeasuredHeight();
                }

                // 記錄每行向上距離
                int[] columnsTopArrays = new int[lineNumber];
                // 記錄每行 Item 個數
                int[] columnsItemNumberArrays = new int[lineNumber];
                // 記錄每行總寬度
                int[] columnsWidthArrays = new int[lineNumber];
                // 記錄每行最大高度
                int[] columnsHeightArrays = new int[lineNumber];
                // 循環寬度, 計算繪製位置
                for (int i = 0; i < itemCount; i++) {
                    // 獲取最小寬度索引
                    int minIndex = getMinimumIndex(columnsWidthArrays);
                    // 累加寬度
                    columnsWidthArrays[minIndex] += itemWidthArrays[i];
                    // 累加數量
                    columnsItemNumberArrays[minIndex] += 1;
                    // 保存每行最大高度
                    columnsHeightArrays[minIndex] = Math.max(itemHeightArrays[i], columnsHeightArrays[minIndex]);
                }

                // 循環追加子項間分隔符佔用的寬度
                for (int i = 0; i < lineNumber; i++) {
                    if (columnsItemNumberArrays[i] > 1) {
                        columnsWidthArrays[i] += (columnsItemNumberArrays[i] - 1) * horizontalSpacing;
                    }
                    if (i > 0) {
                        columnsTopArrays[i] = height + (i * verticalSpacing);
                    }
                    // 累加每行高度
                    height += columnsHeightArrays[i];
                }

                // 獲取最大寬值
                int maxColumnsWidth = columnsWidthArrays[getMaximumIndex(columnsWidthArrays)];
                // 使用最大值
                height += (lineNumber - 1) * verticalSpacing;
                width = maxColumnsWidth;
                // 清空繪製時累加計算
                columnsWidthArrays = new int[lineNumber];
                // 創建位圖
                Bitmap bitmap = Bitmap.createBitmap(width, height, config);
                Canvas canvas = new Canvas(bitmap);
                canvas.drawColor(BACKGROUND_COLOR);
                // 循環繪製
                for (int i = 0; i < itemCount; i++) {
                    // 獲取最小寬度索引
                    int minIndex = getMinimumIndex(columnsWidthArrays);
                    Matrix matrix = new Matrix();
                    matrix.postTranslate(columnsWidthArrays[minIndex], columnsTopArrays[minIndex]);
                    // 繪製到 Bitmap
                    Bitmap bmp = bitmaps[i];
                    canvas.drawBitmap(bmp, matrix, PAINT);
                    // 累加寬度
                    columnsWidthArrays[minIndex] += (itemWidthArrays[i] + horizontalSpacing);
                    // 釋放資源
                    bmp.recycle();
                    bmp = null;
                }
                return bitmap;
            }
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "snapshotByRecyclerView_StaggeredGridLayoutManager");
        }
        return null;
    }

    // ================
    // = 內部私有方法 =
    // ================

    /**
     * 繪製 Bitmap
     * @param childView {@link View}
     * @param config    {@link Bitmap.Config}
     * @return {@link Bitmap}
     */
    private static Bitmap canvasBitmap(final View childView, final Bitmap.Config config) {
        Bitmap bitmap = Bitmap.createBitmap(childView.getMeasuredWidth(), childView.getMeasuredHeight(), config);
        Canvas canvas = new Canvas(bitmap);
        canvas.drawColor(BACKGROUND_COLOR);
        childView.draw(canvas);
        return bitmap;
    }

    // ======================
    // = 其他工具類實現代碼 =
    // ======================

    // ===============
    // = ScreenUtils =
    // ===============

    /**
     * 獲取屏幕寬高
     * @return int[], 0 = 寬度, 1 = 高度
     */
    private static int[] getScreenWidthHeight() {
        try {
            WindowManager windowManager = (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE);
            if (windowManager == null) {
                DisplayMetrics displayMetrics = DevUtils.getContext().getResources().getDisplayMetrics();
                return new int[]{displayMetrics.widthPixels, displayMetrics.heightPixels};
            }
            Point point = new Point();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                windowManager.getDefaultDisplay().getRealSize(point);
            } else {
                windowManager.getDefaultDisplay().getSize(point);
            }
            return new int[]{point.x, point.y};
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "getScreenWidthHeight");
        }
        return new int[]{0, 0};
    }

    /**
     * 獲取應用區域 TitleBar 高度 ( 頂部灰色 TitleBar 高度, 沒有設置 android:theme 的 NoTitleBar 時會顯示 )
     * @param activity {@link Activity}
     * @return 應用區域 TitleBar 高度
     */
    private static int getStatusBarHeight(final Activity activity) {
        try {
            Rect rect = new Rect();
            activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
            return rect.top;
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "getStatusBarHeight");
        }
        return 0;
    }

    // ===============
    // = NumberUtils =
    // ===============

    /**
     * 獲取倍數 ( 自動補 1)
     * @param value   被除數
     * @param divisor 除數
     * @return 倍數
     */
    private static int getMultiple(final int value, final int divisor) {
        if (value <= 0 || divisor <= 0) return 0;
        if (value <= divisor) return 1;
        return (value % divisor == 0) ? (value / divisor) : (value / divisor) + 1;
    }

    // ==============
    // = ArrayUtils =
    // ==============

    /**
     * 獲取數組中最小值索引
     * @param data 數組
     * @return 最小值索引
     */
    private static int getMinimumIndex(final int[] data) {
        if (data != null) {
            int len = data.length;
            if (len > 0) {
                int index = 0;
                int temp = data[index];
                for (int i = 1; i < len; i++) {
                    int value = data[i];
                    if (value < temp) {
                        index = i;
                        temp = value;
                    }
                }
                return index;
            }
        }
        return -1;
    }

    /**
     * 獲取數組中最大值索引
     * @param data 數組
     * @return 最大值索引
     */
    private static int getMaximumIndex(final int[] data) {
        if (data != null) {
            int len = data.length;
            if (len > 0) {
                int index = 0;
                int temp = data[index];
                for (int i = 1; i < len; i++) {
                    int value = data[i];
                    if (value > temp) {
                        index = i;
                        temp = value;
                    }
                }
                return index;
            }
        }
        return -1;
    }

    // =============
    // = ViewUtils =
    // =============

    /**
     * 測量 View
     * @param view           {@link View}
     * @param specifiedWidth 指定寬度
     */
    private static void measureView(final View view, final int specifiedWidth) {
        measureView(view, specifiedWidth, 0);
    }

    /**
     * 測量 View
     * @param view            {@link View}
     * @param specifiedWidth  指定寬度
     * @param specifiedHeight 指定高度
     */
    private static void measureView(final View view, final int specifiedWidth, final int specifiedHeight) {
        try {
            ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
            // MeasureSpec
            int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            // 如果大於 0
            if (specifiedWidth > 0) {
                widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(specifiedWidth, View.MeasureSpec.EXACTLY);
            }
            // 如果大於 0
            if (specifiedHeight > 0) {
                heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(specifiedHeight, View.MeasureSpec.EXACTLY);
            }
            // 判斷是否存在自定義寬高
            if (layoutParams != null) {
                int width = layoutParams.width;
                int height = layoutParams.height;
                if (width > 0 && height > 0) {
                    widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
                    heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
                } else if (width > 0) {
                    widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
                } else if (height > 0) {
                    heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
                }
            }
            view.measure(widthMeasureSpec, heightMeasureSpec);
            view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
        } catch (Exception e) {
            LogPrintUtils.eTag(TAG, e, "measureView");
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章