前言:几乎忘了昨日的种种 开始又敢作梦
系列文章:
Android自定义控件三部曲文章索引:http://blog.csdn.net/harvic880925/article/details/50995268
一篇给大家讲解了有关setXfermode的几种模式,还剩最后一系列DST模式没讲,这篇文章就给大家讲讲这个模式的用法及实战
由于Mode.DST_IN的模式与SRC_IN正好是相反,所以我们利用Mode.SRC_IN实现的示例,只需要将源图像与目标图像对调就可以使用Mode.DST_IN来实现了。
所以大家自己来改造实现下《自定义控件三部曲之绘图篇(十一)——Paint之setXfermode(二)》中的图形圆角效果和图片倒影效果,这里就不再贴代码了,有困难的同学可以参考文章底部源码,源码中有实现。
这里我们就再来实现几个不一样的效果,这些效果也可以通过Mode.SRC_IN模式实现哦,大家自己可以试试
这里使用到一张图片(text_shade.png):
在这张图片中,只有文字部分是纯白色的,其它区域都是透明像素。
所以再加上我们需要自己绘制的水波纹效果的图片,这里就有两张图片了,一张是水波纹效果图,另一张是text_shade.png
那么问题来了,如果我们使用Mode.DST_IN模式的话,谁当目标图像,谁当源图像呢?
这就需要分析Mode.DST_IN模式的成像原理了,在Mode.DST_IN中,源图像所占区域计算结果图像时,相交区域显示的是DST目标图像;
所以我们要最终显示的被裁剪后的波纹图,所以DST目标图像就应该是波纹图。
所以源码如下:
public class CircleWave_DSTIN extends View { private Paint mPaint; private Path mPath; private int mItemWaveLength = 1000; private int dx; private Bitmap BmpSRC,BmpDST; public CircleWave_DSTIN(Context context, AttributeSet attrs) { super(context, attrs); mPath = new Path(); mPaint = new Paint(); mPaint.setColor(Color.GREEN); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.text_shade,null); BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888); startAnim(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); generageWavePath(); //先清空bitmap上的图像,然后再画上Path Canvas c = new Canvas(BmpDST); c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); c.drawPath(mPath,mPaint); canvas.drawBitmap(BmpSRC,0,0,mPaint); int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,0,0,mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(BmpSRC,0,0,mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerId); } /** * 生成此时的Path */ private void generageWavePath(){ mPath.reset(); int originY = BmpSRC.getHeight()/2; int halfWaveLen = mItemWaveLength/2; mPath.moveTo(-mItemWaveLength+dx,originY); for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){ mPath.rQuadTo(halfWaveLen/2,-50,halfWaveLen,0); mPath.rQuadTo(halfWaveLen/2,50,halfWaveLen,0); } mPath.lineTo(BmpSRC.getWidth(),BmpSRC.getHeight()); mPath.lineTo(0,BmpSRC.getHeight()); mPath.close(); } public void startAnim(){ ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength); animator.setDuration(2000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { dx = (int)animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); } }这里涉及到利用贝赛尔曲线生成波浪线的知识,请参考 《自定义控件三部曲之绘图篇(六)——Path之贝赛尔曲线和手势轨迹、水波纹效果 》,有关波浪线的代码就一概而过了,核心放在xfermode上。
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.text_shade,null); BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);在初始化时,生成了两张图像,正如我们所说BmpDST是要显示的波浪线,所以我们新建一个空白图像,用以画即将生成的波浪线;而BmpSRC则用来显示源图。
generageWavePath(); //先清空bitmap上的图像,然后再画上Path Canvas c = new Canvas(BmpDST); c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); c.drawPath(mPath,mPaint);这一段是将波浪线的Path画到BmpDST图像上。
c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);有关使用PorterDuff.Mode.CLEAR来清空图像的知识,后面在讲解PorterDuff.Mode.CLEAR时会细讲,这里知道即可。
c.drawPath(mPath,mPaint);在生成BmpDST之后,我们就可以使用xfermode了:
//先画上SRC图像来显示完整的文字 canvas.drawBitmap(BmpSRC,0,0,mPaint); int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,0,0,mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(BmpSRC,0,0,mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerId);有些同学可能会问,为什么在saveLayer前,先要画一遍BmpSRC呢,这是因为我们在使用Mode.DST_IN时,除了相交区域以外,其它区域都会因为有空白像素而消失不见了,如果我们不加canvas.drawBitmap(BmpSRC,0,0,mPaint);效果图将是这样的:
很明显,正规的心电图应该是利用Path把当前的实时的点连接起来,我这里只是一张图片(hearmap.png)通过使用动画来实现的
中间是一条心电图线,其余位置都是透明像素;大家先想想我们要怎么利用这张图片实现上面的动画呢?
利用Mode.DST_IN模式,由于在这个模式中,相交区域优先显示目标图像,所以我们这里需要显示心电图,所以心电图就是目标图像。
那么问题来了,源图像是啥?
由于我们需要从右向左逐渐显示心电图图像,所以我们源图像就是自建的空白图像,在这个图像中,绘制一个矩形,逐渐增大矩形的区域,即相交区域也会跟着增大,由于相交区域会显示出目标图像,显示出来的结果就是心电图的动画,代码如下:
public class HeartMap extends View { private Paint mPaint; private int mItemWaveLength = 0; private int dx=0; private Bitmap BmpSRC,BmpDST; public HeartMap(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mPaint.setColor(Color.RED); BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.heartmap,null); BmpSRC = Bitmap.createBitmap(BmpDST.getWidth(), BmpDST.getHeight(), Bitmap.Config.ARGB_8888); mItemWaveLength = BmpDST.getWidth(); startAnim(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Canvas c = new Canvas(BmpSRC); //清空bitmap c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); //画上矩形 c.drawRect(BmpDST.getWidth() - dx,0,BmpDST.getWidth(),BmpDST.getHeight(),mPaint); //模式合成 int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,0,0,mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(BmpSRC,0,0,mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerId); } public void startAnim(){ ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength); animator.setDuration(6000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { dx = (int)animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); } }这段代码难度不大,就不再讲了,我们再来举个例子。
在这里我们需要用到两张图:
一张圆形遮罩(circle_shape.png)
一张不规则的波浪图
想必到这里,可能很多同学都知道要怎么做了
就是在圆形遮罩上绘制不断移动的不规则的波浪图。
代码如下:
public class IrregularWaveView extends View { private Paint mPaint; private int mItemWaveLength = 0; private int dx=0; private Bitmap BmpSRC,BmpDST; public IrregularWaveView(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.wave_bg,null); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.circle_shape,null); mItemWaveLength = BmpDST.getWidth(); startAnim(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //先画上圆形 canvas.drawBitmap(BmpSRC,0,0,mPaint); //再画上结果 int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(BmpSRC,0,0,mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerId); } public void startAnim(){ ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength); animator.setDuration(4000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { dx = (int)animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); } }首先看动画部分:
public void startAnim(){ ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength); animator.setDuration(4000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { dx = (int)animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); }其中:
mItemWaveLength = BmpDST.getWidth();在这里生成了一个ValueAnimator动画,动画的值从0到波浪图的总长,实时的值保存在dx变量中。
protected void onDraw(Canvas canvas) { super.onDraw(canvas); //先画上圆形 canvas.drawBitmap(BmpSRC,0,0,mPaint); //再画上计算结果 int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(BmpSRC,0,0,mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerId); }首先,是画了个BmpSRC所表示的原形图:
canvas.drawBitmap(BmpSRC,0,0,mPaint);与“示例1、区域波纹”的原因一样,我们需要先画上圆形图,不然就看不出来整体的样式是什么样的。
canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint);它的意思就是截取波浪图上new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight())这个矩形位置,将其画在BmpSRC的位置:new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight())
由于在SRC中,我们知道了Mode.SRC_ATOP与MODE.SRC_IN的区别:
一般而言SRC_ATOP是可以和SRC_IN通用的,但SRC_ATOP所产生的效果图在目标图的透明度不是0或100%的时候,会比SRC_IN模式产生的图像更亮些;
我们再来对比下DST中的两个模式与SRC中的这两个模式中公式中区别:
SRC_IN: [Sa * Da, Sc * Da]
SRC_ATOP:[Da, Sc * Da + (1 - Sa) * Dc]
DST_IN:[Da * Sa , Dc * Sa ]
DST_ATOP:[Sa, Sa * Dc + Sc * (1 - Da)]
从公式中可以看到,在SRC模式中,以显示源图像为主,透明度和饱和度利用Da来调节
而在DST模式中,以显示目标图像为主,透明度和饱和度利用Sa来调节
所以Mode.DST_ATOP与Mode.DST_IN的关系也是:
一般而言DST_ATOP是可以和DST_IN通用的,但DST_ATOP所产生的效果图在源图像的透明度不是0或100%的时候,会比DST_IN模式产生的图像更亮些;
同样,大家也可以使用Mode.DST_ATOP实现上篇文章中Mode.SRC_ATOP的两个示例:圆角效果和图片倒影,这里就不再讲了
到这里有关DST相关模式都讲完了,我们总结一下:
1、DST相关模式是完全可以使用SRC对应的模式来实现的,只不过需要将目标图像和源图像对调一下即可。
2、在SRC模式中,是以显示源图像为主,通过目标图像的透明度来调节计算结果的透明度和饱和度,而在DST模式中,是以显示目标图像为主,通过源图像的透明度来调节计算结果的透明度和饱和度。
前面我们做清空图像的时候用过这个方法,从公式中可以看到,计算结果直接就是[0,0]即空像素。也就是说,源图像所在区域都会变成空像素!
这样就起到了清空源图像所在区域图像的功能了。上面示例中已经存在这个Mode的用法,这里就不再举例了。
单从示例图像中,好像是异或的功能,即将源图像中除了相交区域以外的部分做为结果。但仔细看看公式,其实并没有这么简单。
首先看公式中透明度部分:Sa + Da - Sa*Da,就是将目标图像和源图像的透明度相加,然后减去它们的乘积,所以计算结果的透明度会增大(即比目标图像和源图像都大,当其中一个图像的透明度为1时,那结果图像的透明度肯定是1)
然后再看颜色值部分:Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc);表示源图像和目标图像分别以自己的透明度的补值乘以对方的颜色值,然后相加得到结果。最后再加上Sc, Dc中的最小值。
这个模式太过复杂,在实际应用中应用也比较少,目前没想到有哪些示例,大家有用到的,可以跟我说哦。
在实际应用中,我们可以从下面三个方面来决定使用哪一个模式:
1、首先,目标图像和源图像混合,需不需要生成颜色的叠加特效,如果需要叠加特效则从颜色叠加相关模式中选择,有Mode.ADD(饱和度相加)、Mode.DARKEN(变暗),Mode.LIGHTEN(变亮)、Mode.MULTIPLY(正片叠底)、Mode.OVERLAY(叠加),Mode.SCREEN(滤色)
2、当不需要特效,而需要根据某一张图像的透明像素来裁剪时,就需要使用SRC相关模式或DST相关模式了。由于SRC相关模式与DST相关模式是相通的,唯一不同的是决定当前哪个是目标图像和源图像;
3、当需要清空图像时,使用Mode.CLEAR
这篇文章到这里就结束啦,有关xfermode的知识比较有难度,大家需要仔细揣摩下
如果本文有帮到你,记得加关注哦
源码下载地址:http://download.csdn.net/detail/harvic880925/9507446
请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/51288006 谢谢
如果你喜欢我的文章,那么你将会更喜欢我的微信公众号,将定期推送博主最新文章与收集干货分享给大家(一周一次)