Android 普通View截图 RecyclerView截图 ScrollView截图分享

4,767 阅读4分钟

思路

实际开发中经常会遇到应用内截图的相关问题,如果是普通View的话我们可以使用View的绘图缓存来获取截图,但是RecyclerViewScrollView呢就稍微有点不同了。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。看看代码吧~

  1. 获取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();
       }
  1. 创建保存截图的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引发的血案

下载源码点击这里 如有错误或不当的地方还请大佬们指出