請(qǐng)尊重原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處【tianyl】的博客
關(guān)于的Android之玩轉(zhuǎn)View目錄
前言
在了解了Paint和Canvas之后诉儒,接下來(lái)就來(lái)說(shuō)是Path,我是比較喜歡稱Paint籍救、Canvas和Path為自定義繪制View時(shí)的三劍客邻寿,主要是它們?cè)诶L制我們想要View時(shí),能起到非常重要的作用涮雷。
接下來(lái)阵面,就說(shuō)說(shuō)Path的一些用法
1 Path的基礎(chǔ)用法
對(duì)于Path的基礎(chǔ)操作,我比較喜歡歸納為點(diǎn)操作洪鸭、線操作和圖形操作
1.1 點(diǎn)操作
點(diǎn)操作的api主要有moveTo样刷、rMoveTo,其實(shí)這兩個(gè)方法的作用比較類似览爵,只有細(xì)微的不同
- moveTo (x,y):移動(dòng)下一個(gè)操作的起始點(diǎn)到坐標(biāo)點(diǎn)(x,y)
例如:對(duì)于一個(gè)Path颂斜,它的默認(rèn)起始點(diǎn)是(0,0),即屏幕左上角拾枣,當(dāng)使用了moveTo(100,100)沃疮,后,它的起始點(diǎn)就變成了坐標(biāo)點(diǎn)(100,100)的位置
- rMoveTo(x,y):移動(dòng)下一個(gè)操作的起始點(diǎn)到當(dāng)前點(diǎn)的相對(duì)位置的坐標(biāo)(x,y)梅肤,這個(gè)r就是relative的首字母司蔬,它表示相對(duì)位置
例如:對(duì)于一個(gè)Path,它的默認(rèn)起始點(diǎn)是(0,0)姨蝴,即屏幕左上角俊啼,當(dāng)使用了moveTo(100,100),后左医,它的起始點(diǎn)就變成了坐標(biāo)點(diǎn)(100,100)的位置授帕,這個(gè)時(shí)候,如果我們?cè)偈褂胢oveTo(100,100)浮梢,就沒(méi)有任何效果跛十,因?yàn)槲覀兊钠鹗键c(diǎn)已經(jīng)在坐標(biāo)點(diǎn)(100,100)上了,如果用rMoveTo(100,100)秕硝,那么起始點(diǎn)就會(huì)變成(200,200)芥映,這就是rMoveTo的用途了
1.2 線操作
說(shuō)完了點(diǎn)操作,那么線操作也就比較簡(jiǎn)單了,線操作的api也是兩個(gè)lineTo奈偏、rLineTo
lineTo (x,y):從起始點(diǎn)(默認(rèn)是坐標(biāo)原點(diǎn)(0,0))到坐標(biāo)點(diǎn)(x,y)繪制一條線
rLineTo(x,y):從起始點(diǎn)(默認(rèn)是坐標(biāo)原點(diǎn)(0,0))到相對(duì)坐標(biāo)點(diǎn)(x,y)繪制一條線坞嘀,這個(gè)相對(duì)坐標(biāo)也是當(dāng)前的起始點(diǎn),比如當(dāng)前起始點(diǎn)是坐標(biāo)點(diǎn)(100,100),那么實(shí)際的結(jié)束點(diǎn)就是坐標(biāo)點(diǎn)(100+x,100+y)
除此之外惊来,還有setLastPoint和close兩個(gè)api
- setLastPoint(dx, dy):改變之前操作的終點(diǎn)的位置
例如:
//初始化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);
這段代碼等價(jià)于
//初始化Path
Path path = new Path();
//畫從(0,0)到(100,400)之間的直線
path.lineTo(100, 800);
path.lineTo(400, 800);
canvas.drawPath(path, mPaint);
關(guān)于對(duì)api setLastPoint的解釋丽涩,我查了一下資料,網(wǎng)上說(shuō)moveTo影響的是后面操作的起點(diǎn)位置裁蚁。不會(huì)影響之前的操作矢渊;而 setLastPoint改變前一步操作最后一個(gè)點(diǎn)的位置,不僅影響前一步操作厘擂,同一時(shí)候也會(huì)影響后一步操作昆淡。其實(shí)我感覺(jué)因?yàn)樗梢灾苯颖恢暗膌ineTo取代,即直接改lineTo的傳遞的參數(shù)即可刽严,所以這個(gè)方法我用得并不多
還有一個(gè)方法
- clost():封閉當(dāng)前的繪制路徑昂灵,這個(gè)api很簡(jiǎn)單,也算比較常用
1.3 圖形操作
接下來(lái)就是Path的圖形操作了舞萄,也是我們經(jīng)常用來(lái)進(jìn)行圖形繪制的api眨补,它們分別是
繪制圓:addCircle
繪制橢圓:addOval
繪制矩形:addRect
繪制圓角矩形:addRoundRect
對(duì)于這些api,除了它們各自特定的參數(shù)之外倒脓,還會(huì)有一個(gè)相同的參數(shù)Direction
對(duì)于參數(shù)Direction撑螺,它有兩個(gè)選項(xiàng)
Direction.CW:按照逆時(shí)針?lè)较蚶L制
Direction.CCW:按照順時(shí)針?lè)较蚶L制
對(duì)于這個(gè)參數(shù),在大多數(shù)情況下崎弃,我們使用順時(shí)針繪制和逆時(shí)針繪制都不會(huì)影響最終結(jié)果甘晤,只是在少數(shù)情況下,比如如果使用了setLastPoint這個(gè)方法饲做,那么就會(huì)因?yàn)槔L制方向的不同得到不同的結(jié)果线婚,或者我們需要根據(jù)繪制的方向來(lái)自定義一些動(dòng)畫,或者使用繪制的路徑盆均,這時(shí)也需要考慮我們繪制的方向造成的影像塞弊。
2 貝塞爾曲線
說(shuō)完了Path的基礎(chǔ)api,再說(shuō)說(shuō)Path稍微難一點(diǎn)的用法泪姨,這也是Android中實(shí)現(xiàn)一些比較炫酷特效的方法——貝塞爾曲線游沿。
說(shuō)到貝塞爾曲線,就先簡(jiǎn)單的介紹一下什么是貝塞爾曲線
2.1 介紹
關(guān)于貝塞爾曲線的介紹肮砾,這里參考維基百科
線性曲線
線性貝塞爾曲線函數(shù)中的t會(huì)經(jīng)過(guò)由P0至P1的B(t)所描述的曲線诀黍。例如當(dāng)t=0.25時(shí),B(t)即一條由點(diǎn)P0至P1路徑的四分之一處唇敞。就像由0至1的連續(xù)t蔗草,B(t)描述一條由P0至P1的直線
二次曲線
為建構(gòu)二次貝塞爾曲線咒彤,可以中介點(diǎn)Q0和Q1作為由0至1的t:
由P0至P1的連續(xù)點(diǎn)Q0疆柔,描述一條線性貝塞爾曲線咒精。
由P1至P2的連續(xù)點(diǎn)Q1,描述一條線性貝塞爾曲線旷档。
由Q0至Q1的連續(xù)點(diǎn)B(t)模叙,描述一條二次貝塞爾曲線。
高階曲線
為建構(gòu)高階曲線鞋屈,便需要相應(yīng)更多的中介點(diǎn)范咨。對(duì)于三次曲線,可由線性貝塞爾曲線描述的中介點(diǎn)Q0厂庇、Q1渠啊、Q2,和由二次曲線描述的點(diǎn)R0权旷、R1所建構(gòu)
2.2 Android 中的貝塞爾曲線
介紹了貝塞爾曲線的基礎(chǔ)知識(shí)替蛉,接下來(lái)說(shuō)說(shuō)Android中的貝塞爾曲線,Android系統(tǒng)已經(jīng)封裝好了二次貝塞爾曲線和三次貝塞爾曲線的api拄氯,我們可以直接使用
二次貝塞爾曲線:quadTo()
三次貝塞爾曲線:cubicTo()
這兩個(gè)api就是Android系統(tǒng)提供給我們使用的api躲查,當(dāng)然,如果想要使用更高階的貝塞爾曲線译柏,那么就需要自己去實(shí)現(xiàn)了(一般情況下镣煮,這兩個(gè)api就已經(jīng)夠用了),接下來(lái)我就通過(guò)一個(gè)簡(jiǎn)單水波紋的例子鄙麦,說(shuō)明貝塞爾曲線在Android中的應(yīng)用
2.2.1 通過(guò)貝塞爾曲線實(shí)現(xiàn)水波紋效果
首先典唇,在寫代碼之前,我們要先確定如何通過(guò)一個(gè)貝塞爾曲線繪制出一個(gè)水波紋胯府,首先介衔,我們需要繪制出一個(gè)波,然后我們對(duì)這個(gè)波進(jìn)行移動(dòng)盟劫,那么就是我們想要的動(dòng)畫效果了
如圖所示
這是我們實(shí)現(xiàn)水波紋的基本思路
首先我們移動(dòng)到波的起始點(diǎn)(x夜牡,y)
假設(shè)一個(gè)波的完整長(zhǎng)度是waveLength,波的高度是waveHeight
首先從波的起始點(diǎn)(x侣签,y)塘装,控制點(diǎn)為1,繪制貝塞爾曲線到中間點(diǎn)(x1影所,y1)蹦肴,然后在使用控制點(diǎn)2,從中間點(diǎn)繪制貝塞爾曲線到結(jié)束點(diǎn)(x2猴娩,y2)
假設(shè)這個(gè)波的一個(gè)規(guī)則的正弦波阴幌,那么起始點(diǎn)的坐標(biāo)為(0勺阐,height/2),控制點(diǎn)1的坐標(biāo)為(waveLength/4矛双,0)渊抽,中間點(diǎn)的坐標(biāo)為(waveHeight/2,height/2)议忽,控制點(diǎn)2的坐標(biāo)為(3*waveLength/4懒闷,height),結(jié)束點(diǎn)的坐標(biāo)為(waveLength栈幸,height/2)
封閉繪制路徑
具體代碼如下
Path path = new Path();
//步驟1
path.moveTo(0, height/2);
//步驟3 注意這里使用的是rQuadTo愤估,也就是相對(duì)當(dāng)前的位置
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);
具體效果如下(為了效果突出,所以設(shè)置的波高較大))
- 完成了靜態(tài)的波速址,接下來(lái)就只需要將這個(gè)波進(jìn)行平移即可玩焰,之前我們波的起始點(diǎn)是(x,y)芍锚,我們通過(guò)不斷的平移這個(gè)起始點(diǎn)昔园,就可以實(shí)現(xiàn)動(dòng)畫的效果,當(dāng)然為了避免邊界的空白闹炉,我們一般可以從view外的開(kāi)始繪制蒿赢,然后超出view,這樣就通過(guò)一個(gè)貝塞爾曲線實(shí)現(xiàn)了水波紋效果
//這里移動(dòng)到-waveLength是為了超出屏幕外渣触,避免動(dòng)畫的時(shí)候出現(xiàn)空白羡棵,offsetX就是波的偏移量,只要不斷的修改這個(gè)偏移量嗅钻,就能實(shí)現(xiàn)動(dòng)畫效果
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);
}
下面是添加了動(dòng)畫效果后的完整draw代碼
private void drawWave(Canvas canvas) {
Path path = new Path();
path.moveTo(-mWaveWidth + offsetX, mHeight / 2);
//注意這里使用的是rQuadTo皂冰,也就是相對(duì)當(dāng)前的位置
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);
}
效果如下