01
背景
随着Android技术发展,经常有将某个界面截图保存下来分享,或者生成用户或者商品二维码图片方便加好友或者购买,所以这个功能在实际应用中非常有用,下面我将重点针对这些情况进行讲解,废话不多说了,先画个序列图,如下:
序列图
实现步骤:
步骤一:创建View;
首先,我们需要创建一个View,这个View可以是任何view,比如一个TextView或者一个Image View,也可以是LinearLayout或者ScrollView,或者用户自定义的控件,这个没有要求。
步骤二:将View转化成Bitmap;
概念一,Bitmap代表一张图片,其存储的是像素点,安卓中不同类型的图片如jpeg,png都可以用Bitmap表示。安卓中对图片的裁剪、缩放等一系列的操作都需要把图片文件以Bitmap的形式加载到内存中进行操作。Bitmap的创建通常是使用BitmapFactory类来创建的,因为通过看Bitmap构造源码可知使用构造函数来创建Bitmap对象的操作都是再jni层来完成的。我们只需使用BitmapFactory即可创建出Bitmap对象。具体Bitmap的构造和内部方法大家可以自定查阅api进行学习,就不多说了。
ALPHA_8——代表8位Alpha位图(只有A通道)
ARGB_4444——代表16位ARGB位图
ARGB_8888——代表32位ARGB位图
RGB_565——代表16位RGB位图(没有A通道)
位图位数越高代表其可以存储的颜色信息越多,当然图像也就越逼真,一般默认用8888。
Canvas是Android图形系统中一个核心的类,它允许你在屏幕上绘制图形,文字等。Canvas对象本身并没有实际的绘图能力,它需要与bitmap或者其他的drawable关联。Canvas的工作原理是:你创建一个bitmap,并且为它创建一个Canvas对象,然后你可以在这个bitmap上绘制你想要的任何东西,功能强大,具体的功能小伙伴可以自行学习哈。
步骤三:保存或分享Bitmap。
通过file文件保存图片文件,注意路径会涉及到申请权限,最终的结果是生成一个图片文件。
02
分类
开发中,我们需要根据不同的View生成图片,本文根据不同情况的View生成图片进行了一些示例,分类如下:
第一种,普通View生成图片(view已经渲染加载到界面上);
第二种,无中生有,通过java代码创建的或者inflate创建;
第三种,WebView 生成图片;
第四种,ScrollView 生成图片;
第五种,ListView 生成图片;
第六种,RecyclerView 生成图片。
废话不多说,直接上视图和代码~
03
代码
核心代码:
原理说明:
通过走一遍ViewGroup的测量(measure),布局(layout),draw流程,把布局展示的界面画到我们准备好的bitmap上(这一过程可在非UI线程完成),再把bitmap保存在文件或显示到界面上。
有一些View,我们是通过代码加载出来的,但是没有加载界面上,我们也可以对这种View生成图片。什么?既然没有显示在界面上,那还要加载来干嘛?此言差矣,用处还是有的,YY即可。
核心代码:
代码中,我们看到,我们按下截图,inflate加载一个简单布局文件, 我们看到,里面就是一个ImagView,我们待会就是要给这个ImageView 设置一张图片,然后对这个View进行生成图片,但是注意,这个ImageView从始至终都是没有显示在界面上的, 这个ImageView并没有加载到布局。我们想直接调用正常View的生成图片方法,但是如果这样会生成图片失败。
因为刚刚inflate的View是没有经过measure和layout的,没有大小,所以我们需要指定一下大小, 有了大小,就可以生成图片了。
代码中,可以看到,就是webView加载完成后,拿到当前webView的高和宽,通过Canvas生成bitmap。
核心代码:
代码原理:中心思想就是传入scrollview,生成一个bitmap,和前面差不多。
核心代码:(垂直排列)
中心原理:这个listView比之前的视图要复杂一些,首先要生成一个List
核心代码:(垂直排列)
代码原理:如果是水平排列,高度累加换成宽度累加即可,和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子视图。对于那些子视图,你可能需要在绘制之前手动处理他们的绘制逻辑,具体参考第一种方法。
如果不是复杂的图片,直接绘制也是可以的,比如要在全屏图片上展示文字并生成一个新的图片并分享出去,图片全屏,文字居中 ,上代码:
代码原理:很简单,用paint写字,居中,用Canvas保存bitmap就可以。
总结:
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是个长图片,需要等待其加载完,否则会出现高度计算错误的问题,具体问题具体分析哈。
04
实际应用
App客户端来了需求,要进行海报图片分享,类似这样:
使用ScrollView生成后bitmap,存储也是个坑,需考虑版本兼容以及权限问题。保存图片的方式根据「版本和权限」分为两种:
Android Q(Android 10) 以上:
1. 保存到应用的内部存储空间 (内部存储);
2. 保存到 Android 系统设置的共享存储空间(外部储存)。
Android Q(Android 10) 以下:
1. 获取外部存储目录;
函数使用:getExternalStorageDirectory()。
2. 获取外部存储公共目录;
函数使用:getExternalStoragePublicDirectory()。
3. 图片(包括照片和屏幕截图),存储在 DCIM/ 或 Pictures/ 目录。
所以,不同系统版本的手机需要做兼容,否则很容易出现保存图片不成功,crash或者各种诡异问题。
参考文献:
https://m.xp.cn/b.php/99754.html
https://www.cnblogs.com/lenkevin/p/8143901.html
https://zhuanlan.zhihu.com/p/43572827