美文网首页
Android自定义View-Path基础

Android自定义View-Path基础

作者: 小的橘子 | 来源:发表于2019-08-27 21:16 被阅读0次

    一、Path认识

    二、Path常用方法详解

    第1组: moveTo、 setLastPoint、 lineTo 和 close

    由于Path的有些知识点无法单独来讲,所以本次采取了一次讲一组方法。
    画笔创建如下:

    Paint mPaint = new Paint();             // 创建画笔
    mPaint.setColor(Color.BLACK);           // 画笔颜色 - 黑色
    mPaint.setStyle(Paint.Style.STROKE);    // 填充模式 - 描边
    mPaint.setStrokeWidth(10);              // 边框宽度 - 10
    

    lineTo:
    方法预览:

    public void lineTo (float x, float y)
    

    连线至目标点,默认第一个点位坐标原点,如果已经有点则是从上次点连线至目标点。

    示例代码:

    canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心(宽高数据在onSizeChanged中获取)
    Path path = new Path();                     // 创建Path
    path.lineTo(200, 200);                      // lineTo
    path.lineTo(200,0);
    canvas.drawPath(path, mPaint);              // 绘制Path
    

    在示例中我们调用了两次lineTo,第一次由于之前没有过操作,所以默认点就是坐标原点O,结果就是坐标原点O到A(200,200)之间连直线(用蓝色圈1标注)。
    第二次lineTo的时候,由于上次的结束位置是A(200,200),所以就是A(200,200)到B(200,0)之间的连线(用蓝色圈2标注)。

    moveTo 和 setLastPoint:
    方法预览

    public void moveTo (float x, float y)
    public void setLastPoint (float dx, float dy)
    

    这两个方法虽然在作用上有相似之处,但实际上却是完全不同的两个东东,具体参照下表:

    方法名 简介 是否影响之前的操作 是否影响之后操作
    moveTo 移动下一次操作的起点位置
    setLastPoint 设置之前操作的最后一个点位置

    示例代码:

    canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
    Path path = new Path();                     // 创建Path
    path.lineTo(200, 200);                      // lineTo
    path.moveTo(200,100);                       // moveTo
    path.lineTo(200,0);                         // lineTo
    canvas.drawPath(path, mPaint);              // 绘制Path
    

    moveTo只改变下次操作的起点,在执行完第一次LineTo的时候,本来的默认点位置是A(200,200),但是moveTo将其改变成为了C(200,100),所以在第二次调用lineTo的时候就是连接C(200,100) 到 B(200,0) 之间的直线(用蓝色圈2标注)。

    下面是setLastPoint的示例:

    canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
    
    Path path = new Path();                     // 创建Path
    
    path.lineTo(200, 200);                      // lineTo
    
    path.setLastPoint(200,100);                 // setLastPoint
    
    path.lineTo(200,0);                         // lineTo
    
    canvas.drawPath(path, mPaint);              // 绘制Path
    

    setLastPoint是重置上一次操作的最后一个点,在执行完第一次的lineTo的时候,最后一个点是A(200,200),而setLastPoint更改最后一个点为C(200,100),所以在实际执行的时候,第一次的lineTo就不是从原点O到A(200,200)的连线了,而变成了从原点O到C(200,100)之间的连线了。

    在执行完第一次lineTo和setLastPoint后,最后一个点的位置是C(200,100),所以在第二次调用lineTo的时候就是C(200,100) 到 B(200,0) 之间的连线(用蓝色圈2标注)。

    close
    方法预览:

    public void close ()
    

    close方法用于连接当前最后一个点和最初的一个点(如果两个点不重合的话),最终形成一个封闭的图形。
    示例代码:

    canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
    
    Path path = new Path();                     // 创建Path
    
    path.lineTo(200, 200);                      // lineTo
    
    path.lineTo(200,0);                         // lineTo
    
    path.close();                               // close
    
    canvas.drawPath(path, mPaint);              // 绘制Path
    

    很明显,两个lineTo分别代表第1和第2条线,而close在此处的作用就算连接了B(200,0)点和原点O之间的第3条线,使之形成一个封闭的图形。

    注意:close的作用是封闭路径,与连接当前最后一个点和第一个点并不等价。如果连接了最后一个点和第一个点仍然无法形成封闭图形,则close什么 也不做。

    第2组: addXxx与arcTo

    第一类(基本形状)

    方法预览:

    // 圆形
    public void addCircle (float x, float y, float radius, Path.Direction dir)
    // 椭圆
    public void addOval (RectF oval, Path.Direction dir)
    // 矩形
    public void addRect (float left, float top, float right, float bottom, Path.Direction dir)
    public void addRect (RectF rect, Path.Direction dir)
    // 圆角矩形
    public void addRoundRect (RectF rect, float[] radii, Path.Direction dir)
    public void addRoundRect (RectF rect, float rx, float ry, Path.Direction dir)
    

    这一类就是在path中添加一个基本形状,基本形状部分和前面所讲的绘制基本形状并无太大差别,本次只将其中不同的部分摘出来详细讲解一下。

    这里说下Path.Direction这个参数
    Direction是一个枚举(Enum)类型,里面只有两个枚举常量,如下:

    类型 解释 翻译
    CW clockwise 顺时针
    CCW counter-clockwise 逆时针

    先偷偷剧透一下这个顺时针和逆时针的作用。

    序号 作用
    1 在添加图形时确定闭合顺序(各个点的记录顺序)
    2 对图形的渲染结果有影响(是判断图形渲染的重要条件)

    这个先剧透这么多,至于对闭合顺序有啥影响,图形的渲染等问题等请慢慢看下去

    咱们先研究确定闭合顺序的问题,添加一个矩形试试看:

    canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
    
    Path path = new Path();
    
    path.addRect(-200,-200,200,200, Path.Direction.CW);
    
    canvas.drawPath(path,mPaint);
    

    将上面代码的CW改为CCW再运行一次。接下来就是见证奇迹的时刻,两次运行结果一模一样,有木有很神奇!

    其实啊,这个东东是自带隐身技能的,想要让它现出原形,就要用到咱们刚刚学到的setLastPoint(重置当前最后一个点的位置)。

    canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
    
    Path path = new Path();
    
    path.addRect(-200,-200,200,200, Path.Direction.CW);
    
    path.setLastPoint(-300,300);                // <-- 重置最后一个点的位置
    
    canvas.drawPath(path,mPaint);
    

    可以明显看到,图形发生了奇怪的变化。为何会如此呢?

    对于上面这个矩形来说,我们采用的是顺时针(CW),所以记录的点的顺序就是 A -> B -> C -> D. 最后一个点就是D,我们这里使用setLastPoint改变最后一个点的位置实际上是改变了D的位置。

    理解了上面的原理之后,设想如果我们将顺时针改为逆时针(CCW),则记录点的顺序应该就是 A -> D -> C -> B, 再使用setLastPoint则改变的是B的位置,我们试试看结果和我们的猜想是否一致:

    canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
    
    Path path = new Path();
    
    path.addRect(-200,-200,200,200, Path.Direction.CCW);
    
    path.setLastPoint(-300,300);                // <-- 重置最后一个点的位置
    
    canvas.drawPath(path,mPaint);
    

    果不其然!
    关于顺时针和逆时针对图形填充结果的影响见Path完结篇

    第二类(Path)

    方法预览:

    public void addPath (Path src)
    public void addPath (Path src, float dx, float dy)
    public void addPath (Path src, Matrix matrix)
    

    这个相对比较简单,也很容易理解,就是将两个Path合并成为一个。

    第二个方法比第一个方法多出来的两个参数是将src进行了位移之后再添加进当前path中。

    第三个方法是将src添加到当前path之前先使用Matrix进行变换。

    代码示例:

    canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
    canvas.scale(1,-1);                         // <-- 注意 翻转y坐标轴
    
    Path path = new Path();
    Path src = new Path();
    
    path.addRect(-200,-200,200,200, Path.Direction.CW);
    src.addCircle(0,0,100, Path.Direction.CW);
    
    path.addPath(src,0,200); // 添加时进行平移操作
    
    canvas.drawPath(path,mPaint);
    

    效果如下:


    第三类(addArc与arcTo)

    方法预览:

    // addArc
    public void addArc (RectF oval, float startAngle, float sweepAngle)
    // arcTo
    public void arcTo (RectF oval, float startAngle, float sweepAngle)
    public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
    

    这两类方法作用都是添加一个圆弧到path中,但既然存在两个方法,两者之间肯定是有区别的

    名称 作用 区别
    addArc 添加一个圆弧到path 直接添加一个圆弧到path中
    arcTo 添加一个圆弧到path 添加一个圆弧到path,如果圆弧的起点和上次最后一个坐标点不相同,就连接两个点

    方法参数表:

    参数 摘要
    oval 圆弧的外切矩形。
    startAngle 开始角度
    sweepAngle 扫过角度(-360 <= sweepAngle <360)
    forceMoveTo 是否强制使用MoveTo

    forceMoveTo是什么作用呢?
    这个变量意思为“是否强制使用moveTo”,也就是说,是否使用moveTo将变量移动到圆弧的起点位移,也就意味着:

    forceMoveTo 含义 等价方法
    true 将最后一个点移动到圆弧起点,即不连接最后一个点与圆弧起点 public void addArc (RectF oval, float startAngle, float sweepAngle)
    false 不移动,而是连接最后一个点与圆弧起点 public void arcTo (RectF oval, float startAngle, float sweepAngle)

    示例(addArc):

    canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
    canvas.scale(1,-1);                         // <-- 注意 翻转y坐标轴
    
    Path path = new Path();
    path.lineTo(100,100);
    
    RectF oval = new RectF(0,0,300,300);
    
    path.addArc(oval,0,270);
    // path.arcTo(oval,0,270,true);             // <-- 和上面一句作用等价
    
    canvas.drawPath(path,mPaint);
    

    示例(arcTo):

    canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
    canvas.scale(1,-1);                         // <-- 注意 翻转y坐标轴
    
    Path path = new Path();
    path.lineTo(100,100);
    
    RectF oval = new RectF(0,0,300,300);
    
    path.arcTo(oval,0,270);
    // path.arcTo(oval,0,270,false);             // <-- 和上面一句作用等价
    
    canvas.drawPath(path,mPaint);
    

    第3组:isEmpty、 isRect、isConvex、 set 和 offset

    isEmpty
    方法预览:

    public boolean isEmpty ()
    

    判断path中是否包含内容。

    Path path = new Path();
    Log.i("breeze", "onDraw: " + path.isEmpty());
    path.lineTo(100, 100);
    Log.i("breeze", "onDraw: " + path.isEmpty());
    

    输出结果

    I/breeze: onDraw: true
    I/breeze: onDraw: false
    

    isRect
    方法预览:

    public boolean isRect (RectF rect)
    

    判断path是否是一个矩形,如果是一个矩形的话,会将矩形的信息存放进参数rect中。

    set
    方法预览:

    public void set (Path src)
    

    将新的path赋值到现有path,现有path原有内容会清除。
    offset
    方法预览:

    public void offset (float dx, float dy)
    public void offset (float dx, float dy, Path dst)
    

    作用很简单,就是对path进行一段平移,它和Canvas中的translate作用很像,但Canvas作用于整个画布,而path的offset只作用于当前path。
    但是第二个方法最后怎么会有一个path作为参数?
    其实第二个方法中最后的参数dst是存储平移后的path的,不影响当前的path。

    代码示例:

    canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
    canvas.scale(1,-1);                         // <-- 注意 翻转y坐标轴
    
    Path path = new Path();                     // path中添加一个圆形(圆心在坐标原点)
    path.addCircle(0,0,100, Path.Direction.CW);
    
    Path dst = new Path();                      // dst中添加一个矩形
    dst.addRect(-200,-200,200,200, Path.Direction.CW);
    
    path.offset(300,0,dst);                     // 平移
    
    canvas.drawPath(path,mPaint);               // 绘制path
    
    mPaint.setColor(Color.BLUE);                // 更改画笔颜色
    
    canvas.drawPath(dst,mPaint);                // 绘制dst
    

    从运行效果图可以看出,虽然我们在dst中添加了一个矩形,但是并没有表现出来,所以,当dst中存在内容时,dst中原有的内容会被清空,而存放平移后的path。

    相关文章

      网友评论

          本文标题:Android自定义View-Path基础

          本文链接:https://www.haomeiwen.com/subject/mfgrectx.html