爱哥原文地址:http://blog.csdn.net/aigestudio/article/details/41316141
深挖如何去绘制更复杂的view。
一、ColorFilter及其子类
Paint.setColorFilter(ColorFilter filter);设置颜色过滤。
ColorFilter没有颜色相关的方法,所以肯定是通过其子类来控制的。有三个直接子类:ColorMatrixColorFilter、LightingColorFilter、PorterDuffColorFilter。
1、ColorMatrixColorFilter
a、简介及how用
色彩矩阵颜色过滤器。先了解下色彩矩阵,在Android中图片是以RGBA像素点的形式加载到内存中的,修改这些像素信息需要一个叫做ColorMatrix类的支持,其定义了一个4X5的float[]类型的矩阵:
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
});
Paint.setColorFilter(ColorFilter filter)传入ColorMatrixColorFilter对象后,是怎么计算最后要显示的颜色呢?通过色彩矩阵与原色彩相乘得出色彩(复习下矩阵的乘法):
使用方法:定义一个ColorMatrixColorFilter对象,然后传入Paint.setFilter()。
b、适用场景
这么复杂的实现只是为了改变颜色,你是不是想起来还有setColor()。是没错,但是setColor只是针对单一颜色使用的,如果是一张图片(包含了至少两种颜色),那你就需要此方法了。通过此方法你可以打到跟PS相同的效果。
2、LightingColorFilter
a、简介及用法
光照颜色过滤器。只有一个构造方法:LightingColorFilter(int mul, int add);mul全称是colorMultiply意为色彩倍增,而add全称是colorAdd意为色彩添加,这两个值都是16进制的色彩值0xAARRGGBB。
用法同上:定义对象,传入Paint.setFilter()。比如想过滤绿色,则只需将第一个参数ARGB中G的值设为0即可:
同样想增强某个颜色值只需将第二个参数的值增强即可。
// 设置颜色过滤
mPaint.setColorFilter(new LightingColorFilter(0xFFFF00FF, 0x00000000));
b、适用场景
如果有这个需求:点击一个图片改变他的颜色,而不是替换成另一张点击效果的图片。那么这个类就有用了。
3、PorterDuffColorFilter
a、简介及用法
只有一个构造方法:PorterDuffColorFilter(int color, PorterDuff.Mode mode);color是16进制的颜色值;mode是PorterDuff内部类Mode中的一个常量,这个值表示混合模式:是将color值和画布上的元素混合。
PorterDuff.Mode中的模式不仅仅是应用于图像色彩混合,还应用于图形混合。
用法同上。
PS中图层的混合模式跟PorterDuff.Mode提供的其实差不多。
二、setXfermode(Xfermode xfermode)及其子类
译为过渡模式或图像混合模式。同setColorFilter一样,没有公开的方法,所以还是直接看它的子类吧:AvoidXfermode、PixelXorXfermode和PorterDuffXfermode。
1、AvoidXfermode
因不支持硬件加速在API 16已经过时了,如果想在高于API 16的机子上测试这玩意,必须关闭硬件加速(在清单文件中可设置)。
2、PixelXorXfermode
也在API 16过时了。。。
3、PorterDuffXfermode
a、简介及用法
只有一个构造函数:PorterDuffXfermode(PorterDuff.Mode mode);先来看一张API DEMO里的图片:
在API中android为我们提供了18种模式(比上图多了两种:ADD和OVERLAY)。src为源图像,即为要绘制的图像;Dis为目标图像,即为要把源图像绘制到的图像。
用法:
@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);
}
@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);
}
b、混合模式到底有什么用
看下面这张图,怎么画呢?
善于观察,善于利用混合模式:
是不是突然感觉简单很多。
橡皮擦效果:通过手指不断的触摸屏幕绘制Path,再以Path作遮罩遮掉填充的色块,显示下层的图像:
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;
}
}
效果图: