多图像合并

用户图像一般是一张单图片,但是群图像或者聊天室图像,以及明星的饭团图像,一般是有多张单用户图像按照一定规则合成的一张聚合图像。常见的聚合效果图如下图样式:

 

当然还有五张或是更多张图像的线性和多边形聚合的方式,这里就不一一列举,那么这样的效果是怎么实现的呢?

讨论这个问题的前提是,网络download图片的部分不作讨论,假定已经熟知单用户图像的下载和展示的全过程,本文只讨论在这个基础上,如何实现这种多图像聚合的效果。

Plan A:虽然知道不是一个好的实现方案,但是一定可以实现的做法。既然可以展示单用户图像,再看需要实现的效果图,无非就是多个单用户图像的重叠效果;因此,可以通过多个Imageview的叠加,比如图3,那么就四个ImageView,在XML中把布局间距调整好即可;至于图4可能会比较复杂点,一个是布局间距需要慢慢调整,另外一点就是有一层白边的效果,这个的处理可能需要更多的依赖,比如再加一个背景View

A方案总归觉得不好,在不知道到底需要把几个用户图像聚合的时候,可能需要写很多布局,以适配需要聚合图像个数导致额差异。那么有没有更好的解决方案呢?

答案自然是有的,何不考虑下自定义View + drawBitmap,一起看看Plan B

Plan B:针对Plan A的痛点,可以考虑首先把需要聚合的用户图像先download到内存,这时就有聚合图像数量和该数量的单用户图像;预先制定图像合并规则,按照规则去创建一个特定的图像合并器Merger,合并器里面去实现多张Bitmap的重绘,之后生成一张新的Bitmap;最后把这张Bitmap填充到自定义的View里面。后文将阐述细节。

下载图片这步的核心是需要写一个同步方法,保证聚合图片都下完了才执行后续的聚合图像方法,可以用一个表示图片数量的全局变量+synchronized关键字来实现。

 

图像合并规则,会根据不同产品需求而不同,这里给出一些一些参考规则,后文的绘制就是基于这个规则。图像聚合分为两类场景,一类是线性聚合,每个图像有白边阴影效果,每两个图像有重叠效果,如效果图中的图一和图四;另外一类是平面聚合,多个图像呈现平面几何形状,三个图像就是三角形,四个图像就是正方形等,如效果图中的图二和图三。

说完规则,不急于说合并器Merger,先说下数学。考虑平面聚合里面的图二,三张图片合并成一张图片,它们三个的位置关系是三张图片的圆心构成了一个正三角形,如下图所示:

 

上图中的R是布局XML中设定的这个自定义View的宽和高,d是单张用户图像的直径,ABC是三个圆形。考虑绘制Bitmap,需要知道它的leftrighttop,和bottom,所以接下来需要计算出每张图像的具体位置。图片A(这里假定圆心为A的图片就叫图片A)在整个View的相对位置,可以根据几何知识来计算,图A的左侧位置就是圆心位置减掉半径,右侧是圆心加上半径,顶部就是View的顶部所以为0,底部就是圆的直径,而圆心就是整个View的宽度的一半。

 

同理可以计算出图B和图C的位置。

 

说完平面聚合,再说下线性聚合。以效果图里面的图四为例,四张图片合并成一张一排有重叠的图片,它们的圆心在一条直线上,如下图所示:

 

上图中,有四张图片ABCD,圆心也就是ABCD,考虑有白边效果,内侧直径为d,也就是真实显示用户图像区域,外侧直径为D,白边宽度就是外侧直径减掉内侧直径的一半;而每幅图像的重叠部分是指外侧直径这个大圆的重合部分,长度为overlap

合并的时候,首先需要计算出合并后整张图片的宽和高,高比较直观,就是外侧直径,宽可以根据几何知识,是n张图片的外侧直径减掉(n-1)个重叠部分的长度。

其次是计算出每一张图片的位置,从图可以发现规律,每张图片的左边都是外侧圆直径减掉重叠部分,右侧就是左边加上外侧直径,顶部就是View的头所以就是0,底部就是顶部加上外侧直径,所以比较好计算。至于白边效果,可以通过drawBitmap里面的设置stroke来达到效果。

 

有了计算公式,接下来就是写代码实现一个合并器Merger。定义一个抽象类BitmapMerger,里面只有一个待实现的抽象方法mergeBitmap,传入的是下载好的Bitmap数组,返回的是一个合并完成的Bitmap

 

根据合并规则,所以有两个子类,一个是线性聚合子类,一个是平面聚合子类,在子类里面去实现具体的聚合方法。以线性聚合子类为例,在构造函数的时候,传入聚合需要的参数,如内外侧圆的直径和重叠部分的长度;需要具体实现父类里的mergeBitmap方法,具体实现:首先创建计算好的合并后Bitmap宽度和高度的Bitmap,之后把需要聚合的Bitmap数组里面Bitmap取出来,依次绘制,考虑效果需要第一张图片盖在第二张图片上,所以应该是从数组的最后一张开始绘制,最后绘制的是第一张图片。

 

这里有一个核心方法,就是drawCircleImageWithStroke方法,它实现了Bitmap的真实绘制过程,本质是利用CanvasdrawCircle方法来实现。具体的代码如下:

 

在合并图像的时候,可以利用简单工厂模式,根据需要合并的规则,去创建不同的合并器BitmapMerger,然后来聚合图像,补充完善之前遗留的todo

 

还有最后一部分,就是承载这个视图的自定义View的实现。自定义View的实现比较简单,这里就不赘述。说核心的细节点,就是需要合并4张图片,但是网络比较慢,需要先展示一张默认图,等图片下载完成,合并完成,之后再刷XUI,这里的观察者模式也比较常见也不是我说的重点,重点是因为考虑内存效率,默认图最好用一张图展示,而不是也用4张默认图合并成一张,这样会浪费资源,这里可能就会引入的问题就是本来应该展示4张图合并后的Bitmap的地方只放一张默认图,会导致默认图尺寸问题,所以需要写一个方法,对默认图做适配处理。

 

这里提供了一个通过矩阵重新计算默认图尺寸的方案,仅供参考。

至此,整个实现过程已经讲完,还参入部分设计模式,方便扩展,如果需要引入其他的合并规则,可以直接写一个具体的BitmpaMerger子类,这样在不破坏整体架构基础之上,实现需求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章