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 程式碼無關。