1. 程式人生 > >Android實現截圖和截長圖功能的幾種方法

Android實現截圖和截長圖功能的幾種方法

一般情況下各種型號的手機都會有自帶的截圖功能,也會有諸如“開關機鍵+音量鍵”的截圖快捷鍵,只要手機是亮屏狀態,都會將手機螢幕的可視區域(包含狀態列)全部擷取下來。

如果開發中想要調取系統的截圖功能,理論上講是可以的,需要在APK中呼叫“adb shell screencap -pfilepath” 命令,但是需要獲取root許可權,呼叫系統的隱藏API。這就很麻煩了,感興趣的可以自己研究一下,由於實踐性很差,這裡就不再贅述了。

下面說幾種常用截圖和截長圖的方法:

1、截圖:

即擷取螢幕可視區域內的內容。

(1)擷取螢幕的整個可視區域(不包含狀態列)。

    /**
     * 擷取除了導航欄之外的整個螢幕
     */
    private Bitmap screenShotWholeScreen() {
        View dView = getWindow().getDecorView();
        dView.setDrawingCacheEnabled(true);
        dView.buildDrawingCache();
        Bitmap bitmap = Bitmap.createBitmap(dView.getDrawingCache());
        return bitmap;
    }

截圖示例如下:

       

(2)擷取某個View在螢幕可視區域內的部分,在可視區域外的部分無法擷取到。

原理:利用View的快取功能。Android為了提供滑動等情況下的效能,可以為每一個View建立快取,這個快取實際上就是一個Bitmap物件。

注意:要擷取的區域一定要有背景色的設定,比如設定為白色(#ffffff),否則會出現擷取後檢視圖片黑屏,或者分享到微信(QQ、紛享銷客等)後黑屏的問題。

    /**
     * 使用View的快取功能,擷取指定區域的View
     */
    private Bitmap screenShotView(View view) {
        //開啟快取功能
        view.setDrawingCacheEnabled(true);
        //建立快取
        view.buildDrawingCache();
        //獲取快取Bitmap
        Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
        return bitmap;
    }

截圖示例如下:


2、截長圖。

比如:當LinearLayout、ScrollView、ListView、WebView的高度超出螢幕高度時,想要擷取整個LinearLayout、ScrollView、ListView、WebView的內容。

(1)LinearLayout

原理比較簡單,將LinearLayout中的子控制元件的高度一個個獲取到,然後在Canvas上利用LinearLayout的draw()方法繪製出來即可。

注意:LinearLayout容器中不能有諸如ListView、WebView這樣的高度可變的控制元件,否則請參照下面專門針對ListView、WebView的方法。

    /**
     * 擷取LinearLayout
     **/
    public static Bitmap getLinearLayoutBitmap(LinearLayout linearLayout) {
        int h = 0;
        Bitmap bitmap;
        for (int i = 0; i < linearLayout.getChildCount(); i++) {
            h += linearLayout.getChildAt(i).getHeight();
        }
        // 建立對應大小的bitmap
        bitmap = Bitmap.createBitmap(linearLayout.getWidth(), h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        linearLayout.draw(canvas);
        return bitmap;
    }

(2)ScrollView

各位比對程式碼會發現,原理及實現方式與上述的LinearLayout一模一樣。

    /**
     * 擷取scrollview的螢幕
     **/
    public static Bitmap getScrollViewBitmap(ScrollView scrollView) {
        int h = 0;
        Bitmap bitmap;
        for (int i = 0; i < scrollView.getChildCount(); i++) {
            h += scrollView.getChildAt(i).getHeight();
        }
        // 建立對應大小的bitmap
        bitmap = Bitmap.createBitmap(scrollView.getWidth(), h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        scrollView.draw(canvas);
        return bitmap;
    }

(3)ListView

網上很多帖子中提到的都是下面這個方法,原理和實現程式碼與上述的LinearLayout和ScrollView一樣,然而,實踐中發現對於ListView來說,這個方法不好用。

    /**
     * 截圖listview
     **/
    public static Bitmap getListViewBitmap(ListView listView) {
        int h = 0;
        Bitmap bitmap;
        // 獲取listView實際高度
        for (int i = 0; i < listView.getChildCount(); i++) {
            h += listView.getChildAt(i).getHeight();
        }
        Log.d(TAG, "實際高度:" + h);
        Log.d(TAG, "list 高度:" + listView.getHeight());
        // 建立對應大小的bitmap
        bitmap = Bitmap.createBitmap(listView.getWidth(), h,
                Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        listView.draw(canvas);
        return bitmap;
    }

如果ListView的高度沒有超出螢幕可視區域,這個方法可行;

如果ListView的高度沒有超出螢幕可視區域,截出來的長圖只能看到可視區域內的listitem,不可見區域的listitem由於不可見高度也是0。所以截的圖還是隻有可視區域的部分。示例如下:


這就雞肋了。

至於原因,ListView會回收和重用其item,並且只會繪製可視區域內的item,如果發生滑動,一旦之前可見的item變為不可見,就會被ListView回收,以備重用。這就是不可見區域的item呈現為白屏的原因。(當然,如果你的ListView的背景色是紅色,那就是紅屏,是綠色就是綠屏)

那可以用的ListView擷取長圖的方法呢?

下面這個就是,來源於stackoverflow,原理就是將listitem一個個繪製為小bitmap,然後再將所有小bitmap按順序組合成大bitmap,即長圖。

    /**
     * http://stackoverflow.com/questions/12742343/android-get-screenshot-of-all-listview-items
     */
    public static Bitmap shotListView(ListView listview) {

        ListAdapter adapter = listview.getAdapter();
        int itemscount = adapter.getCount();
        int allitemsheight = 0;
        List<Bitmap> bmps = new ArrayList<Bitmap>();

        for (int i = 0; i < itemscount; i++) {
            View childView = adapter.getView(i, null, listview);
            
            childView.measure(
                    View.MeasureSpec.makeMeasureSpec(listview.getWidth(), View.MeasureSpec.EXACTLY),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
            childView.setDrawingCacheEnabled(true);
            childView.buildDrawingCache();

            bmps.add(childView.getDrawingCache());
            allitemsheight += childView.getMeasuredHeight();
        }
        int w = listview.getMeasuredWidth();
        Bitmap bigbitmap = Bitmap.createBitmap(w, allitemsheight, Bitmap.Config.ARGB_8888);
        Canvas bigcanvas = new Canvas(bigbitmap);

        Paint paint = new Paint();
        int iHeight = 0;

        for (int i = 0; i < bmps.size(); i++) {
            Bitmap bmp = bmps.get(i);
            bigcanvas.drawBitmap(bmp, 0, iHeight, paint);
            iHeight += bmp.getHeight();

            bmp.recycle();
            bmp = null;
        }
        return bigbitmap;
    }

長圖示例:


(3)WebView

這裡提供兩種方法。

方法一:

    /**
     * 擷取WebView的螢幕,
     * 
     * webView.capturePicture()方法在android 4.4(19)版本被廢棄,
     * 
     * 官方建議通過onDraw(Canvas)截圖
     *
     * @param webView
     * @return
     */
    private static Bitmap captureWebView(WebView webView) {
        Picture snapShot = webView.capturePicture();
        Bitmap bitmap = Bitmap.createBitmap(snapShot.getWidth(),
                snapShot.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        snapShot.draw(canvas);
        return bitmap;
    }

方法二:

    /**
     * 擷取WebView的螢幕,
     *
     * webView.getScale()方法在android 4.4.2(17)版本被廢棄,
     *
     * 官方建議通過 onScaleChanged(WebView, float, float)回撥方法獲取縮放率
     *
     * @param webView
     * @return
     */
    private static Bitmap getWebViewBitmap(WebView webView) {
        //獲取webview縮放率
        float scale = webView.getScale();
        //得到縮放後webview內容的高度
        int webViewHeight = (int) (webView.getContentHeight() * scale);
        Bitmap bitmap = Bitmap.createBitmap(webView.getWidth(), webViewHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        //繪製
        webView.draw(canvas);
        return bitmap;
    }

兩種方法都可用,但也都有同樣的問題:

在android 5.0以下版本的系統中,截WebView長圖都可用;

在android 5.0及5.0以上版本的系統中,截出來的長圖都是不完整的,只有螢幕可視區域內的內容,可視區域外的是白屏,示例如下:


原因在於在5.0及以上版本,Android為了降低WebView的記憶體消耗,對WebView進行了優化:可視區域內的HTML頁面進行渲染,不可視區域的HTML顯示為白屏,待需要顯示時再進行渲染。

解決方法有兩個:

方法一(推薦):

在設定佈局檔案的setContentView()方法前呼叫WebView.enableSlowWholeDocumentDraw(),作用在於禁止Android對WebView進行優化。

不過,這個方法對系統相容性有要求,要求專案支援的系統最低版本為5.0,即設定minSdkVersion >= 21。

但是,設定minSdkVersion >= 21 會有很嚴重的問題,將導致系統版本為5.0以下的手機無法安裝自己的APP,要權衡。

補充:

5.0以下系統,不用出現WebView.enableSlowWholeDocumentDraw()呼叫的相容性問題,因為Android沒有對WebView進行上文提及的優化;

方法二:

設定targetSdkVersion < 21,即設定目標SDK的版本小於5.0。

但是,也會有問題,Android在5.0及5.0以後提供的新功能和新特性在APP中將不可使用,如5.0的Meterial Design、6.0的許可權管理、7.0的多視窗支援等,當然肯定也包括Android在5.0對WebView所做的效能優化。

這樣,WebView的截長圖也大功告成了:


網頁中圖片載入失敗,是測試伺服器的圖片資源發生了移動,與Android 程式碼無關。