正值猿宵佳节,小盆友在此祝大家新年无BUG。?

目录

一、前言

二、PorterDuffXfermode

三、实战

四、写在最后

一、前言

自定义UI中,少不了对多种图像的叠加覆盖,而需要达到预期的目的,我们便需要今天的主角XfermodeXfermode 有三个孩子,分别是:

  1. AvoidXfermode
  2. PixelXorXfermode
  3. PorterDuffXfermode

而 AvoidXfermode 和 PixelXorXfermode 已经在 API 16之后被标记为removed,所以就只剩下小儿子 PorterDuffXfermode 为我们合成图像,理所当然我们今天的重点也就在他身上。老规矩,先上几张实战图,然后开始我们今天的分享。

Xfermode 小工具

刮刮卡

心跳

二、PorterDuffXfermode

我们看以下两段源码,可知 PorterDuffXfermode 作用时通过 Paint的setXfermode 设置,而 PorterDuffXfermode 的实例化其实还需要一个参数,类型为 PorterDuff.Mode

// Paint 类
public Xfermode setXfermode(Xfermode xfermode) {int newMode = xfermode != null ? xfermode.porterDuffMode : Xfermode.DEFAULT;int curMode = mXfermode != null ? mXfermode.porterDuffMode : Xfermode.DEFAULT;if (newMode != curMode) {nSetXfermode(mNativePaint, newMode);}mXfermode = xfermode;return xfermode;
}
// PorterDuffXfermode 类
public class PorterDuffXfermode extends Xfermode {public PorterDuffXfermode(PorterDuff.Mode mode) {porterDuffMode = mode.nativeInt;}
}

所以经过上面得知,最终起作用的是 PorterDuff.Mode。进入源码,会看到以下可用的模式,这段代码是API 22 的片段,如果你在比较高的版本看的话会有些许不同,但相同模式的计算公式一样。

public enum Mode {/** [0, 0] */CLEAR       (0),/** [Sa, Sc] */SRC         (1),/** [Da, Dc] */DST         (2),/** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */SRC_OVER    (3),/** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */DST_OVER    (4),/** [Sa * Da, Sc * Da] */SRC_IN      (5),/** [Sa * Da, Sa * Dc] */DST_IN      (6),/** [Sa * (1 - Da), Sc * (1 - Da)] */SRC_OUT     (7),/** [Da * (1 - Sa), Dc * (1 - Sa)] */DST_OUT     (8),/** [Da, Sc * Da + (1 - Sa) * Dc] */SRC_ATOP    (9),/** [Sa, Sa * Dc + Sc * (1 - Da)] */DST_ATOP    (10),/** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */XOR         (11),/** [Sa + Da - Sa*Da,Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */DARKEN      (12),/** [Sa + Da - Sa*Da,Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */LIGHTEN     (13),/** [Sa * Da, Sc * Dc] */MULTIPLY    (14),/** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */SCREEN      (15),/** Saturate(S + D) */ADD         (16),OVERLAY     (17);Mode(int nativeInt) {this.nativeInt = nativeInt;}/*** @hide*/public final int nativeInt;
}

每个模式的效果是怎样的呢? 我们先看看官方给出的 Demo 图。小盆友也跟着手写了一遍,需要看源码的童鞋进传送门

但是,这个 demo 少了一样东西,那就是透明度,不能全面的体现出Xfermode的威力。所以我们需要先说明下参数的意思,然后给出我们较为全面的demo。

PorterDuff.Mode 源码中每个模式的组成都是 [xx, yy] 形式,我们拿 SRC_OUT 来举例。

/** [Sa * (1 - Da), Sc * (1 - Da)] */
SRC_OUT     (7),

“xx” 指的就是 Sa * (1 - Da),其值决定了这张合成图的透明度。而透明度的取值范围为 [0, 1]。0代表着完全透明,而1代表完全可见。

“yy” 指的就是 Sc * (1 - Da),其值决定了这张合成图的颜色值。

聪明的童鞋还会注意到 SaDaScDc这几个值。他们各自代表(结合着英文记,更容易):

  • Sa(Source Alpha):源图像的透明值;
  • Da(Destination Alpha):目标图像的透明值;
  • Sc(Source Color):源图像的色值;
  • Dc(Destination Color):目标图像的色值;

源图像目标图像 又是什么呢?记住一句话就可以,先设置的为目标图(Dst),后设置的为源图(Src)

所有的疑惑我们已经先点破,接下里就给出我们比较全面的Demo,这是小盆友以官方所示的十六种模式提供的Xfermode小工具,如果有时候拿捏不准具体使用什么模式时,可以进行加入这个工具来进行琢磨。对该小工具感兴趣的请进传送门。

接下来我们便逐个讲解模式,所使用的图片均来自 Xfermode工具 的zinc例子。

1、CLEAR

注释给出的是 [0, 0] , 透明度 为0,即完全看不见颜色 为0,即无色;
最终呈现如下图,什么都没有。

2、SRC

注释给出的是[Sa, Sc], 透明度 为Sa,即取决于源图的透明值颜色 为Sc,即取源图的色值
最终呈现如下图,因为都是取源图的值,所以最终就是显示 源图

3、DST

注释给出的是[Da, Dc],透明度 为Da,即取目标图的透明度颜色 为Dc,即取目标图的色值
最终呈现如下图,因为都取目标图的值,所以最终呈现的就是 目标图

4、SRC_OVER

注释给出的是 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] ,其实就是源图盖于目标图上,若有透明度,则会看到下一层,从名字也可以很好的记忆。

5、DST_OVER

注释给出的是 [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc],和 SRC_OVER相反目标图盖于源图上,有透明度的地方可以看到下一层

6、SRC_IN

注释给出的是 [Sa * Da, Sc * Da]

透明度为 Sa * Da,说明 透明度取决源图和目标图的各自透明度,只有两者的透明均为1时(完全可见),最终成像区域的透明才为完全可见,否则会被相应弱化。

色值为 Sc * Da,说明呈现图像 色值以源图渲染

最终呈现效果如下,成像的结果是 目标图和源图的交集

7、DST_IN

注释给出的是 [Sa * Da, Sa * Dc]

透明度为 Sa * Da,说明 透明度取决源图和目标图的各自透明度,只有两者的透明均为1时(完全可见),最终成像区域的透明才为完全可见,否则会被相应弱化。

色值为 Sa * Dc,说明呈现图像 色值以目标图渲染

最终呈现效果如下,成像的结果是 目标图和源图的交集

8、SRC_OUT

注释给出的是 [Sa * (1 - Da), Sc * (1 - Da)]

透明度为 Sa * (1 - Da),说明 透明度取决源图和目标图的透明度,值得注意的是,目标图的透明值越大,反而最终结果越弱,即目标图透明度为1的地方,则最终图像不显示该地方。目标图透明度不为1的区域,则会对最终图进行削弱透明度。目标图透明度为0的区域,则不会影响到最终图像。

色值为 Sc * (1 - Da),说明呈现图像 色值以源图渲染

最终呈现效果如下,成像的结果是 以源图为主,剔除与目标图交集的地方 (因为还受透明度影响)。

9、DST_OUT

注释给出的是 [Da * (1 - Sa), Dc * (1 - Sa)]

透明度为 Da * (1 - Sa),说明 透明度取决源图和目标图的透明度,值得注意的是,源图的透明值越大,反而最终结果越弱,即源图透明度为1的地方,则最终图像不显示该地方。源图透明度不为1的区域,则会对最终图进行削弱透明度。源图透明度为0的区域,则不会影响到最终图像。

色值为 Dc * (1 - Sa),说明呈现图像 色值以目标图渲染

最终呈现效果如下,成像的结果是 以目标图图为主,剔除与源图交集的地方 (因为还受透明度影响)。

10、SRC_ATOP

注释给出的是 [Da, Sc * Da + (1 - Sa) * Dc]

透明度为 Da,说明 最终图像的可见区域只取决于目标图像

色值 Sc * Da + (1 - Sa) * Dc,说明由 目标图和源图共同决定

最终呈现的效果如下,成像的结果是在 目标图的区域内,源图覆盖在它上面。

11、DST_ATOP

注释给出的是 [Sa, Sa * Dc + Sc * (1 - Da)]

透明度为 Sa,说明 最终图像的可见区域只取决于源图像

色值 Sa * Dc + Sc * (1 - Da),说明由 目标图和源图共同决定

最终呈现的效果如下,成像的结果是在 源图的区域内,目标图覆盖在它上面。

12、XOR

注释给出的是 [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]

透明度 Sa + Da - 2 * Sa * Da,说明 透明受源图和目标图的共同影响,当两者透明度为1时,最终此区域的透明度反而会为0。

色值 Sa * Dc + Sc * (1 - Da),说明由 目标图和源图共同决定

最终呈现的效果如下,成像的结果为 不相交的地方,以各自的图像呈现。相交的地方受两者的透明度影响

13、DARKEN

注释给出的是 [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]

透明度为 Sa + Da - Sa*Da,从公式可以知道 透明度受源图和目标图的共同影响,并且最终的透明度数值会大些或是保持原值

色值 Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc),说明由 目标图和源图共同决定

最终呈现的效果如下,成像的结果为 图像的颜色会稍微偏重些

14、LIGHTEN

注释给出的是 [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)]

透明度为 Sa + Da - Sa*Da,从公式可以知道 透明度受源图和目标图的共同影响,并且最终的透明度数值会大些或是保持原值

色值 Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc),说明由 目标图和源图共同决定

最终呈现的效果如下,成像的结果为 相交部分图像的颜色会偏亮些

15、MULTIPLY

注释给出的是 [Sa * Da, Sc * Dc],最终成像如下,与 DST_INSRC_IN 有些类似,只是以灰度显示。

16、SCREEN

注释给出的是 [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc],最终成像如下,会削弱相交部分的颜色,呈现出更为亮的色泽。

17、ADD

注释给出的是 Saturate(S + D),效果图如下

18、OVERLAY

三、实战

1、刮刮卡

(1)效果图

(2)效果分析

想必大家能看出,这里需要两层图,一层为“黑蜘蛛”的图,一层为灰色遮罩。根据我们手指的滑动轨迹“擦拭掉”该地方的灰色遮罩。最后在手指抬起时,判断被“擦拭掉”的区域是否已经超出20%,如果超出,则不再绘制遮罩,达到底层图显现的效果。

(3)具体实现

第一步,我们通过 onTouchEvent 实现记录手指滑动的轨迹。 但值得注意的是,这里做了一个小优化,使用了贝塞尔曲线,使滑动轨迹会更加的顺滑,具体代码如下

“贝塞尔曲线” 感兴趣的童鞋,可以查看小盆友的另一片文章 自带美感的贝塞尔曲线原理与实战。

public boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mPreX = event.getX();mPreY = event.getY();mPath.moveTo(mPreX, mPreY);break;case MotionEvent.ACTION_MOVE:float endX = (mPreX + event.getX()) / 2;float endY = (mPreY + event.getY()) / 2;// 此处使用贝塞尔曲线mPath.quadTo(mPreX, mPreY, endX, endY);mPreX = endX;mPreY = endY;break;case MotionEvent.ACTION_UP:post(calculatePixelsRunnable);break;}postInvalidate();return true;
}

第二步,我们需要将获取到的轨迹作用于 灰色涂层 上,达到“刮卡”效果。这里其实可以使用的模式不止一个,主要看设置的 灰色涂层手指路径 的先后顺序。我们使用的为 DST_OUT

这里值得注意的是,需要开辟一个新的图层, 以免模式效果作用到其他的图像上。具体代码如下

// 开辟新的一个图层
int layer = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);canvas.drawBitmap(mCoatingLayerBitmap, 0, 0, mPaint);
mPaint.setXfermode(mXfermode);
canvas.drawPath(mPath, mPaint);mCanvas.drawPath(mPath, mPaint);mPaint.setXfermode(null);canvas.restoreToCount(layer);

经过这两步,效果就已经达到,因为我们继承的是 ImageView ,所以 “黑蜘蛛” 图层的放入便已经实现。

第三步,自动去除 “灰色图层” 的操作,在每次手指抬起时,就会开启一个线程来计算 “灰色图层” 的像素色值,如果超过20%被擦拭,则说明可以去除该 “灰色图层”。具体代码如下:

private Runnable calculatePixelsRunnable = new Runnable() {@Overridepublic void run() {int width = getWidth();int height = getHeight();float totalPixel = width * height;int[] pixel = new int[width * height];mCoatingLayerBitmap.getPixels(pixel, 0, width, 0, 0, width, height);int cleanPixel = 0;for (int col = 0; col < height; ++col) {for (int row = 0; row < width; ++row) {if (pixel[col * width + row] == 0) {cleanPixel++;}}}float result = cleanPixel / totalPixel;if (result >= PERCENT) {isShowAll = true;postInvalidate();}}
};

核心三步便已经在以上实现,剩余的便是组装起来,这里不再过多赘述,完整代码请进传送门。

2、心跳

(1)效果图

(2)动画分析


我们借助以上小盆友手绘的一张图来讲解,绿色的心跳作为目标图,蓝色的作为源图,通过不断的增大dx的距离,从而让蓝色的源图宽度不断缩小,最终使用 DST_IN 模式合成就可以达到一点点出现的效果。

至于如何让dx一点点增大,我们使用了属性动画。这个例子比较简单,我们就不再粘贴代码。有兴趣的童鞋请进传送门。

关于 属性动画 小盆友在另一篇博客中有详细讲述其原理和应用,感兴趣的话,可以进传送门。

四、写在最后

通过Xfermode的多种模式组合可以绘制出一些酷炫的图像和效果,限制我们的永远还是我们的想象力和那懒惰的双手?。最后如果你从这篇文章有所收获,请给我个赞❤️,并关注我吧。文章中如有理解错误或是晦涩难懂的语句,请评论区留言,我们进行讨论共同进步。你的鼓励是我前进的最大动力。

图像操纵大师Xfermode讲解与实战——Android高级UI相关推荐

  1. Android 高级UI解密 (三) :Canvas裁剪 与 二维、三维Camera几何变换(图层Layer原理)

    Android的绘图机制是核心内容之一,无论是什么样的功能最终都是以图像的形式呈现给用户.因此掌握Android的绘图技巧,有助于Android理解层次的提高,在面对产品经理提出的idea时也更有底气 ...

  2. 你连《Android高级UI与FrameWork源码》都搞不懂学什么Android?还敢面试阿里P7!

    Android高级UI与FrameWork源码 重要性? 这块知识是现今使用者最多的,我们称之为Android2013~2016年的技术,但是,即使是这样的技术,Android开发者也往往因为网上Co ...

  3. Android 高级UI解密 (四) :花式玩转贝塞尔曲线(波浪、轨迹变换动画)

    讲解此UI系列必然少不了一个奇妙数学曲线-–贝塞尔曲线,它目前运用于App的范围是在太广了,最初的QQ气泡拖拽,到个人界面的波浪效果.Loading波浪效果,甚至于轨迹变化的动画都可以依赖贝塞尔曲线完 ...

  4. Android 高级UI解密 (二) :Paint滤镜 与 颜色过滤(矩阵变换)

    若是曾经查看过系统UI的源码, 会发现其中使用了一些渲染效果,例如将图片加上黑白.怀旧的效果,生活中常用的逆天美颜相机,其中的原理就是使用了滤镜效果.颜色通道过滤.若还要深究其原理组成,便涉及到了高等 ...

  5. android高级UI之贝塞尔曲线<上>---基本概念、德卡斯特里奥算法

    在上一次android高级UI之Canvas综合案例操练 - cexo - 博客园对于Android UI绘制中核心的Canvas进行了相关的学习,这块的学习也中断一年多了,既然主业是Android开 ...

  6. android炫酷动画代码,Android高级UI特效仿直播点赞动画效果

    Android高级UI特效仿直播点赞动画效果 发布时间:2020-10-02 16:06:18 来源:脚本之家 阅读:117 作者:mrr 本文给大家分享高级UI特效仿直播点赞效果-一个优美炫酷的点赞 ...

  7. Android高级UI系列教程(二)

    上期回顾 Android高级UI系列教程(一)_我想月薪过万的博客-CSDN博客https://blog.csdn.net/qq_41885673/article/details/121870917 ...

  8. android高级UI视频全套

    android高级UI视频全套 http://nez.cc/NfhYoO

  9. android高级UI之PathMeasure<一>--Path测量基础(nextContour、getPosTan、getMatrix、getSegment)

    前言: 在上一次android高级UI之贝塞尔曲线<下>--贝塞尔曲线运用:QQ消息气泡完成了对于贝塞尔曲线绘制的学习,今天准备学习UI绘制中经常会用到的跟Path相关的一些知识,也是很重 ...

最新文章

  1. javascript取得鼠标的位置
  2. 使用synchronized修饰静态方法和非静态方法有什么区别
  3. LeetCode之Maximum Depth of Binary Tree
  4. mysql建表主键_常见的MySQL命令大全second
  5. python字符串类型图解_Python基础——数据类型(图解+实例,非常详细!)
  6. python中随机生成数字生成对了是猜对了_python入门(一) 一个猜随机数小游戏...
  7. zookeeper配置文件说明
  8. HyperLPR车牌识别代码解读
  9. IDEA与SVN集成
  10. 蘑菇租房java,租房经历总结-----我是如何2天找到合适租房的(房东直租)简单粗暴...
  11. Color RGB颜色深浅的排序
  12. 北大药学院院长被指涉嫌学术造假,同组院士做出回应
  13. 联发科MT6797/Helio X20软件用户手册资料介绍
  14. githug通关部分黏贴(git代码练习)
  15. 惊呆了!无聊感可激发创造力
  16. 图解 802.11wifi协议
  17. 2015年第六届蓝桥杯C/C++程序设计本科B组省赛 星系炸弹(日期推算)
  18. 基于 WEB 的实时事件通知方式 服务器推送
  19. python入门技能树评测-积跬步以至千里
  20. 在线支付——微信支付宝二维码合一

热门文章

  1. kotlin 尾递归阶乘_Kotlin程序查找数字的阶乘
  2. 论文笔记:Very deep convolutional networks for large-scale image recognition(VGG)
  3. D3.js实现人物关系图谱有移动、拖拽、放大功能
  4. iTunes的备份路径修改的
  5. lsi 搜驱动 H755 H740P chipset
  6. 安卓毕设项目总结(二)校园订餐app
  7. Procreate绘画教程
  8. 物联网——Al二哈人脸识别
  9. python打印hello word_在屏幕上打印输出Hello World,使用的Python语句是( )_学小易找答案...
  10. windows server 2003 DCOM 10016错误事件的解决