@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class DrawPath extends View {
public DrawPath(Context context) {
super(context);
}
public DrawPath(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public DrawPath(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public DrawPath(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
Paint paint = new Paint();
Path path = new Path(); // 初始化 Path 对象
{
// 使用 path 对图形进行描述(这段描述代码不必看懂)
path.addArc(200, 200, 400, 400, -225, 225);
path.arcTo(400, 200, 600, 400, -180, 225, false);
path.lineTo(400, 542);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制出 path 描述的图形(心形),大功告成
canvas.drawPath(path, paint);
/*-------------------------------Path 方法第一类:直接描述路径-------------------------------*/
//1、第一组: addXxx() ——添加子图形
//addCircle(float x, float y, float radius, Direction dir) 添加圆
path.addCircle(500, 800, 200, Path.Direction.CW);
//addOval(float left, float top, float right, float bottom, Direction dir)
//addOval(RectF oval, Direction dir) 添加椭圆
//addRect(float left, float top, float right, float bottom, Direction dir)
//addRect(RectF rect, Direction dir) 添加矩形
//addRoundRect(RectF rect, float rx, float ry, Direction dir)
//addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Direction dir)
//addRoundRect(RectF rect,float[] radii, Direction dir)
//addRoundRect(float left, float top, float right, float bottom, float[] radii, Direction dir) 添加圆角矩形
//addPath(Path path) 添加另一个 Path
//2、第二组:xxxTo() ——画线(直线或曲线)
//lineTo(float x, float y) / rLineTo(float x, float y) 画直线
paint.setStyle(Paint.Style.STROKE);
path.lineTo(200, 1000); // 由当前位置 (0, 0) 向 (200, 1000) 画一条直线
path.rLineTo(100, 0); // 由当前位置 (100, 100) 向正右方 100 像素的位置画一条直线
//quadTo(float x1, float y1, float x2, float y2)
//rQuadTo(float dx1, float dy1, float dx2, float dy2) 画二次贝塞尔曲线
//cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
//rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 画三次贝塞尔曲线
//moveTo(float x, float y) / rMoveTo(float x, float y) 移动到目标位置
paint.setStyle(Paint.Style.STROKE);
path.lineTo(100, 100); // 画斜线
path.moveTo(200, 100); // 我移~~
path.lineTo(200, 0); // 画竖线
paint.setStyle(Paint.Style.STROKE);
path.lineTo(100, 100); // 由当前位置 (0, 0) 向 (100, 100) 画一条直线
path.rLineTo(100, 0); // 由当前位置 (100, 100) 向正右方 100 像素的位置画一条直线
paint.setStyle(Paint.Style.STROKE);
path.lineTo(100, 100);
path.arcTo(100, 100, 300, 300, -90, 90, false); // 直接连线连到弧形起点(有痕迹)
//addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle)
//addArc(RectF oval, float startAngle, float sweepAngle)
//又是一个弧形的方法。一个叫 arcTo ,一个叫 addArc(),都是弧形,区别在哪里?
//其实很简单: addArc() 只是一个直接使用了 forceMoveTo = true 的简化版 arcTo() 。
paint.setStyle(Paint.Style.STROKE);
path.lineTo(100, 100);
path.addArc(100, 100, 300, 300, -90, 90);
//close() 封闭当前子图形
//子图形未封闭
paint.setStyle(Paint.Style.STROKE);
path.moveTo(100, 100);
path.lineTo(200, 100);
path.lineTo(150, 150);
//子图形封闭
paint.setStyle(Paint.Style.STROKE);
path.moveTo(100, 100);
path.lineTo(200, 100);
path.lineTo(150, 150);
path.close(); // 使用 close() 封闭子图形。等价于 path.lineTo(100, 100)
canvas.drawPath(path, paint);
/*-------------------------------Path 方法第一类:直接描述路径-------------------------------*/
/*-------------------------------Path 方法第二类:辅助的设置或计算------------------------------*/
//Path.setFillType(Path.FillType ft) 设置填充方式
//方法中填入不同的 FillType 值,就会有不同的填充效果。FillType 的取值有四个:
//EVEN_ODD
//WINDING (默认值)
//INVERSE_EVEN_ODD
//INVERSE_WINDING
//drawBitmap(Bitmap bitmap, float left, float top, Paint paint) 画 Bitmap
//drawBitmap(bitmap, 200, 100, paint);
//drawText(String text, float x, float y, Paint paint) 绘制文字
//canvas.drawText(text, 200, 100, paint);
//Paint.setTextSize(float textSize)
String text = "Hello";
paint.setTextSize(18);
canvas.drawText(text, 100, 25, paint);
paint.setTextSize(36);
canvas.drawText(text, 100, 70, paint);
paint.setTextSize(60);
canvas.drawText(text, 100, 145, paint);
paint.setTextSize(84);
canvas.drawText(text, 100, 240, paint);
/*-------------------------------Path 方法第二类:辅助的设置或计算------------------------------*/
}
}
即 even-odd rule (奇偶原则):对于平面中的任意一点,向任意方向射出一条射线,这条射线和图形相交的次数(相交才算,相切不算哦)如果是奇数,则这个点被认为在图形内部,是要被涂色的区域;如果是偶数,则这个点被认为在图形外部,是不被涂色的区域。还以左右相交的双圆为例:
射线的方向无所谓,同一个点射向任何方向的射线,结果都是一样的,不信你可以试试。
从上图可以看出,射线每穿过图形中的一条线,内外状态就发生一次切换,这就是为什么 EVEN_ODD
是一个「交叉填充」的模式。
即 non-zero winding rule (非零环绕数原则):首先,它需要你图形中的所有线条都是有绘制方向的:
然后,同样是从平面中的点向任意方向射出一条射线,但计算规则不一样:以 0 为初始值,对于射线和图形的所有交点,遇到每个顺时针的交点(图形从射线的左边向右穿过)把结果加 1,遇到每个逆时针的交点(图形从射线的右边向左穿过)把结果减 1,最终把所有的交点都算上,得到的结果如果不是 0,则认为这个点在图形内部,是要被涂色的区域;如果是 0,则认为这个点在图形外部,是不被涂色的区域。
和
EVEN_ODD
相同,射线的方向并不影响结果。
所以,我前面的那个「简单粗暴」的总结,对于 WINDING
来说并不完全正确:如果你所有的图形都用相同的方向来绘制,那么 WINDING
确实是一个「全填充」的规则;但如果使用不同的方向来绘制图形,结果就不一样了。
图形的方向:对于添加子图形类方法(如
Path.addCircle()
Path.addRect()
)的方向,由方法的dir
参数来控制,这个在前面已经讲过了;而对于画线类的方法(如Path.lineTo()
Path.arcTo()
)就更简单了,线的方向就是图形的方向。
所以,完整版的 EVEN_ODD
和 WINDING
的效果应该是这样的:
而 INVERSE_EVEN_ODD
和 INVERSE_WINDING
,只是把这两种效果进行反转而已,你懂了 EVEN_ODD
和 WINDING
,自然也就懂 INVERSE_EVEN_ODD
和 INVERSE_WINDING
了,我就不讲了。
好,花了好长的篇幅来讲 drawPath(path)
和 Path
,终于讲完了。同时, Canvas
对图形的绘制就也讲完了。图形简单时,使用 drawCircle()
drawRect()
等方法来直接绘制;图形复杂时,使用 drawPath()
来绘制自定义图形。
除此之外, Canvas
还可以绘制 Bitmap
和文字。
参照文章:http://hencoder.com/ui-1-1/