自定义View(Path一)

上一篇内容自定义View(Canvas)
http://www.jianshu.com/p/d25fb10ad34e

Path这东西,这东西太麻烦了,牵扯的东西很多。但是又特别重要,所以一定要写,思路不清晰的地方回头再整理吧。

前面我们已经用canvas的drawXxx()方法绘制了一些简单的基本图形,但这显然远远不能满足我们的需求。比如我们想绘制一些稍微复杂一点(如绘制一个心形 正多边形 五角星等)的图形就没法去绘制,而使用Path不仅能够绘制简单图形,也可以绘制这些比较复杂的图形。另外,根据路径绘制文本和剪裁画布都会用到Path。以下是Path类中常用到的方法:

作用 相关方法 备注
移动起点 moveTo 移动下一次操作的起点位置
设置终点 setLastPoint 重置当前path中最后一个点位置,如果在绘制之前调用,效果和moveTo相同
连接直线 lineTo 添加上一个点到当前点之间的直线到Path
闭合路径 close 连接第一个点连接到最后一个点,形成一个闭合区域
添加内容 addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo 添加(矩形, 圆角矩形, 椭圆, 圆, 路径, 圆弧) 到当前Path (注意addArc和arcTo的区别)
是否为空 isEmpty 判断Path是否为空
是否为矩形 isRect 判断path是否是一个矩形
替换路径 set 用新的路径替换到当前路径所有内容
偏移路径 offset 对当前路径之前的操作进行偏移(不会影响之后的操作)
贝塞尔曲线 quadTo, cubicTo 分别为二次和三次贝塞尔曲线的方法
rXxx方法 rMoveTo, rLineTo, rQuadTo, rCubicTo 不带r的方法是基于原点的坐标系(偏移量), rXxx方法是基于当前点坐标系(偏移量)
填充模式 setFillType, getFillType, isInverseFillType, toggleInverseFillType 设置,获取,判断和切换填充模式
提示方法 incReserve 提示Path还有多少个点等待加入(这个方法貌似会让Path优化存储结构)
布尔操作(API19) op 对两个Path进行布尔运算(即取交集、并集等操作)
计算边界 computeBounds 计算Path的边界
重置路径 reset, rewind 清除Path中的内容 reset不保留内部数据结构,但会保留FillType rewind会保留内部的数据结构,但不保留FillType
矩阵操作 transform 矩阵变换

注意:我们可以把path看成是存储操作轨迹的一个容器,我们把希望进行的操作轨迹记录在path中。由Canvas的drawPath()方法把path记录的图形真正绘制到画布上

然后我们简单的把path分一个类:

接下来看看path的一些主要方法

xxxTo()系列

xxxTo家族成员主要有:

public void moveTo(float x, float y)
public void lineTo(float x, float y)
public void quadTo(float x1, float y1, float x2, float y2)
public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)  
public void arcTo (RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)  

moveTo()

将绘制的起点移动到某处,打个比方,比如要写字,moveTo()方法则是把笔尖移动到纸的相应位置,准备开始书写

lineTo()

在上一次操作结束的点与参数坐标点之间连一条线

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);

        //绘制坐标系
        float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
        mPaint.setColor(Color.BLACK);
        canvas.drawLines(pts,mPaint);

        Path path = new Path();// 创建Path

        path.moveTo(100,100);//移动绘制点
        path.lineTo(200,100);//从(100,100)位置画一条线到(200,100)位置
        path.lineTo(200,200);//从(200,100)位置画一条线到(200,200)位置
        //path.close();闭合区域

        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(15);
        canvas.drawPath(path, mPaint);// 绘制Path
    }

通过两次lineTo()方法绘制了两条线段:


讲到这里顺带提一手close()这个方法。close()方法没有参数,作用是连接第一个点连接到最后一个点,形成一个闭合区域。去掉

//path.close();

这一句代码的注释,显示如下:


注:如果Paint的填充模式是FILL,即mPaint.setStyle(Paint.Style.FILL);则即使是开放型path,也会自动填充,效果和close()后的效果一致

arcTo()

这是一个画弧线的方法,原理是从一个椭圆上截取一段弧

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

这个方法(还有两个重载方法,大同小异,这里只讲这一个)有四个参数:
RectF oval:用于确定椭圆(重要说明:该RectF 的4个参数必须遵从left,top,right,bottom的顺序,否则无法画出弧线)
float startAngle:起始角度
float sweepAngle:扫过的角度,顺时针方向为正
boolean forceMoveTo:用于确定是否与上一点连接(是否抬起笔)

这样说可能有点抽象,看代码:

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);

        //绘制坐标系
        float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
        mPaint.setColor(Color.BLACK);
        canvas.drawLines(pts,mPaint);

        RectF rectF = new RectF(0,0,300,200);//用于绘制椭圆

        Path path = new Path();// 创建Path

        path.moveTo(0,0);
        path.arcTo(rectF,0,60,false);//截取0度到60度之间的弧

        mPaint.setColor(Color.RED);
        canvas.drawPath(path, mPaint);// 绘制Path
    }

因为这里forceMoveTo参数设置为false,所以不抬起笔(连接上一点)。



如果把forceMoveTo参数设置为true(抬起笔):

最后,quadTo()和cubicTo()方法是分别绘制二阶和三阶贝塞尔曲线的方法,我们先了解一下贝塞尔曲线

什么叫贝赛尔曲线?其实很简单,使用三个或多个点来确定的一条曲线,贝塞尔曲线在图形图像学中有相当重要的地位,Path中也提供了一些方法来给我们模拟低阶贝赛尔曲线。
贝塞尔曲线的定义也比较简单,你只需要一个起点、一个终点和至少零个控制点则可定义一个贝赛尔曲线,当控制点为零时,只有起点和终点,此时的曲线说白了就是一条线段,我们称之为一阶贝赛尔曲线。
PS:以下图片和公式均来自维基百科和互联网

一阶贝赛尔曲线:


其公式可概括为:

其中B(t)为时间为t时点的坐标,P0为起点、Pn为终点
贝塞尔曲线于1962年由法国数学家Pierre Bézier第一次研究使用并给出了详细的计算公式,So该曲线也是由其名字命名。Path中给出的quadTo方法属于

二阶贝赛尔曲线:


二阶贝赛尔曲线的一个明显特征是其拥有一个控制点,大家可以这样想想贝赛尔曲线,在一根两端固定橡皮筋上有一块磁铁,现在我们拿另一块磁铁去吸引橡皮筋上的磁铁,因为引力,橡皮筋会随着我们手上磁铁的移动而改变形状,又因为橡皮筋的张力让束缚在橡皮筋上的磁铁不会轻易吸附到我们手上的磁铁,这时橡皮筋的状态就可以看成是一条贝塞尔曲线,而我们手中的磁铁就是一个控制点,通过这个控制点我们“拉扯”橡皮筋的曲度。
二阶贝赛尔曲线的公式为:

同样的,Path中也提供了三阶贝塞尔曲线的方法cubicTo,按照上面我们的推论,三阶应该是有两个控制点才对对吧

三阶贝赛尔曲线:


公式:

高阶贝赛尔曲线在Path中没有对应的方法,对我们来说三阶也足够了,不过大家可以了解下,难得我在墙外找到如此动感的贝赛尔曲线高清无码动图

高阶贝塞尔曲线:
四阶:


五阶:

贝塞尔曲线通用公式:

quadTo()

quadTo方法有四个参数,前两个参数为控制点的坐标,后两个参数为终点坐标,至于起点默认是画布的左上角。这里的屏幕中心就是起点,(x1,y1)就是中点P1,(x2,y2)就是末端点P2。
看一下代码:

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);

        //绘制坐标系
        float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
        mPaint.setColor(Color.BLACK);
        canvas.drawLines(pts,mPaint);

        Path path = new Path();// 创建Path

        path.quadTo(100,100,200,0);//绘制二阶贝塞尔曲线

        mPaint.setColor(Color.RED);
        canvas.drawPath(path, mPaint);// 绘制Path

        mPaint.setColor(Color.BLUE);
        mPaint.setStrokeWidth(15);//加粗 便于显示
        canvas.drawPoint(0,0,mPaint);//绘制起点
        canvas.drawPoint(100,100,mPaint);//绘制控制点
        canvas.drawPoint(200,0,mPaint);//绘制终点
    }

cubicTo

使用方法与quadTo()方法相同,前4个参数分别为两个控制点,最后两个参数是终点

总结一下:xxxTo()方法的作用是从某一位置连接(除moveTo)到另一位置。

addXxx()系列

add家族成员主要有:addRect, addRoundRect, addOval, addCircle, addPath, addArc
前面几个方法小伙伴们一看就知道啥东西了。和上一期讲的内容相似,只不过是把基础图形添加到path中绘制。
以addRect为例,

public void addRect(RectF rect, Direction dir)

第二个参数是一个枚举,有Path.Direction.CW和Path.Direction.CCW两种选择,分别代表顺时针和逆时针的闭合方向(所有add封闭型path都需要这个参数)。什么叫闭合方向呢?光说大家一定会蒙,有学习激情的童鞋看到后肯定会马上敲代码试验一下两者的区别,可是不管你如何改,单独地在一条闭合曲线上你是看不出所谓闭合方向的区别的。为了弄懂它,我们在path上绘制一些文字来说明最后参数的意义:

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);

        //绘制坐标系
        float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
        mPaint.setColor(Color.BLACK);
        canvas.drawLines(pts,mPaint);

        Path path = new Path();// 创建Path
        path.addRect(100,100,300,200, Path.Direction.CW);

        mPaint.setTextSize(50);
        // 绘制路径上的文字
        canvas.drawTextOnPath("abcdefghijklmn", path, 0, 0, mPaint);
    }

顺时针

逆时针

addArc()

这东西咋一看和arcTo()很像,没错,事实上也差不多。
addArc(参数一,参数二,参数三)等价于arcTo(参数一,参数二,参数三,true);

addPath()

顾名思义,把一个path添加到另一个path中:

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);

        //绘制坐标系
        float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
        mPaint.setColor(Color.BLACK);
        canvas.drawLines(pts,mPaint);

        RectF rectF = new RectF(100,100,400,300);

        Path path = new Path();
        path.addRect(rectF, Path.Direction.CW);

        Path path1 = new Path();
        path1.addOval(rectF, Path.Direction.CW);

        path1.addPath(path);
//        path1.addPath(path,100,100);//后两个参数控制平移

        canvas.drawPath(path1,mPaint);
    }

结果如下:

如果添加上两个参数,则在X轴和Y轴方向上分别平移了100px:


rXXXTo()系列

rXXXTo()系列基本上是XXXTo()系列的小弟

public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)  
public void rLineTo(float dx, float dy)  
public void rMoveTo(float dx, float dy)  
public void rQuadTo(float dx1, float dy1, float dx2, float dy2)  

这一系列rXXXTo方法其实跟上面的那些XXXTo差不多的,唯一的不同是rXXXTo方法的参考坐标是相对的而XXXTo方法的参考坐标始终是参照画布原点坐标,什么意思呢?举个简单的例子:

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);

        //绘制坐标系
        float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
        mPaint.setColor(Color.BLACK);
        canvas.drawLines(pts,mPaint);

        Path path = new Path();
        path.moveTo(100,100);
        path.lineTo(200,200);
        //path.rLineTo(200,200);

        canvas.drawPath(path,mPaint);
    }

把path.lineTo(200,200);换成path.rLineTo(200,200);结果如下:


线段长了很多,因为这里的(200,200)是相对于开始点(100,100)来说的,是相对坐标。如果换算成绝对坐标就是绘制一条(100,100)到(300,300)之间的线段。其实,这个前缀“r”也就是relative(相对)的简写!

其他几个比较重要的方法

setLastPoint()

重置当前path中最后一个点位置,会对上一次的绘制造成影响。

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);

        //绘制坐标系
        float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
        mPaint.setColor(Color.BLACK);
        canvas.drawLines(pts,mPaint);

        Path path = new Path();
        path.lineTo(200,200);
        path.setLastPoint(300,200);
        canvas.drawPath(path,mPaint);
        mPaint.setStrokeWidth(15);//加粗,便于显示
        mPaint.setColor(Color.RED);
        canvas.drawPoint(200,200,mPaint);
    }

虽然我们lineTo(200,200),但是紧接着调用setLastPoint(300,200)方法,把最后一个点移动到(300,200)的位置,所以所画的直线是从原点到(300,200)的位置


offset()

对当前路径之前的操作进行偏移(不会影响之后的操作)

//将新的path赋值到现有path
public void offset (float dx, float dy)
//将新的path赋值到dst,不影响现有path,当dst为null时,效果同上一种方法
public void offset (float dx, float dy, Path dst)

参数:
dx:X轴方向上的平移
dy:Y轴方向上的平移
dst:用于储存平移后的结果

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);

        //绘制坐标系
        float [] pts = new float[]{0,0,mWidth/2,0,0,0,0,mHeight/2};
        mPaint.setColor(Color.BLACK);
        canvas.drawLines(pts,mPaint);

        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
    }
1500287070(1).jpg

结语

剩余的一些方法比较简单,查看上面的表就可以了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容