您的位置:首页 > Web前端

PorterDuffXfermode使用

2015-11-03 09:47 302 查看
尊重原创转载请注明:From AigeStudio(http://blog.csdn.net/aigestudio)Power
by Aige

该类同样有且只有一个含参的构造方法PorterDuffXfermode(PorterDuff.Mode mode),这个PorterDuff.Mode大家看后是否会有些面熟,它跟上面我们讲ColorFilter时候用到的PorterDuff.Mode是一样的!麻雀虽小五脏俱全,虽说构造方法的签名列表里只有一个PorterDuff.Mode的参数,但是它可以实现很多酷毙的图形效果!!而PorterDuffXfermode就是图形混合模式的意思,其概念最早来自于SIGGRAPH的Tomas
Proter和Tom Duff,混合图形的概念极大地推动了图形图像学的发展,延伸到计算机图形图像学像Adobe和AutoDesk公司著名的多款设计软件都可以说一定程度上受到影响,而我们PorterDuffXfermode的名字也来源于这俩人的人名组合PorterDuff,那PorterDuffXfermode能做些什么呢?我们先来看一张API DEMO里的图片:



这张图片从一定程度上形象地说明了图形混合的作用,两个图形一圆一方通过一定的计算产生不同的组合效果,在API中Android为我们提供了18种(比上图多了两种ADD和OVERLAY)模式:



来定义不同的混合效果,这18种模式Android还为我们提供了它们的计算方式比如LIGHTEN的计算方式为[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)],其中Sa全称为Source alpha表示源图的Alpha通道;Sc全称为Source color表示源图的颜色;Da全称为Destination alpha表示目标图的Alpha通道;Dc全称为Destination color表示目标图的颜色,细心的朋友会发现“[……]”里分为两部分,其中“,”前的部分为“Sa
+ Da - Sa*Da”这一部分的值代表计算后的Alpha通道而“,”后的部分为“Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)”这一部分的值代表计算后的颜色值,图形混合后的图片依靠这个矢量来计算ARGB的值,如果大家感兴趣可以查看维基百科中对Alpha合成的解释:http://en.wikipedia.org/wiki/Alpha_compositing。作为一个猿,我们不需要知道复杂的图形学计算但是一定要知道这些模式会为我们提供怎样的效果,当大家看到上面API DEMO给出的效果时一定会觉得PorterDuffXfermode其实就是简单的图形交并集计算,比如重叠的部分删掉或者叠加等等,事实上呢!PorterDuffXfermode的计算绝非是根据于此!上面我们也说了PorterDuffXfermode的计算是要根据具体的Alpha值和RGB值的,既然如此,我们就来看一个比API
DEMO稍微复杂的例子来更有力地说明PorterDuffXfermode是如何工作而我们又能用它做些什么,在这个例子中我将用到两个带有Alpha通道的渐变图形Bitmap:



我们将在不同的模式下混合这两个Bitmap来看看这两个渐变色的颜色值在不同的混合模式下究竟发生了什么?先看看我们的测试代码:

[java] view
plaincopyprint?

@TargetApi(Build.VERSION_CODES.HONEYCOMB)

public class PorterDuffView extends View {

/*

* PorterDuff模式常量

* 可以在此更改不同的模式测试

*/

private static final PorterDuff.Mode MODE = PorterDuff.Mode.ADD;

private static final int RECT_SIZE_SMALL = 400;// 左右上方示例渐变正方形的尺寸大小

private static final int RECT_SIZE_BIG = 800;// 中间测试渐变正方形的尺寸大小

private Paint mPaint;// 画笔

private PorterDuffBO porterDuffBO;// PorterDuffView类的业务对象

private PorterDuffXfermode porterDuffXfermode;// 图形混合模式

private int screenW, screenH;// 屏幕尺寸

private int s_l, s_t;// 左上方正方形的原点坐标

private int d_l, d_t;// 右上方正方形的原点坐标

private int rectX, rectY;// 中间正方形的原点坐标

public PorterDuffView(Context context, AttributeSet attrs) {

super(context, attrs);

// 实例化画笔并设置抗锯齿

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

// 实例化业务对象

porterDuffBO = new PorterDuffBO();

// 实例化混合模式

porterDuffXfermode = new PorterDuffXfermode(MODE);

// 计算坐标

calu(context);

}

/**

* 计算坐标

*

* @param context

* 上下文环境引用

*/

private void calu(Context context) {

// 获取包含屏幕尺寸的数组

int[] screenSize = MeasureUtil.getScreenSize((Activity) context);

// 获取屏幕尺寸

screenW = screenSize[0];

screenH = screenSize[1];

// 计算左上方正方形原点坐标

s_l = 0;

s_t = 0;

// 计算右上方正方形原点坐标

d_l = screenW - RECT_SIZE_SMALL;

d_t = 0;

// 计算中间方正方形原点坐标

rectX = screenW / 2 - RECT_SIZE_BIG / 2;

rectY = RECT_SIZE_SMALL + (screenH - RECT_SIZE_SMALL) / 2 - RECT_SIZE_BIG / 2;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

// 设置画布颜色为黑色以便我们更好地观察

canvas.drawColor(Color.BLACK);

// 设置业务对象尺寸值计算生成左右上方的渐变方形

porterDuffBO.setSize(RECT_SIZE_SMALL);

/*

* 画出左右上方两个正方形

* 其中左边的的为src右边的为dis

*/

canvas.drawBitmap(porterDuffBO.initSrcBitmap(), s_l, s_t, mPaint);

canvas.drawBitmap(porterDuffBO.initDisBitmap(), d_l, d_t, mPaint);

/*

* 将绘制操作保存到新的图层(更官方的说法应该是离屏缓存)我们将在1/3中学习到Canvas的全部用法这里就先follow me

*/

int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG);

// 重新设置业务对象尺寸值计算生成中间的渐变方形

porterDuffBO.setSize(RECT_SIZE_BIG);

// 先绘制dis目标图

canvas.drawBitmap(porterDuffBO.initDisBitmap(), rectX, rectY, mPaint);

// 设置混合模式

mPaint.setXfermode(porterDuffXfermode);

// 再绘制src源图

canvas.drawBitmap(porterDuffBO.initSrcBitmap(), rectX, rectY, mPaint);

// 还原混合模式

mPaint.setXfermode(null);

// 还原画布

canvas.restoreToCount(sc);

}

}

代码中我们使用到了View的离屏缓冲,也通俗地称之为层,这个概念很简单,我们在绘图的时候新建一个“层”,所有的绘制操作都在该层上而不影响该层以外的图像,比如代码中我们在绘制了画布颜色和左右上方两个方形后就新建了一个图层来绘制中间的大正方形,这个方形和左右上方的方形是在两个不同的层上的:



注:图中所显示色彩效果与我们的代码不同,上图只为演示图层概念

当我们绘制完成后要通过restore将所有缓冲(层)中的绘制操作还原到画布以结束绘制,具体关于画布的知识在自定义控件其实很简单1/3,这里就不多说了,下面我们看具体各种模式的计算效果

PS:Src为源图像,意为将要绘制的图像;Dis为目标图像,意为我们将要把源图像绘制到的图像……是不是感脚很拗口 = = !Fuck……意会意会~~

PorterDuff.Mode.ADD

计算方式:Saturate(S + D);Chinese:饱和相加



从计算方式和显示的结果我们可以看到,ADD模式简单来说就是对图像饱和度进行相加,这个模式在应用中不常用,我唯一一次使用它是通过代码控制RGB通道的融合生成图片。

PorterDuff.Mode.CLEAR

计算方式:[0, 0];Chinese:清除

清除图像,很好理解不扯了。

PorterDuff.Mode.DARKEN

计算方式:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)];Chinese:变暗



这个模式计算方式目测很复杂,其实效果很好理解,两个图像混合,较深的颜色总是会覆盖较浅的颜色,如果两者深浅相同则混合,如图,黄色覆盖了红色而蓝色和青色因为是跟透明混合所以不变。细心的朋友会发现青色和黄色之间有一层类似橙色的过渡色,这就是混合的结果。在实际的测试中源图和目标图的DARKEN混合偶尔会有相反的结果比如红色覆盖了黄色,这源于Android对颜色值“深浅”的定义,我暂时没有在官方查到有关资料不知道是否与图形图像学一致。DARKEN模式的应用在图像色彩方面比较广泛我们可以利用其特性来获得不同的成像效果,这点与之前介绍的ColorFilter有点类似。

PorterDuff.Mode.DST

计算方式:[Da, Dc];Chinese:只绘制目标图像



如Chinese所说,很好理解。

PorterDuff.Mode.DST_ATOP

计算方式:[Sa, Sa * Dc + Sc * (1 - Da)];Chinese:在源图像和目标图像相交的地方绘制目标图像而在不相交的地方绘制源图像



PorterDuff.Mode.DST_IN

计算方式:[Sa * Da, Sa * Dc];Chinese:只在源图像和目标图像相交的地方绘制目标图像



最常见的应用就是蒙板绘制,利用源图作为蒙板“抠出”目标图上的图像,这里我讲一个很简单的例子,如果大家用过PS就很容易理解,我这里有两张图:



一张是一个很漂亮的手绘古典美女:



而另一张是一张只有黑色和透明通道的遮罩图:



我们把这张美女图画在我们的屏幕上:

[java] view
plaincopyprint?

public class DisInView extends View {

private Paint mPaint;// 画笔

private Bitmap bitmapDis;// 位图

private int x, y;// 位图绘制时左上角的起点坐标

private int screenW, screenH;// 屏幕尺寸

public DisInView(Context context) {

this(context, null);

}

public DisInView(Context context, AttributeSet attrs) {

super(context, attrs);

// 初始化画笔

initPaint();

// 初始化资源

initRes(context);

}

/**

* 初始化画笔

*/

private void initPaint() {

// 实例化画笔

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

}

/**

* 初始化资源

*/

private void initRes(Context context) {

// 获取位图

bitmapDis = BitmapFactory.decodeResource(context.getResources(), R.drawable.a3);

// 获取包含屏幕尺寸的数组

int[] screenSize = MeasureUtil.getScreenSize((Activity) context);

// 获取屏幕尺寸

screenW = screenSize[0];

screenH = screenSize[1];

/*

* 计算位图绘制时左上角的坐标使其位于屏幕中心

* 屏幕坐标x轴向左偏移位图一半的宽度

* 屏幕坐标y轴向上偏移位图一半的高度

*/

x = screenW / 2 - bitmapDis.getWidth() / 2;

y = screenH / 2 - bitmapDis.getHeight() / 2;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

// 绘制美女图

canvas.drawBitmap(bitmapDis, x, y, mPaint);

}

}

运行后如下:



美女脑袋上有个文字标识巨恶心而且因为图片画质问题美图周围还有一片淡黄色的不好看,那我们就通过刚才那个黑色的透明通道图把美女“抠”出来:

[java] view
plaincopyprint?

public class DisInView extends View {

private Paint mPaint;// 画笔

private Bitmap bitmapDis, bitmapSrc;// 位图

private PorterDuffXfermode porterDuffXfermode;// 图形混合模式

private int x, y;// 位图绘制时左上角的起点坐标

private int screenW, screenH;// 屏幕尺寸

public DisInView(Context context) {

this(context, null);

}

public DisInView(Context context, AttributeSet attrs) {

super(context, attrs);

// 实例化混合模式

porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);

// 初始化画笔

initPaint();

// 初始化资源

initRes(context);

}

/**

* 初始化画笔

*/

private void initPaint() {

// 实例化画笔

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

}

/**

* 初始化资源

*/

private void initRes(Context context) {

// 获取位图

bitmapDis = BitmapFactory.decodeResource(context.getResources(), R.drawable.a3);

bitmapSrc = BitmapFactory.decodeResource(context.getResources(), R.drawable.a3_mask);

// 获取包含屏幕尺寸的数组

int[] screenSize = MeasureUtil.getScreenSize((Activity) context);

// 获取屏幕尺寸

screenW = screenSize[0];

screenH = screenSize[1];

/*

* 计算位图绘制时左上角的坐标使其位于屏幕中心

* 屏幕坐标x轴向左偏移位图一半的宽度

* 屏幕坐标y轴向上偏移位图一半的高度

*/

x = screenW / 2 - bitmapDis.getWidth() / 2;

y = screenH / 2 - bitmapDis.getHeight() / 2;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawColor(Color.WHITE);

/*

* 将绘制操作保存到新的图层(更官方的说法应该是离屏缓存)我们将在1/3中学习到Canvas的全部用法这里就先follow me

*/

int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG);

// 先绘制dis目标图

canvas.drawBitmap(bitmapDis, x, y, mPaint);

// 设置混合模式

mPaint.setXfermode(porterDuffXfermode);

// 再绘制src源图

canvas.drawBitmap(bitmapSrc, x, y, mPaint);

// 还原混合模式

mPaint.setXfermode(null);

// 还原画布

canvas.restoreToCount(sc);

}

}

看!只剩米女了~~~~:



当然该混合模式的用法绝不止这么简单,这只是阐述了一个原理,更棒的用法就看你怎么用了~~~~

PorterDuff.Mode.DST_OUT

计算方式:[Da * (1 - Sa), Dc * (1 - Sa)];Chinese:只在源图像和目标图像不相交的地方绘制目标图像



上面那个例子呢我们把米女抠了出来,而这次我们将从一个色块中把米女的轮廓挖出来~~~~啦啦啦:

[java] view
plaincopyprint?

public class DisOutView extends View {

private Paint mPaint;// 画笔

private Bitmap bitmapSrc;// 位图

private PorterDuffXfermode porterDuffXfermode;// 图形混合模式

private int x, y;// 位图绘制时左上角的起点坐标

private int screenW, screenH;// 屏幕尺寸

public DisOutView(Context context) {

this(context, null);

}

public DisOutView(Context context, AttributeSet attrs) {

super(context, attrs);

// 实例化混合模式

porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);

// 初始化画笔

initPaint();

// 初始化资源

initRes(context);

}

/**

* 初始化画笔

*/

private void initPaint() {

// 实例化画笔

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

}

/**

* 初始化资源

*/

private void initRes(Context context) {

// 获取位图

bitmapSrc = BitmapFactory.decodeResource(context.getResources(), R.drawable.a3_mask);

// 获取包含屏幕尺寸的数组

int[] screenSize = MeasureUtil.getScreenSize((Activity) context);

// 获取屏幕尺寸

screenW = screenSize[0];

screenH = screenSize[1];

/*

* 计算位图绘制时左上角的坐标使其位于屏幕中心

* 屏幕坐标x轴向左偏移位图一半的宽度

* 屏幕坐标y轴向上偏移位图一半的高度

*/

x = screenW / 2 - bitmapSrc.getWidth() / 2;

y = screenH / 2 - bitmapSrc.getHeight() / 2;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawColor(Color.WHITE);

/*

* 将绘制操作保存到新的图层(更官方的说法应该是离屏缓存)我们将在1/3中学习到Canvas的全部用法这里就先follow me

*/

int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG);

// 先绘制一层颜色

canvas.drawColor(0xFF8f66DA);

// 设置混合模式

mPaint.setXfermode(porterDuffXfermode);

// 再绘制src源图

canvas.drawBitmap(bitmapSrc, x, y, mPaint);

// 还原混合模式

mPaint.setXfermode(null);

// 还原画布

canvas.restoreToCount(sc);

}

}

看看美女那动人的轮廓~~~~~么么哒:



PorterDuff.Mode.DST_OVER

计算方式:[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc];Chinese:在源图像的上方绘制目标图像



这个就不说啦,就是两个图片谁在上谁在下的意思

PorterDuff.Mode.LIGHTEN

计算方式:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)];Chinese:变亮



与DARKEN相反,不多说了

PorterDuff.Mode.MULTIPLY

计算方式:[Sa * Da, Sc * Dc];Chinese:正片叠底



该模式通俗的计算方式很简单,源图像素颜色值乘以目标图像素颜色值除以255即得混合后图像像素的颜色值,该模式在设计领域应用广泛,因为其特性黑色与任何颜色混合都会得黑色,在手绘的上色、三维动画的UV贴图绘制都有应用,具体效果大家自己尝试我就不说了

PorterDuff.Mode.OVERLAY

计算方式:未给出;Chinese:叠加



这个模式没有在官方的API DEMO中给出,谷歌也没有给出其计算方式,在实际效果中其对亮色和暗色不起作用,也就是说黑白色无效,它会将源色与目标色混合产生一种中间色,这种中间色生成的规律也很简单,如果源色比目标色暗,那么让目标色的颜色倍增否则颜色递减。

PorterDuff.Mode.SCREEN

计算方式:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc];Chinese:滤色



计算方式我不解释了,滤色产生的效果我认为是Android提供的几个色彩混合模式中最好的,它可以让图像焦媃幻化,有一种色调均和的感觉:

[java] view
plaincopyprint?

public class ScreenView extends View {

private Paint mPaint;// 画笔

private Bitmap bitmapSrc;// 位图

private PorterDuffXfermode porterDuffXfermode;// 图形混合模式

private int x, y;// 位图绘制时左上角的起点坐标

private int screenW, screenH;// 屏幕尺寸

public ScreenView(Context context) {

this(context, null);

}

public ScreenView(Context context, AttributeSet attrs) {

super(context, attrs);

// 实例化混合模式

porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SCREEN);

// 初始化画笔

initPaint();

// 初始化资源

initRes(context);

}

/**

* 初始化画笔

*/

private void initPaint() {

// 实例化画笔

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

}

/**

* 初始化资源

*/

private void initRes(Context context) {

// 获取位图

bitmapSrc = BitmapFactory.decodeResource(context.getResources(), R.drawable.a3);

// 获取包含屏幕尺寸的数组

int[] screenSize = MeasureUtil.getScreenSize((Activity) context);

// 获取屏幕尺寸

screenW = screenSize[0];

screenH = screenSize[1];

/*

* 计算位图绘制时左上角的坐标使其位于屏幕中心

* 屏幕坐标x轴向左偏移位图一半的宽度

* 屏幕坐标y轴向上偏移位图一半的高度

*/

x = screenW / 2 - bitmapSrc.getWidth() / 2;

y = screenH / 2 - bitmapSrc.getHeight() / 2;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawColor(Color.WHITE);

/*

* 将绘制操作保存到新的图层(更官方的说法应该是离屏缓存)我们将在1/3中学习到Canvas的全部用法这里就先follow me

*/

int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG);

// 先绘制一层带透明度的颜色

canvas.drawColor(0xcc1c093e);

// 设置混合模式

mPaint.setXfermode(porterDuffXfermode);

// 再绘制src源图

canvas.drawBitmap(bitmapSrc, x, y, mPaint);

// 还原混合模式

mPaint.setXfermode(null);

// 还原画布

canvas.restoreToCount(sc);

}

}

它比原图多一层蓝紫色调给人感觉更古典~~



PorterDuff.Mode.SRC

计算方式:[Sa, Sc];Chinese:显示源图

只绘制源图,SRC类的模式跟DIS的其实差不多就不多说了,大家多动手自己试试,我已经写不动了快………………………………………………………………………………………………………………

PorterDuff.Mode.SRC_ATOP

计算方式:[Da, Sc * Da + (1 - Sa) * Dc];Chinese:在源图像和目标图像相交的地方绘制源图像,在不相交的地方绘制目标图像



PorterDuff.Mode.SRC_IN

计算方式:[Sa * Da, Sc * Da];Chinese:只在源图像和目标图像相交的地方绘制源图像



PorterDuff.Mode.SRC_OUT

计算方式:[Sa * (1 - Da), Sc * (1 - Da)];Chinese:只在源图像和目标图像不相交的地方绘制源图像



PorterDuff.Mode.SRC_OVER

计算方式:[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc];Chinese:在目标图像的顶部绘制源图像



PorterDuff.Mode.XOR

计算方式:[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc];Chinese:在源图像和目标图像重叠之外的任何地方绘制他们,而在不重叠的地方不绘制任何内容



XOR我们在将PixelXorXfermode的时候提到过,不了解的话上去看看~~~实在写不动了

那么这些混合模式究竟有什么用?能够给我们带来什么好处呢?假设我们要画一个闹钟,如下:



注:该闹钟图标为已投入运行项目文件并已有商标,请大家不要以任何盈利手段为目的盗用,当然做练习是没问题的

构思一下怎么做,我们需要画一个圆圈做钟体,两个Path(Path为路径,我们将会在1/3详细学习到,这里就先follow me)作为指针,问题是两个铃铛~~~~如果我们不会混合模式,一定会想怎么计算坐标啊去绘制曲线然后闭合然后填充啊之类………………实际上有必要吗?这个闹铃不就是一个小圆再用一个大圆去遮罩吗:



问题是不是一下子就变简单了?如果等你去计算怎么画路径怎么闭合曲线填充颜色还有多屏幕的匹配………………哥已经死了又活过来又死了…………在学完1/2的View尺寸计算和布局后我会教大家如何做类似的View并匹配在所有的屏幕上~~~~这里就先缓一缓。大家一定要有这样的思维,当你想要去画一个View的时候一定要想想看这个View的图形是不是可以通过基本的几何图形混合来生成,如果可以,那么恭喜你,使用PorterDuffXfermode的混合模式你可以事半功倍!

PorterDuffXfermode的另一个比较常见的应用就是橡皮檫效果,我们可以通过手指不断地触摸屏幕绘制Path,再以Path作遮罩遮掉填充的色块显示下层的图像:

[java] view
plaincopyprint?

public class EraserView extends View {

private static final int MIN_MOVE_DIS = 5;// 最小的移动距离:如果我们手指在屏幕上的移动距离小于此值则不会绘制

private Bitmap fgBitmap, bgBitmap;// 前景橡皮擦的Bitmap和背景我们底图的Bitmap

private Canvas mCanvas;// 绘制橡皮擦路径的画布

private Paint mPaint;// 橡皮檫路径画笔

private Path mPath;// 橡皮擦绘制路径

private int screenW, screenH;// 屏幕宽高

private float preX, preY;// 记录上一个触摸事件的位置坐标

public EraserView(Context context, AttributeSet set) {

super(context, set);

// 计算参数

cal(context);

// 初始化对象

init(context);

}

/**

* 计算参数

*

* @param context

* 上下文环境引用

*/

private void cal(Context context) {

// 获取屏幕尺寸数组

int[] screenSize = MeasureUtil.getScreenSize((Activity) context);

// 获取屏幕宽高

screenW = screenSize[0];

screenH = screenSize[1];

}

/**

* 初始化对象

*/

private void init(Context context) {

// 实例化路径对象

mPath = new Path();

// 实例化画笔并开启其抗锯齿和抗抖动

mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);

// 设置画笔透明度为0是关键!我们要让绘制的路径是透明的,然后让该路径与前景的底色混合“抠”出绘制路径

mPaint.setARGB(128, 255, 0, 0);

// 设置混合模式为DST_IN

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

// 设置画笔风格为描边

mPaint.setStyle(Paint.Style.STROKE);

// 设置路径结合处样式

mPaint.setStrokeJoin(Paint.Join.ROUND);

// 设置笔触类型

mPaint.setStrokeCap(Paint.Cap.ROUND);

// 设置描边宽度

mPaint.setStrokeWidth(50);

// 生成前景图Bitmap

fgBitmap = Bitmap.createBitmap(screenW, screenH, Config.ARGB_8888);

// 将其注入画布

mCanvas = new Canvas(fgBitmap);

// 绘制画布背景为中性灰

mCanvas.drawColor(0xFF808080);

// 获取背景底图Bitmap

bgBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a4);

// 缩放背景底图Bitmap至屏幕大小

bgBitmap = Bitmap.createScaledBitmap(bgBitmap, screenW, screenH, true);

}

@Override

protected void onDraw(Canvas canvas) {

// 绘制背景

canvas.drawBitmap(bgBitmap, 0, 0, null);

// 绘制前景

canvas.drawBitmap(fgBitmap, 0, 0, null);

/*

* 这里要注意canvas和mCanvas是两个不同的画布对象

* 当我们在屏幕上移动手指绘制路径时会把路径通过mCanvas绘制到fgBitmap上

* 每当我们手指移动一次均会将路径mPath作为目标图像绘制到mCanvas上,而在上面我们先在mCanvas上绘制了中性灰色

* 两者会因为DST_IN模式的计算只显示中性灰,但是因为mPath的透明,计算生成的混合图像也会是透明的

* 所以我们会得到“橡皮擦”的效果

*/

mCanvas.drawPath(mPath, mPaint);

}

/**

* View的事件将会在7/12详解

*/

@Override

public boolean onTouchEvent(MotionEvent event) {

/*

* 获取当前事件位置坐标

*/

float x = event.getX();

float y = event.getY();

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:// 手指接触屏幕重置路径

mPath.reset();

mPath.moveTo(x, y);

preX = x;

preY = y;

break;

case MotionEvent.ACTION_MOVE:// 手指移动时连接路径

float dx = Math.abs(x - preX);

float dy = Math.abs(y - preY);

if (dx >= MIN_MOVE_DIS || dy >= MIN_MOVE_DIS) {

mPath.quadTo(preX, preY, (x + preX) / 2, (y + preY) / 2);

preX = x;

preY = y;

}

break;

}

// 重绘视图

invalidate();

return true;

}

}

运行效果如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: