思路
實際開發中經常會遇到應用內截圖的相關問題,如果是普通View
的話我們可以使用View
的繪圖快取來獲取截圖,但是RecyclerView
和ScrollView
呢就稍微有點不同了。ScrollView
還好,只有一個子View
,而RecyclerView
中會有itemView
重用的問題,只會繪製在螢幕上顯示出來的itemView
,因此我們可以依次獲取每個itemView
的檢視儲存到LruCache中,最後在進行拼裝。這種方案有個問題就是如果資料過多可能會產生OOM問題,不過我測試的幾百條item都還好,沒有遇到。思路參考自這裡
程式碼
普通View
普通View截圖就是獲取View的繪圖快取,這裡的程式碼很簡單,先看看吧:
//開啟繪圖快取
view.setDrawingCacheEnabled(true);
//這個方法可調可不調,因為在getDrawingCache()裡會自動判斷有沒有快取有沒有準備好,
//如果沒有,會自動呼叫buildDrawingCache()
view.buildDrawingCache();
//獲取繪圖快取 這裡直接建立了一個新的bitmap
//因為我們在最後需要釋放快取資源,會釋放掉快取中建立的bitmap物件
Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, 0, view.getMeasuredWidth(),
view.getMeasuredHeight());
//清理繪圖快取,釋放資源
view.destroyDrawingCache();
複製程式碼
可以看下destroyDrawingCache()
方法的原始碼:
public void destroyDrawingCache() {
if (mDrawingCache != null) {
mDrawingCache.recycle();
mDrawingCache = null;
}
if (mUnscaledDrawingCache != null) {
mUnscaledDrawingCache.recycle();
mUnscaledDrawingCache = null;
}
}
複製程式碼
如果你需要整個螢幕的截圖,使用了DecorView
來獲取截圖的話頂部會有狀態列的一條,可以通過獲取狀態列高度,在重新建立Bitmap時擷取掉。
RecyclerView截圖
RecyclerView的截圖稍微複雜些。我們可以先獲取到rv的adapter物件,通過adapter物件手動呼叫建立和繫結ViewHolder等方法,模擬rv的載入過程,再依次儲存每個itemView的繪圖快取,最後拼裝成一個完整的Bitmap。看看程式碼吧~
- 獲取Adapter,通過Adapter獲取每個itemView的繪圖快取
//獲取設定的adapter
RecyclerView.Adapter adapter = view.getAdapter();
if (adapter == null) {
return null;
}
//建立儲存截圖的bitmap
Bitmap bigBitmap = null;
//獲取item的數量
int size = adapter.getItemCount();
//recycler的完整高度 用於建立bitmap時使用
int height = 0;
//獲取最大可用記憶體
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用1/8的快取
final int cacheSize = maxMemory / 8;
//把每個item的繪圖快取儲存在LruCache中
LruCache<String, Bitmap> bitmapCache = new LruCache<>(cacheSize);
for (int i = 0; i < size; i++) {
//手動呼叫建立和繫結ViewHolder方法,
RecyclerView.ViewHolder holder = adapter.createViewHolder(view, adapter.getItemViewType(i));
adapter.onBindViewHolder(holder, i);
//測量
holder.itemView.measure(
View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
//佈局
holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(),
holder.itemView.getMeasuredHeight());
//開啟繪圖快取
holder.itemView.setDrawingCacheEnabled(true);
holder.itemView.buildDrawingCache();
Bitmap drawingCache = holder.itemView.getDrawingCache();
if (drawingCache != null) {
bitmapCache.put(String.valueOf(i), drawingCache);
}
//獲取itemView的實際高度並累加
height += holder.itemView.getMeasuredHeight();
}
複製程式碼
- 建立儲存截圖的Bitmap 把儲存的itemView繪圖快取畫上去
//根據計算出的recyclerView高度建立bitmap
bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), height, Bitmap.Config.RGB_565);
//建立一個canvas畫板
Canvas canvas = new Canvas(bigBitmap);
//獲取recyclerView的背景顏色
Drawable background = view.getBackground();
//畫出recyclerView的背景色 這裡只用了color一種 有需要也可以自己擴充套件
if (background instanceof ColorDrawable) {
ColorDrawable colorDrawable = (ColorDrawable) background;
int color = colorDrawable.getColor();
canvas.drawColor(color);
}
//當前bitmap的高度
int top = 0;
//畫筆
Paint paint = new Paint();
for (int i = 0; i < size; i++) {
Bitmap bitmap = bitmapCache.get(String.valueOf(i));
canvas.drawBitmap(bitmap, 0f, top, paint);
top += bitmap.getHeight();
}
複製程式碼
ScrollView截圖
ScrollView的截圖也比較簡單,獲取到子View的高度就好,而ScrollView只允許又一個子View。
int height = 0;
//理論上scrollView只會有一個子View啦
for (int i = 0; i < view.getChildCount(); i++) {
height += view.getChildAt(i).getHeight();
}
//建立儲存快取的bitmap
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), height, Bitmap.Config.RGB_565);
//可以簡單的把Canvas理解為一個畫板 而bitmap就是塊畫布
Canvas canvas = new Canvas(bitmap);
//獲取ScrollView的背景顏色
Drawable background = view.getBackground();
//畫出ScrollView的背景色 這裡只用了color一種 有需要也可以自己擴充套件 也可以自己直接指定一種背景色
if (background instanceof ColorDrawable) {
ColorDrawable colorDrawable = (ColorDrawable) background;
int color = colorDrawable.getColor();
canvas.drawColor(color);
}
//把view的內容都畫到指定的畫板Canvas上
view.draw(canvas);
return bitmap;
複製程式碼
關於Bitmap的recycle()
方法
很多人以為這個釋放資源的方法是必須呼叫的。其實不然,在2.3
之後可以完全交由GC管理。具體的解釋可以參見官方註釋
Free the native object associated with this bitmap, and clear the reference to the pixel data. This will not free the pixel data synchronously; it simply allows it to be garbage collected if there are no other references. The bitmap is marked as "dead", meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing. This operation cannot be reversed, so it should only be called if you are sure there are no further uses for the bitmap. This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.
或者醫生的這篇部落格Bitmap.recycle引發的血案
下載原始碼點選這裡 如有錯誤或不當的地方還請大佬們指出