Android之玩轉(zhuǎn)View(八):Path

請(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();

//畫(huà)從(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();

//畫(huà)從(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)畫(huà),或者使用繪制的路徑,這時(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)畫(huà)效果了

如圖所示

設(shè)計(jì)思路

這是我們實(shí)現(xiàn)水波紋的基本思路

  1. 首先我們移動(dòng)到波的起始點(diǎn)(x,y)

  2. 假設(shè)一個(gè)波的完整長(zhǎng)度是waveLength,波的高度是waveHeight

  3. 首先從波的起始點(diǎn)(x,y),控制點(diǎn)為1,繪制貝塞爾曲線到中間點(diǎn)(x1,y1),然后在使用控制點(diǎn)2,從中間點(diǎn)繪制貝塞爾曲線到結(jié)束點(diǎn)(x2,y2)

  4. 假設(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)

  5. 封閉繪制路徑

具體代碼如下


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è)置的波高較大))

效果圖
  1. 完成了靜態(tài)的波,接下來(lái)就只需要將這個(gè)波進(jìn)行平移即可,之前我們波的起始點(diǎn)是(x,y),我們通過(guò)不斷的平移這個(gè)起始點(diǎn),就可以實(shí)現(xiàn)動(dòng)畫(huà)的效果,當(dāng)然為了避免邊界的空白,我們一般可以從view外的開(kāi)始繪制,然后超出view,這樣就通過(guò)一個(gè)貝塞爾曲線實(shí)現(xiàn)了水波紋效果

//這里移動(dòng)到-waveLength是為了超出屏幕外,避免動(dòng)畫(huà)的時(shí)候出現(xiàn)空白,offsetX就是波的偏移量,只要不斷的修改這個(gè)偏移量,就能實(shí)現(xiàn)動(dòng)畫(huà)效果
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)畫(huà)效果后的完整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);
}

效果如下


水波紋
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容