爱哥原文地址:http://blog.csdn.net/aigestudio/article/details/41316141
深挖如何去绘制更复杂的view。
Paint.setColorFilter(ColorFilter filter);设置颜色过滤。
ColorFilter没有颜色相关的方法,所以肯定是通过其子类来控制的。有三个直接子类:ColorMatrixColorFilter、LightingColorFilter、PorterDuffColorFilter。
色彩矩阵颜色过滤器。先了解下色彩矩阵,在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()。
这么复杂的实现只是为了改变颜色,你是不是想起来还有setColor()。是没错,但是setColor只是针对单一颜色使用的,如果是一张图片(包含了至少两种颜色),那你就需要此方法了。通过此方法你可以打到跟PS相同的效果。
光照颜色过滤器。只有一个构造方法:LightingColorFilter(int mul, int add);mul全称是colorMultiply意为色彩倍增,而add全称是colorAdd意为色彩添加,这两个值都是16进制的色彩值0xAARRGGBB。
用法同上:定义对象,传入Paint.setFilter()。比如想过滤绿色,则只需将第一个参数ARGB中G的值设为0即可:
同样想增强某个颜色值只需将第二个参数的值增强即可。
// 设置颜色过滤 mPaint.setColorFilter(new LightingColorFilter(0xFFFF00FF, 0x00000000));
如果有这个需求:点击一个图片改变他的颜色,而不是替换成另一张点击效果的图片。那么这个类就有用了。
只有一个构造方法:PorterDuffColorFilter(int color, PorterDuff.Mode mode);color是16进制的颜色值;mode是PorterDuff内部类Mode中的一个常量,这个值表示混合模式:是将color值和画布上的元素混合。
PorterDuff.Mode中的模式不仅仅是应用于图像色彩混合,还应用于图形混合。
用法同上。
PS中图层的混合模式跟PorterDuff.Mode提供的其实差不多。
译为过渡模式或图像混合模式。同setColorFilter一样,没有公开的方法,所以还是直接看它的子类吧:AvoidXfermode、PixelXorXfermode和PorterDuffXfermode。
因不支持硬件加速在API 16已经过时了,如果想在高于API 16的机子上测试这玩意,必须关闭硬件加速(在清单文件中可设置)。
也在API 16过时了。。。
只有一个构造函数: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); }
看下面这张图,怎么画呢?
善于观察,善于利用混合模式:
是不是突然感觉简单很多。
橡皮擦效果:通过手指不断的触摸屏幕绘制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; } }效果图: