Android Canvas+Paint 画图
一、Path相关讲解
主要讲下android里的Path(封装了贝塞尔曲线)& Canvas里的drawPath(path,paint);
很多人听到贝塞尔曲线,就觉得似乎挺高端大气上档次,后面会和大家一起揭开它的面纱,一睹真容;
Path(路径):
我们先看看Path类里有哪些方法
咱们从上往下看:
构造函数有两个,分别是
- /**
- * Create an empty path
- */
- public Path() {
- mNativePath = init1();
- mDetectSimplePaths = HardwareRenderer.isAvailable();
- }
- /**
- * Create a new path, copying the contents from the src path.
- *
- * @param src The path to copy from when initializing the new path
- */
- public Path(Path src) {
- int valNative = 0;
- if (src != null) {
- valNative = src.mNativePath;
- }
- mNativePath = init2(valNative);
- mDetectSimplePaths = HardwareRenderer.isAvailable();
- }
这没啥好说的,第二种就是直接复用src 里设置的属性创建一个新的Path对象;
path.reset():清除掉path里的线条和曲线,但是不会改变它的fill-type(后面setFillType再说);
path.rewind():清除掉path里的线条和曲线,但是会保留内部的数据结构以便重用;
path.set(Path src);用src的内容替换原path的内容,一起看个小例子:
创建一个path,添加一个实心圆到path里
- mEndPath = new Path();
- mEndPath.addCircle(300, 300, 100, Direction.CW);
- canvas.drawPath(mEndPath, mPaint);
此时在path里再添加一个矩形:
- mEndPath = new Path();
- mEndPath.addCircle(300, 300, 100, Direction.CW);
- mEndPath.addRect(new RectF(50, 50, 250, 200), Direction.CW);
做如下改动:
- mEndPath = new Path();
- mEndPath.addCircle(300, 300, 100, Direction.CW);
- //mEndPath.addRect(new RectF(50, 50, 250, 200), Direction.CW);
- mSrcPath = new Path();
- mSrcPath.addRect(new RectF(50, 50, 250, 200), Direction.CW);
- mEndPath.set(mSrcPath);
下面一起来看看Path 的 FillType - 填充模式:
android里定义了四种FillType,分别是:
WINDING (0),
EVEN_ODD (1),
INVERSE_WINDING (2),
INVERSE_EVEN_ODD (3)
有张图可以专门用来说明这四种模式的差别:
以上图示已经非常清晰,我们还是用如下代码做下测试:
- mEndPath = new Path();
- mEndPath.addCircle(300, 300, 150, Direction.CW);
- mEndPath.addCircle(380, 380, 150, Direction.CW);
- mEndPath.setFillType(FillType.INVERSE_EVEN_ODD);
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setStyle(Style.FILL);
- mPaint.setColor(Color.RED);
不设置FillType:
setFillType(FillType.WINDING) setFillType(FillType.EVEN_ODD):
setFillType(FillType.INVERSE_WINDING): setFillType(FillType.INVERSE_EVEN_ODD):
根据以上图示,Path的FillType可以总结如下:
1.Path的默认FillType为 FillType.WINDING;
2.作用的范围为绘制 Path 的 Canvas 整体,而非 path 所在区域;
3.FillType.WINDING:取path所有所在区域;
4.FillType.EVEN_ODD:取path所在并不相交区域;
5.FillType.INVERSE_WINDING:取path所有未占区域;
6.FillType.INVERSE_EVEN_ODD:取path未占或相交区域;
下面看看和填充模式相关的几个方法:
getFillType():不用多说,返回 Path 的填充模式;
setFillType():设置 Path 的填充模式;
isInverseFillType():是否是 逆 填充模式:
WINDING 和 EVEN_ODD 返回false,INVERSE_WINDING 和 INVERSE_EVEN_ODD 返回true;
toggleInverseFillType():切换相反的填充模式,举个小例子:
- mEndPath = new Path();
- mEndPath.addCircle(300, 300, 150, Direction.CW);
- mEndPath.addCircle(380, 380, 150, Direction.CW);
- mEndPath.setFillType(FillType.WINDING);
- mEndPath.toggleInverseFillType();
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setStyle(Style.FILL);
- mPaint.setColor(Color.RED);
FillType.INVERSE_WINDING
isEmpty():path是否为空,如果path不包含任何线条和曲线,则返回true,否则返回false;
isRect(RectF rect):如果path指定的是一个rect,则返回true,否则返回false,如果返回true & rect 不为null,则将该rect设置为path 的区域;
computeBounds(RectF bounds,boolean exact):计算path所在区域,并将结果写入bounds,如果整个path只包含0或1个点,将返回(0,0,0,0):
用如下代码做下测试:
- mComputeRect = new RectF();
- mEndPath = new Path();
- mEndPath.addCircle(380, 380, 150, Direction.CW);
- mEndPath.addRect(new RectF(200, 300, 500, 500), Direction.CW);
- mEndPath.computeBounds(mComputeRect, false);
- Toast.makeText(
- mContext,
- "" + mComputeRect.left + "," + mComputeRect.top + "," + mComputeRect.right + ","
- + mComputeRect.bottom,
- Toast.LENGTH_LONG).show();
返回结果为(200,230,530,530),即path所含内容的边界区域
incReserve(int extraPtCount):提示path将会增加extraPtCount个点,这能使path有效率的分配它的存储空间;
二、Paint
1、Xfermode:
setXfermode
设置两张图片相交时的模式
我们知道 在正常的情况下,在已有的图像上绘图将会在其上面添加一层新的形状。 如果新的Paint是完全不透明的,那么它将完全遮挡住下面的Paint;
而setXfermode就可以来解决这个问题
一般来说 用法是这样的
Canvas canvas = new Canvas(bitmap1); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); canvas.drawBitmap(mask, 0f, 0f, paint);
就是在图片bitmap1上面绘制图片mask时 处理两者相交时候显示的问题
canvas原有的图片 可以理解为背景 就是dst
新画上去的图片 可以理解为前景 就是src
Mode的值 如下图
PorterDuff.Mode.CLEAR 清除画布上图像
PorterDuff.Mode.SRC 显示上层图像
PorterDuff.Mode.DST 显示下层图像
PorterDuff.Mode.SRC_OVER上下层图像都显示,上层居上显示
PorterDuff.Mode.DST_OVER 上下层都显示,下层居上显示
PorterDuff.Mode.SRC_IN 取两层图像交集部门,只显示上层图像
PorterDuff.Mode.DST_IN 取两层图像交集部门,只显示下层图像
PorterDuff.Mode.SRC_OUT 取上层图像非交集部门
PorterDuff.Mode.DST_OUT 取下层图像非交集部门
PorterDuff.Mode.SRC_ATOP 取下层图像非交集部门与上层图像交集部门
PorterDuff.Mode.DST_ATOP 取上层图像非交集部门与下层图像交集部门
PorterDuff.Mode.XOR 取两层图像的非交集部门
画图示例(画圆角矩形轨迹 常用语市面上流行的poker倒计时):
package com.k.gameview; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; public class GameView1 extends View implements Runnable { /* 声明Paint对象 */ private Paint mPaint = null; public GameView1(Context context) { super(context); /* 构建对象 */ mPaint = new Paint(); /* 开启线程 */ new Thread(this).start(); } float arc; public void onDraw(Canvas canvas) { super.onDraw(canvas); if(arc>360) arc=0; /* 设置画布的颜色 */ canvas.drawColor(Color.BLACK); /* 设置取消锯齿效果 */ mPaint.setAntiAlias(true); mPaint.setAlpha(255); if(arc>200){ mPaint.setColor(Color.YELLOW); }else{ mPaint.setColor(Color.GREEN); } mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(5); RectF rf = new RectF(10, 10, 90, 140); canvas.drawRoundRect(rf, 10, 10, mPaint); mPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); mPaint.setAlpha(0); mPaint.setStyle(Paint.Style.FILL); canvas.drawArc(new RectF(-100, -75, 200, 225), 240, arc, true, mPaint); arc+=2.5; } // 触笔事件 public boolean onTouchEvent(MotionEvent event) { return true; } // 按键按下事件 public boolean onKeyDown(int keyCode, KeyEvent event) { return true; } // 按键弹起事件 public boolean onKeyUp(int keyCode, KeyEvent event) { return false; } public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return true; } public void run() { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // 使用postInvalidate可以直接在线程中更新界面 postInvalidate(); } } }