Android 自定义View (五)
本讲内容:PorterDuffXfermode图形混合模式类(只有一个含参的构造方法)
在API中Android为我们提供了的18种图形混合模式:
Sa全称为Source alpha表示源图的Alpha通道;Sc全称为Source color表示源图的颜色;Da全称为Destination alpha表示目标图的Alpha通道;Dc全称为Destination color表示目标图的颜色
比上图多了少种ADD和OVERLAY)模式:(上层( src源图 )、下层(dis目标图) )
1、PorterDuff.Mode.CLEAR 所绘制不会提交到画布上。
2、PorterDuff.Mode.SRC 显示上层绘制图片
3、PorterDuff.Mode.DST 显示下层绘制图片
4、PorterDuff.Mode.SRC_OVER 正常绘制显示,上下层绘制叠盖。
5、PorterDuff.Mode.DST_OVER 上下层都显示。下层居上显示。
6、PorterDuff.Mode.SRC_IN 取两层绘制交集。显示上层。
7、PorterDuff.Mode.DST_IN 取两层绘制交集。显示下层。
8、PorterDuff.Mode.SRC_OUT 取上层绘制非交集部分。
9、PorterDuff.Mode.DST_OUT 取下层绘制非交集部分。
10、PorterDuff.Mode.SRC_ATOP 取下层非交集部分与上层交集部分
11、PorterDuff.Mode.DST_ATOP 取上层非交集部分与下层交集部分
12.PorterDuff.Mode.XOR 取两层绘制非交集。两层绘制非交集。
13、PorterDuff.Mode.DARKEN 上下层都显示。变暗
14、PorterDuff.Mode.LIGHTEN 上下层都显示。变量
15、PorterDuff.Mode.MULTIPLY 取两层绘制交集
16、PorterDuff.Mode.SCREEN 上下层都显示。
17、PorterDuff.Mode.ADD 上下层饱和度相加
18、PorterDuff.Mode.OVERLAY 上下层叠加
示例一:DST_IN 取两层绘制交集。显示下层。
dis目标图 src源图 混合模式效果图(文字不见了)
下面是自定义CustomTitleView.java文件:
public class CustomView extends View {
private Paint mPaint;// 画笔
private Bitmap bitmapDis, bitmapSrc;// 位图
private PorterDuffXfermode porterDuffXfermode;// 图形混合模式
private int x, y;// 位图绘制时左上角的起点坐标
private int screenW, screenH;// 屏幕尺寸
public CustomView(Context context) {
super(context, null);
}
public CustomView(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.a1);
bitmapSrc = BitmapFactory.decodeResource(context.getResources(), R.drawable.a2);
// 获取包含屏幕尺寸的数组
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;
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
// 将绘制操作保存到新的图层(离屏缓存)
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);
}
}
示例二:DST_OUT 取下层绘制非交集部分。
src源图 效果图
下面是自定义CustomTitleView.java文件:
public class CustomView extends View {
private Paint mPaint;// 画笔
private Bitmap bitmapSrc;// 位图
private PorterDuffXfermode porterDuffXfermode;// 图形混合模式
private int x, y;// 位图绘制时左上角的起点坐标
private int screenW, screenH;// 屏幕尺寸
public CustomView(Context context) {
super(context, null);
}
public CustomView(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.c5);
// 获取包含屏幕尺寸的数组
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;
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
// 将绘制操作保存到新的图层(离屏缓存)
int sc = canvas.saveLayer(0, 0, screenW, screenH, null,
Canvas.ALL_SAVE_FLAG);
// 先绘制一层颜色(先绘制dis目标图)
canvas.drawColor(0xFFFF04DA);
// 设置混合模式
mPaint.setXfermode(porterDuffXfermode);
// 再绘制src源图
canvas.drawBitmap(bitmapSrc, x, y, mPaint);
// 还原混合模式
mPaint.setXfermode(null);
// 还原画布
canvas.restoreToCount(sc);
}
}
示例三:橡皮檫效果
我们可以通过手指不断地触摸屏幕绘制Path,再以Path作遮罩遮掉填充的色块显示下层的图像:
public class CustomView 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 CustomView(Context context) {
super(context, null);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
// 计算参数
cal(context);
// 初始化对象
init(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);
}
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的事件
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;
}
}