Android 根据View生成图片
Android 根据View生成图片
背景
随着Android技术发展,经常有将某个界面截图保存下来分享,或者生成用户或者商品二维码图片方便加好友或者购买,所以这个功能在实际应用中非常有用。下面将重点介绍这些情况的实现方法。
实现步骤:
创建View:可以是任何View,比如TextView、ImageView,也可以是LinearLayout、ScrollView或用户自定义的控件。
将View转化成Bitmap:
- Bitmap代表一张图片,存储的是像素点,安卓中不同类型的图片如jpeg、png都可以用Bitmap表示。
- Bitmap的创建通常是使用BitmapFactory类来创建的,因为通过看Bitmap构造源码可知使用构造函数来创建Bitmap对象的操作都是再jni层来完成的。
- 位图位数越高代表其可以存储的颜色信息越多,当然图像也就越逼真,一般默认用ARGB_8888。
- 保存或分享Bitmap:通过file文件保存图片文件,注意路径会涉及到申请权限,最终的结果是生成一个图片文件。
分类
开发中,我们需要根据不同的View生成图片,本文根据不同情况的View生成图片进行了一些示例,分类如下:
- 普通View生成图片(view已经渲染加载到界面上)
- 无中生有,通过java代码创建的或者inflate创建
- WebView 生成图片
- ScrollView 生成图片
- ListView 生成图片
- RecyclerView 生成图片
代码示例
3.1 普通View,就是在文本中已经绘制到界面的View
核心代码:
// 通过走一遍ViewGroup的测量(measure),布局(layout),draw流程,把布局展示的界面画到我们准备好的bitmap上(这一过程可在非UI线程完成),再把bitmap保存在文件或显示到界面上。
// 1. 在布局中写好图片的样子,然后把布局inflate成View,当然也可以直接代码编写View,设置好里面的可变元素,如头像,昵称等;
// 2. 通过调用View的measure,layout方法使之测量出内部各控件的大小和排列好各控件;
// 3. 创建一个和View大小相同的空Bitmap,新建一个画布传入该bitamp(new Canvas(bitmap)),调用view的draw(canvas)方法,view会把图片绘制在该bitmap上;
// 4. 保存到文件或直接使用图片。
3.2 代码加载但是未显示在界面的View
有一些View,我们是通过代码加载出来的,但是没有加载界面上,我们也可以对这种View生成图片。什么?既然没有显示在界面上,那还要加载来干嘛?此言差矣,用处还是有的,YY即可。
核心代码:
代码中,我们看到,我们按下截图,inflate加载一个简单布局文件, 我们看到,里面就是一个ImagView,我们待会就是要给这个ImageView 设置一张图片,然后对这个View进行生成图片,但是注意,这个ImageView从始至终都是没有显示在界面上的, 这个ImageView并没有加载到布局。我们想直接调用正常View的生成图片方法,但是如果这样会生成图片失败。
因为刚刚inflate的View是没有经过measure和layout的,没有大小,所以我们需要指定一下大小, 有了大小,就可以生成图片了。
3.3 WebView生成图片
代码中,可以看到,就是webView加载完成后,拿到当前webView的高和宽,通过Canvas生成bitmap。
3.4 ScrollView生成图片
核心代码:
代码原理:中心思想就是传入scrollview,生成一个bitmap,和前面差不多。
3.5 ListView生成图片
核心代码:(垂直排列)
中心原理:这个listView比之前的视图要复杂一些,首先要生成一个List,遍历adapter子对象,测量每一个子对象的高度和宽度,设置view缓存后生成bitmap,并放在list里面,再通过paint把每一个bitmap画出来,放在最后生成的bitmap里面,最后清除缓存文件,避免占用内存。
3.6 RecyclerView 生成图片
核心代码:(垂直排列)
代码原理:如果是水平排列,高度累加换成宽度累加即可,和ListView生成图片总体来说差不多,个别有细微差别,主要体现在生成LruCache上,啥是LruCache?说白了就是个链表,LRU Cache 的替换原则就是将最近最少使用的内容替换掉,LruCache是个泛型类,内部采用LinkedHashMap来实现缓存机制,它提供get方法和put方法来获取缓存和添加缓存,其最重要的方法trimToSize是用来移除最少使用的缓存和使用最久的缓存,并添加最新的缓存到队列中,最后释放掉多余bitmap,但是本质都是生成一个bitmap。
还有一种方法,创建一个自定义的ItemViewBinder来绑定数据到RecyclerView的ViewHolder,使用RecyclerView的measure和layout方法来布局其子视图,创建一个足够大的Bitmap对象,并使用Canvas来绘制RecyclerView的内容。
使用这个方法,你可以调用它并传入你的RecyclerView以及期望的图片尺寸。这个方法会返回一个包含了RecyclerView内容的Bitmap对象。请注意,这种方法可能不适用于包含复杂交互或自定义绘制的RecyclerView子视图。对于那些子视图,你可能需要在绘制之前手动处理他们的绘制逻辑,具体参考第一种方法。
3.7 鉴权数据可视化
如果不是复杂的图片,直接绘制也是可以的,比如要在全屏图片上展示文字并生成一个新的图片并分享出去,图片全屏,文字居中 ,上代码:
代码原理:很简单,用paint写字,居中,用Canvas保存bitmap就可以。
总结:
- 对于简单的view生成图片,直接采用通用方式,即创建bitmap;创建Canvas;draw(canvas);
- 对于列表类型的View,处理方式有所区别,以下分别说明ScrollView,Listview,Recyclerview。
- ScrollView:三个截屏中,ScrollView最简单,因为ScrollView只有一个childView,虽然没有全部显示在界面上,但是已经全部渲染绘制,因此可以直接调用scrollView.draw(canvas)来完成图片绘制,
- 而ListView就是会回收与重用Item,并且只会绘制在屏幕上显示的ItemView,采用一个List来存储Item的视图,这个list用啥类型都可以,目的就是存储bitmap,避免oom,而在新的Android版本中,已经可以用RecyclerView来代替使用ListView的场景,相比较ListView,RecyclerView对Item View的缓存支持的更好。可以采用和ListView相同的方案,而对于RecyclerView的manager,是Layout Manager,方向是竖直,因为是针对每个item计算高度,高度累加,如果是别的类型的,需要手动算出高度,要注意如果item是个长图片,需要等待其加载完,否则会出现高度计算错误的问题,具体问题具体分析哈。
实际应用
App客户端来了需求,要进行海报图片分享,类似这样:
使用ScrollView生成后bitmap,存储也是个坑,需考虑版本兼容以及权限问题。保存图片的方式根据「版本和权限」分为两种:
Android Q(Android 10) 以上:
- 保存到应用的内部存储空间 (内部存储);
- 保存到 Android 系统设置的共享存储空间(外部储存)。
Android Q(Android 10) 以下:
- 获取外部存储目录;函数使用:getExternalStorageDirectory()。
- 获取外部存储公共目录;函数使用:getExternalStoragePublicDirectory()。
- 图片(包括照片和屏幕截图),存储在 DCIM/ 或 Pictures/ 目录。
所以,不同系统版本的手机需要做兼容,否则很容易出现保存图片不成功,crash或者各种诡异问题。