Android进阶-自定义view(2)(DrawPath)

@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 和 WINDING 的原理

EVEN_ODD

即 even-odd rule (奇偶原则):对于平面中的任意一点,向任意方向射出一条射线,这条射线和图形相交的次数(相交才算,相切不算哦)如果是奇数,则这个点被认为在图形内部,是要被涂色的区域;如果是偶数,则这个点被认为在图形外部,是不被涂色的区域。还以左右相交的双圆为例:

Android进阶-自定义view(2)(DrawPath)_第1张图片

射线的方向无所谓,同一个点射向任何方向的射线,结果都是一样的,不信你可以试试。

从上图可以看出,射线每穿过图形中的一条线,内外状态就发生一次切换,这就是为什么 EVEN_ODD 是一个「交叉填充」的模式。

WINDING

即 non-zero winding rule (非零环绕数原则):首先,它需要你图形中的所有线条都是有绘制方向的:

Android进阶-自定义view(2)(DrawPath)_第2张图片

然后,同样是从平面中的点向任意方向射出一条射线,但计算规则不一样:以 0 为初始值,对于射线和图形的所有交点,遇到每个顺时针的交点(图形从射线的左边向右穿过)把结果加 1,遇到每个逆时针的交点(图形从射线的右边向左穿过)把结果减 1,最终把所有的交点都算上,得到的结果如果不是 0,则认为这个点在图形内部,是要被涂色的区域;如果是 0,则认为这个点在图形外部,是不被涂色的区域。

Android进阶-自定义view(2)(DrawPath)_第3张图片

和 EVEN_ODD 相同,射线的方向并不影响结果。

所以,我前面的那个「简单粗暴」的总结,对于 WINDING 来说并不完全正确:如果你所有的图形都用相同的方向来绘制,那么 WINDING 确实是一个「全填充」的规则;但如果使用不同的方向来绘制图形,结果就不一样了。

图形的方向:对于添加子图形类方法(如 Path.addCircle() Path.addRect())的方向,由方法的 dir 参数来控制,这个在前面已经讲过了;而对于画线类的方法(如 Path.lineTo()Path.arcTo())就更简单了,线的方向就是图形的方向。

所以,完整版的 EVEN_ODD 和 WINDING 的效果应该是这样的:

Android进阶-自定义view(2)(DrawPath)_第4张图片

而 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/

你可能感兴趣的:(Android进阶)