Android之玩转View(八):Path

请尊重原创,转载请注明出处【tianyl】的博客

关于的Android之玩转View目录

前言

在了解了Paint和Canvas之后,接下来就来说是Path,我是比较喜欢称Paint、Canvas和Path为自定义绘制View时的三剑客,主要是它们在绘制我们想要View时,能起到非常重要的作用。

接下来,就说说Path的一些用法

1 Path的基础用法

对于Path的基础操作,我比较喜欢归纳为点操作、线操作和图形操作

1.1 点操作

点操作的api主要有moveTo、rMoveTo,其实这两个方法的作用比较类似,只有细微的不同

  • moveTo (x,y):移动下一个操作的起始点到坐标点(x,y)

例如:对于一个Path,它的默认起始点是(0,0),即屏幕左上角,当使用了moveTo(100,100),后,它的起始点就变成了坐标点(100,100)的位置

  • rMoveTo(x,y):移动下一个操作的起始点到当前点的相对位置的坐标(x,y),这个r就是relative的首字母,它表示相对位置

例如:对于一个Path,它的默认起始点是(0,0),即屏幕左上角,当使用了moveTo(100,100),后,它的起始点就变成了坐标点(100,100)的位置,这个时候,如果我们再使用moveTo(100,100),就没有任何效果,因为我们的起始点已经在坐标点(100,100)上了,如果用rMoveTo(100,100),那么起始点就会变成(200,200),这就是rMoveTo的用途了

1.2 线操作

说完了点操作,那么线操作也就比较简单了,线操作的api也是两个lineTo、rLineTo

  • lineTo (x,y):从起始点(默认是坐标原点(0,0))到坐标点(x,y)绘制一条线

  • rLineTo(x,y):从起始点(默认是坐标原点(0,0))到相对坐标点(x,y)绘制一条线,这个相对坐标也是当前的起始点,比如当前起始点是坐标点(100,100),那么实际的结束点就是坐标点(100+x,100+y)

除此之外,还有setLastPoint和close两个api

  • setLastPoint(dx, dy):改变之前操作的终点的位置

例如:


//初始化Path

Path path = new Path();

//画从(0,0)到(400,400)之间的直线

path.lineTo(400, 400);

//新加的setLastPoint

path.setLastPoint(100, 800);

path.lineTo(400, 800);

canvas.drawPath(path, mPaint);

这段代码等价于


//初始化Path

Path path = new Path();

//画从(0,0)到(100,400)之间的直线

path.lineTo(100, 800);

path.lineTo(400, 800);

canvas.drawPath(path, mPaint);

关于对api setLastPoint的解释,我查了一下资料,网上说moveTo影响的是后面操作的起点位置。不会影响之前的操作;而 setLastPoint改变前一步操作最后一个点的位置,不仅影响前一步操作,同一时候也会影响后一步操作。其实我感觉因为它可以直接被之前的lineTo取代,即直接改lineTo的传递的参数即可,所以这个方法我用得并不多

还有一个方法

  • clost():封闭当前的绘制路径,这个api很简单,也算比较常用

1.3 图形操作

接下来就是Path的图形操作了,也是我们经常用来进行图形绘制的api,它们分别是

  • 绘制圆:addCircle

  • 绘制椭圆:addOval

  • 绘制矩形:addRect

  • 绘制圆角矩形:addRoundRect

对于这些api,除了它们各自特定的参数之外,还会有一个相同的参数Direction

对于参数Direction,它有两个选项

  • Direction.CW:按照逆时针方向绘制

  • Direction.CCW:按照顺时针方向绘制

对于这个参数,在大多数情况下,我们使用顺时针绘制和逆时针绘制都不会影响最终结果,只是在少数情况下,比如如果使用了setLastPoint这个方法,那么就会因为绘制方向的不同得到不同的结果,或者我们需要根据绘制的方向来自定义一些动画,或者使用绘制的路径,这时也需要考虑我们绘制的方向造成的影像。

2 贝塞尔曲线

说完了Path的基础api,再说说Path稍微难一点的用法,这也是Android中实现一些比较炫酷特效的方法——贝塞尔曲线。

说到贝塞尔曲线,就先简单的介绍一下什么是贝塞尔曲线

2.1 介绍

关于贝塞尔曲线的介绍,这里参考维基百科

线性曲线

线性贝塞尔曲线函数中的t会经过由P0至P1的B(t)所描述的曲线。例如当t=0.25时,B(t)即一条由点P0至P1路径的四分之一处。就像由0至1的连续t,B(t)描述一条由P0至P1的直线

一阶贝塞尔曲线

二次曲线

为建构二次贝塞尔曲线,可以中介点Q0和Q1作为由0至1的t:

由P0至P1的连续点Q0,描述一条线性贝塞尔曲线。

由P1至P2的连续点Q1,描述一条线性贝塞尔曲线。

由Q0至Q1的连续点B(t),描述一条二次贝塞尔曲线。

二阶贝塞尔曲线

高阶曲线

为建构高阶曲线,便需要相应更多的中介点。对于三次曲线,可由线性贝塞尔曲线描述的中介点Q0、Q1、Q2,和由二次曲线描述的点R0、R1所建构


三阶贝塞尔曲线

2.2 Android 中的贝塞尔曲线

介绍了贝塞尔曲线的基础知识,接下来说说Android中的贝塞尔曲线,Android系统已经封装好了二次贝塞尔曲线和三次贝塞尔曲线的api,我们可以直接使用

  • 二次贝塞尔曲线:quadTo()

  • 三次贝塞尔曲线:cubicTo()

这两个api就是Android系统提供给我们使用的api,当然,如果想要使用更高阶的贝塞尔曲线,那么就需要自己去实现了(一般情况下,这两个api就已经够用了),接下来我就通过一个简单水波纹的例子,说明贝塞尔曲线在Android中的应用

2.2.1 通过贝塞尔曲线实现水波纹效果

首先,在写代码之前,我们要先确定如何通过一个贝塞尔曲线绘制出一个水波纹,首先,我们需要绘制出一个波,然后我们对这个波进行移动,那么就是我们想要的动画效果了

如图所示

设计思路

这是我们实现水波纹的基本思路

  1. 首先我们移动到波的起始点(x,y)

  2. 假设一个波的完整长度是waveLength,波的高度是waveHeight

  3. 首先从波的起始点(x,y),控制点为1,绘制贝塞尔曲线到中间点(x1,y1),然后在使用控制点2,从中间点绘制贝塞尔曲线到结束点(x2,y2)

  4. 假设这个波的一个规则的正弦波,那么起始点的坐标为(0,height/2),控制点1的坐标为(waveLength/4,0),中间点的坐标为(waveHeight/2,height/2),控制点2的坐标为(3*waveLength/4,height),结束点的坐标为(waveLength,height/2)

  5. 封闭绘制路径

具体代码如下


Path path = new Path();

//步骤1

path.moveTo(0, height/2);

//步骤3 注意这里使用的是rQuadTo,也就是相对当前的位置

path.rQuadTo(waveLength / 4, -height/2, waveLength / 2,0);

path.rQuadTo(waveLength / 4, height/2, waveLength / 2, 0);

//步骤4

path.lineTo(mWidth, mHeight);

path.lineTo(0, mHeight);

path.close();

canvas.drawPath(path, mPaint);

具体效果如下(为了效果突出,所以设置的波高较大))

效果图
  1. 完成了静态的波,接下来就只需要将这个波进行平移即可,之前我们波的起始点是(x,y),我们通过不断的平移这个起始点,就可以实现动画的效果,当然为了避免边界的空白,我们一般可以从view外的开始绘制,然后超出view,这样就通过一个贝塞尔曲线实现了水波纹效果

//这里移动到-waveLength是为了超出屏幕外,避免动画的时候出现空白,offsetX就是波的偏移量,只要不断的修改这个偏移量,就能实现动画效果
path.moveTo(-waveLength + offsetX, mHeight / 2);
//绘制到超出屏幕即可
for (int i = -waveLength; i < getWidth() + waveLength; i += waveLength) {
   path.rQuadTo(waveLength / 4, -height/2, waveLength / 2,0);
   path.rQuadTo(waveLength / 4, height/2, waveLength / 2, 0);
}

下面是添加了动画效果后的完整draw代码

private void drawWave(Canvas canvas) {
    Path path = new Path();
    path.moveTo(-mWaveWidth + offsetX, mHeight / 2);
    //注意这里使用的是rQuadTo,也就是相对当前的位置
    for (int i = -mWaveWidth; i < getWidth() + mWaveWidth; i += mWaveWidth) {
        path.rQuadTo(mWaveWidth / 4, -mHeight / 2, mWaveWidth / 2, 0);
        path.rQuadTo(mWaveWidth / 4, mHeight / 2, mWaveWidth / 2, 0);
    }
    path.lineTo(mWaveWidth, mHeight);
    path.lineTo(0, mHeight);
    path.close();
    canvas.drawPath(path, mPaint);
}

效果如下


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