在Android UI开发中,经常会需要去展示各种各样的图片,如果只是一个新闻,一件商品的图片展示,可能产品或者美工没有什么太多的要求,默认的矩形展示就可以了。但是如果涉及到用户头像这种的,可能需要已不同于普通矩形的样式展示出来,比如说,圆角矩形,圆形等,感觉就是为了突出效果的目的呢,whatever,反正产品就要这种展示,我们苦逼的就需要去实现。
1,常规实现方式:
最最常规的实现方式,这个百度一下就能出来一坨,关键点就是使用了Paint.setXfermode()方法。Xfermode,这个合成词应该翻译成图像混合模式,就是用来指定使用Paint绘制的图像与原有的图像以怎样一个体位合成。
Xfermode有三个直接子类:
- AvoidXfermode //已被标注为废弃了
- PixelXorXfermode //已被标注为废弃了
- PorterDuffXfermode //主要使用对象
于是当前主要的设置方式就是:Paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XXX));其中的XXX有几种呢,如下:
*图像中,dest图像为蓝色的矩形,首先被绘制;src图像为黄色的原型,最后被绘制;中间一步就是在绘制src之前的设置Paint的xfermode。各种XXX代表的意思如下:
ADD:饱和相加,对图像饱和度进行相加,不常用
CLEAR:清除图像
DARKEN:变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合
DST:只显示目标图像
DST_ATOP:在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响
DST_IN:只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响
DST_OUT:只在源图像和目标图像不相交的地方绘制【目标图像】,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤
DST_OVER:将目标图像放在源图像上方
LIGHTEN:变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关
MULTIPLY:正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值
OVERLAY:叠加
SCREEN:滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖
SRC:只显示源图像
SRC_ATOP:在源图像和目标图像相交的地方绘制【源图像】,在不相交的地方绘制【目标图像】,相交处的效果受到源图像和目标图像alpha的影响
SRC_IN:只在源图像和目标图像相交的地方绘制【源图像】
SRC_OUT:只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤
SRC_OVER:将源图像放在目标图像上方
XOR:在源图像和目标图像相交的地方之外绘制它们,在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制
现在明确了XXX参数的含义,做出一个圆形的用户头像ImageView就不困难了
大体的代码就如上,首先绘制Dest(Circle),然后设置Paint,再然后使用Avatar和这个Paint进一步绘制到canvas上,就完成了用户的圆形头像功能。
要十分注意这里的绘制顺序,如果顺序颠倒了,先绘制Avatar,然后设置Paint(Mode也相应的发生变化),然后绘制Circle,就会无法达到我们需要的效果。这是为什么呢?android PorterDuffXferMode真正的效果测试集合(对比官方demo)
其文将两个图像的size相较于官方demo做了一些变化,然后就会得到一些大相径庭的效果,非常值得细细一看。至于上面这个问题,
文作者总结说:因为我们的Xfermode 叠合裁剪,都是建立在不同的层级上,重新画一个bitmap会新开一层。
第一种:先画circle 在canvas那层,再画Bitmap,新开了一层,中间镶嵌Xfermode,成功。
第二种: 先画bitmap,新开了一层,再画circle,还是在bitmap那层,中间镶嵌 Xfermode,不成功。
综上,如果改变了绘制顺序,想要成功的显示出想要的效果,就需要将绘制Circle这一步也产生一个Bitmap,然后将这个Bitmap绘制在Canvas上面。总之,就是需要把每一步都产生一个图层,这样xfermode才能在图层中间进行具体的操作。
以上,为产生圆角矩形,圆形等图像的常规步骤,一般情况下,就可以应付产品了。
当然,有一般,就会有二般的情况,而且这个二般的情况,处理起来还非常蛋疼。。。
2,一种不常规的做法:
现在的产品都趋于大同,比如说底栏tab,比如说右上角的更多button,比如说圆角矩形。然后我们的产品就想着怎么做些改变,提高安装量,提高日活,这样才能有收入。浏览器首页改版,最突出的一块就剩快链了,所以,就琢么着怎么把快链做的有点新意,更美观一些,然后就给我派了不算大也不算小的活,改版快链模块,功能上没有什么大的变化,也就是能拖动调整位置,能长按编辑,删除,添加等等。这些都还好说,但是,在UI上,产品想做点改变,原先每一个快链都是圆角矩形,竞品的也多是圆角矩形,或者矩形,而我们要跟他们不一样,最终定的方案,是做成拉美曲线的outline。
先放一张改完版之后的lame curve版本的快链,好有个直观感受:
有没有感到图片确实比圆角矩形在边缘过渡的更圆滑,顺畅一些。。。
下面就说说我苦逼实现的过程:
首先,什么是lame curve,刚开始知道方案的时候,我是一脸懵逼。后来查了查,才算了解了什么是Lame Curve
首先是lame的公式:,
这是需要我去实现的特定lame,你们感受一下
四阶方程,两个变量,当然可以使用Path,将根据方程算出的x,y值串联起来,画在canvas上面,然后设置Paint,再将快链的原始矩形图标绘制到canvas,最终完成lame 外形的快链图标。
but,数学都还给老师了,即使还记得怎么求解xy,但是需要把xy都计算出来,收敛于一个固定值,精确度的问题,再加上本身我对canvas上面的绘制就不是太熟练,所以,把这个方案当成了下下策。
另外想招,可以让美工把图标都处理好,下发给我的时候已经是拉美曲线外形的了,这样移动端也省去的计算,这种方案对像淘宝,京东首页应该非常适用,因为他们的首页固定就8-10个图标,除了点击,没有给用户提供更多的交互途径,完全可以后台做完,直接下发展示就over了。但是浏览器一类的应用,首页的快链,都是可以用户自主编辑的,他完全可以自己定义一个快速链接,URL,名称完全自定义,世界上的网站千千万,每一个网站的favicon都预处理,要累死美工。。so,这个方案也被pass了。
说句题外话,有时候当你想不到好的方案,对bug一筹莫展,找不到头绪的时候。适当的暂时放下来,搁置在一旁,忙一下其他的事情,有时候过一段时间回头看看,说不定就突然有了思路,知道bug在哪里了。
走神的时候看到圆形头像的处理过程,想到既然 Circle和Avatar都可以做成Bitmap,分别绘制在Canvas上面,lame曲线跟Circle的作用是一样的,提供一个轮廓而已,完全可以让美工给做一个lame的模板,然后在绘制Avatar。
有了思路,赶紧当下做做试试,也许冥冥中天注定,我随手拿来写demo用的图片资源成了我能实现这个功能的关键。
又but,很快就遇到了问题:
1,之前是绘制一个Circle在Canvas上面,系统有api,很方便这样做。但是如果是读取一个lame Bitmap,然后将其绘制在Canvas上,接着在绘制另一个Bitmap,此时就会抛出异常:Immutable bitmap passed to Canvas constructor,什么意思呢?就是Android中不允许对res或者网络提供的图片资源进行修改,咱们第一步将lame曲线的模板图片绘制完,在绘制其他bitmap,此时就是对lame 模板的修改,系统是不允许的,当然,这个也好规避绕过去:把他存起来,在读进来,就变成Mutable的了,哈哈哈。
转换完lame模板,剩下的步骤就是按部就班了。
当时我随便在工程资源文件里面挑了一张当做lame模板,另一张当做要做曲线变换的显示图标,观察结果发现,按照我的想法,确实两者有一部分发生了交集,模板蓝色的部分,确实显示了图标,但是透明区域还是透明的,没有显示图标,没有达到想要的效果。
第二天我让美工给我提供了一个蓝色边线,中间非透明的lame曲线的图片,继续做实验,测试各种lame模板的搭配,比如红色边界线,白色的中间填充等等。最终发现,能达到我需要的效果的lame模板必须是,边界蓝色,中间非透明黑色的,就如下图这样:
有了模板,有了原图标,那么下一步就duang的搞定了
当然,上述代码是针对我们项目做了调整和优化的,但也有优化上面的不足,具体情况具体分析吧,各位。
3,Show一下做成的成果:
拿着上面的各种模板,还有原始图标(本来想拿搜狗浏览器的图标呢,结果我们图标没有方的,只有圆的,做出来效果不明显,让我很方,只好给搜狗搜索做做硬广了 :P)
具体为什么lame模板必须是上述那样子的,我暂时还没有搞明白,
因为Canvas绘制都是直接native的方法,本人的c、c++也只是皮毛水平,就不想自虐了,有兴趣研究的,如果知道,劳烦告知我一下其中缘由,先谢谢了~~~