自定義View(Path一)

上一篇內(nèi)容自定義View(Canvas)
http://www.lxweimin.com/p/d25fb10ad34e

Path這東西,這東西太麻煩了,牽扯的東西很多。但是又特別重要,所以一定要寫,思路不清晰的地方回頭再整理吧。

前面我們已經(jīng)用canvas的drawXxx()方法繪制了一些簡單的基本圖形,但這顯然遠遠不能滿足我們的需求。比如我們想繪制一些稍微復(fù)雜一點(如繪制一個心形 正多邊形 五角星等)的圖形就沒法去繪制,而使用Path不僅能夠繪制簡單圖形,也可以繪制這些比較復(fù)雜的圖形。另外,根據(jù)路徑繪制文本和剪裁畫布都會用到Path。以下是Path類中常用到的方法:

作用 相關(guān)方法 備注
移動起點 moveTo 移動下一次操作的起點位置
設(shè)置終點 setLastPoint 重置當(dāng)前path中最后一個點位置,如果在繪制之前調(diào)用,效果和moveTo相同
連接直線 lineTo 添加上一個點到當(dāng)前點之間的直線到Path
閉合路徑 close 連接第一個點連接到最后一個點,形成一個閉合區(qū)域
添加內(nèi)容 addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo 添加(矩形, 圓角矩形, 橢圓, 圓, 路徑, 圓弧) 到當(dāng)前Path (注意addArc和arcTo的區(qū)別)
是否為空 isEmpty 判斷Path是否為空
是否為矩形 isRect 判斷path是否是一個矩形
替換路徑 set 用新的路徑替換到當(dāng)前路徑所有內(nèi)容
偏移路徑 offset 對當(dāng)前路徑之前的操作進行偏移(不會影響之后的操作)
貝塞爾曲線 quadTo, cubicTo 分別為二次和三次貝塞爾曲線的方法
rXxx方法 rMoveTo, rLineTo, rQuadTo, rCubicTo 不帶r的方法是基于原點的坐標系(偏移量), rXxx方法是基于當(dāng)前點坐標系(偏移量)
填充模式 setFillType, getFillType, isInverseFillType, toggleInverseFillType 設(shè)置,獲取,判斷和切換填充模式
提示方法 incReserve 提示Path還有多少個點等待加入(這個方法貌似會讓Path優(yōu)化存儲結(jié)構(gòu))
布爾操作(API19) op 對兩個Path進行布爾運算(即取交集、并集等操作)
計算邊界 computeBounds 計算Path的邊界
重置路徑 reset, rewind 清除Path中的內(nèi)容 reset不保留內(nèi)部數(shù)據(jù)結(jié)構(gòu),但會保留FillType rewind會保留內(nèi)部的數(shù)據(jù)結(jié)構(gòu),但不保留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()方法則是把筆尖移動到紙的相應(yīng)位置,準備開始書寫

lineTo()

在上一次操作結(jié)束的點與參數(shù)坐標點之間連一條線

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();// 創(chuàng)建Path

        path.moveTo(100,100);//移動繪制點
        path.lineTo(200,100);//從(100,100)位置畫一條線到(200,100)位置
        path.lineTo(200,200);//從(200,100)位置畫一條線到(200,200)位置
        //path.close();閉合區(qū)域

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

通過兩次lineTo()方法繪制了兩條線段:


講到這里順帶提一手close()這個方法。close()方法沒有參數(shù),作用是連接第一個點連接到最后一個點,形成一個閉合區(qū)域。去掉

//path.close();

這一句代碼的注釋,顯示如下:


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

arcTo()

這是一個畫弧線的方法,原理是從一個橢圓上截取一段弧

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

這個方法(還有兩個重載方法,大同小異,這里只講這一個)有四個參數(shù):
RectF oval:用于確定橢圓(重要說明:該RectF 的4個參數(shù)必須遵從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();// 創(chuàng)建Path

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

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

因為這里forceMoveTo參數(shù)設(shè)置為false,所以不抬起筆(連接上一點)。



如果把forceMoveTo參數(shù)設(shè)置為true(抬起筆):

最后,quadTo()和cubicTo()方法是分別繪制二階和三階貝塞爾曲線的方法,我們先了解一下貝塞爾曲線

什么叫貝賽爾曲線?其實很簡單,使用三個或多個點來確定的一條曲線,貝塞爾曲線在圖形圖像學(xué)中有相當(dāng)重要的地位,Path中也提供了一些方法來給我們模擬低階貝賽爾曲線。
貝塞爾曲線的定義也比較簡單,你只需要一個起點、一個終點和至少零個控制點則可定義一個貝賽爾曲線,當(dāng)控制點為零時,只有起點和終點,此時的曲線說白了就是一條線段,我們稱之為一階貝賽爾曲線。
PS:以下圖片和公式均來自維基百科和互聯(lián)網(wǎng)

一階貝賽爾曲線:


其公式可概括為:

其中B(t)為時間為t時點的坐標,P0為起點、Pn為終點
貝塞爾曲線于1962年由法國數(shù)學(xué)家Pierre Bézier第一次研究使用并給出了詳細的計算公式,So該曲線也是由其名字命名。Path中給出的quadTo方法屬于

二階貝賽爾曲線:


二階貝賽爾曲線的一個明顯特征是其擁有一個控制點,大家可以這樣想想貝賽爾曲線,在一根兩端固定橡皮筋上有一塊磁鐵,現(xiàn)在我們拿另一塊磁鐵去吸引橡皮筋上的磁鐵,因為引力,橡皮筋會隨著我們手上磁鐵的移動而改變形狀,又因為橡皮筋的張力讓束縛在橡皮筋上的磁鐵不會輕易吸附到我們手上的磁鐵,這時橡皮筋的狀態(tài)就可以看成是一條貝塞爾曲線,而我們手中的磁鐵就是一個控制點,通過這個控制點我們“拉扯”橡皮筋的曲度。
二階貝賽爾曲線的公式為:

同樣的,Path中也提供了三階貝塞爾曲線的方法cubicTo,按照上面我們的推論,三階應(yīng)該是有兩個控制點才對對吧

三階貝賽爾曲線:


公式:

高階貝賽爾曲線在Path中沒有對應(yīng)的方法,對我們來說三階也足夠了,不過大家可以了解下,難得我在墻外找到如此動感的貝賽爾曲線高清無碼動圖

高階貝塞爾曲線:
四階:


五階:

貝塞爾曲線通用公式:

quadTo()

quadTo方法有四個參數(shù),前兩個參數(shù)為控制點的坐標,后兩個參數(shù)為終點坐標,至于起點默認是畫布的左上角。這里的屏幕中心就是起點,(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();// 創(chuàng)建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個參數(shù)分別為兩個控制點,最后兩個參數(shù)是終點

總結(jié)一下:xxxTo()方法的作用是從某一位置連接(除moveTo)到另一位置。

addXxx()系列

add家族成員主要有:addRect, addRoundRect, addOval, addCircle, addPath, addArc
前面幾個方法小伙伴們一看就知道啥東西了。和上一期講的內(nèi)容相似,只不過是把基礎(chǔ)圖形添加到path中繪制。
以addRect為例,

public void addRect(RectF rect, Direction dir)

第二個參數(shù)是一個枚舉,有Path.Direction.CW和Path.Direction.CCW兩種選擇,分別代表順時針和逆時針的閉合方向(所有add封閉型path都需要這個參數(shù))。什么叫閉合方向呢?光說大家一定會蒙,有學(xué)習(xí)激情的童鞋看到后肯定會馬上敲代碼試驗一下兩者的區(qū)別,可是不管你如何改,單獨地在一條閉合曲線上你是看不出所謂閉合方向的區(qū)別的。為了弄懂它,我們在path上繪制一些文字來說明最后參數(shù)的意義:

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();// 創(chuàng)建Path
        path.addRect(100,100,300,200, Path.Direction.CW);

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

順時針

逆時針

addArc()

這東西咋一看和arcTo()很像,沒錯,事實上也差不多。
addArc(參數(shù)一,參數(shù)二,參數(shù)三)等價于arcTo(參數(shù)一,參數(shù)二,參數(shù)三,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);//后兩個參數(shù)控制平移

        canvas.drawPath(path1,mPaint);
    }

結(jié)果如下:

如果添加上兩個參數(shù),則在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);結(jié)果如下:


線段長了很多,因為這里的(200,200)是相對于開始點(100,100)來說的,是相對坐標。如果換算成絕對坐標就是繪制一條(100,100)到(300,300)之間的線段。其實,這個前綴“r”也就是relative(相對)的簡寫!

其他幾個比較重要的方法

setLastPoint()

重置當(dāng)前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),但是緊接著調(diào)用setLastPoint(300,200)方法,把最后一個點移動到(300,200)的位置,所以所畫的直線是從原點到(300,200)的位置


offset()

對當(dāng)前路徑之前的操作進行偏移(不會影響之后的操作)

//將新的path賦值到現(xiàn)有path
public void offset (float dx, float dy)
//將新的path賦值到dst,不影響現(xiàn)有path,當(dāng)dst為null時,效果同上一種方法
public void offset (float dx, float dy, Path dst)

參數(shù):
dx:X軸方向上的平移
dy:Y軸方向上的平移
dst:用于儲存平移后的結(jié)果

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

結(jié)語

剩余的一些方法比較簡單,查看上面的表就可以了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內(nèi)容