一、Path常用方法表
為了兼容性(偷懶) 本表格中去除了部分API21(即安卓版本5.0)以上才添加的方法。
二杈曲、Path詳解
請關(guān)閉硬件加速丈钙,以免引起不必要的問題非驮!
請關(guān)閉硬件加速,以免引起不必要的問題雏赦!
請關(guān)閉硬件加速劫笙,以免引起不必要的問題芙扎!
在AndroidMenifest文件中application節(jié)點下添上 android:hardwareAccelerated=”false”以關(guān)閉整個應(yīng)用的硬件加速。
Path作用
本次特地開了一篇詳細講解Path填大,為什么要單獨摘出來呢戒洼,這是因為Path在2D繪圖中是一個很重要的東西。
在前面我們講解的所有繪制都是簡單圖形(如 矩形 圓 圓弧等)允华,而對于那些復雜一點的圖形則沒法去繪制(如繪制一個心形 正多邊形 五角星等)施逾,而使用Path不僅能夠繪制簡單圖形,也可以繪制這些比較復雜的圖形例获。另外汉额,根據(jù)路徑繪制文本和剪裁畫布都會用到Path。
Path含義
Path封裝了由直線和曲線(二次榨汤,三次貝塞爾曲線)構(gòu)成的幾何路徑蠕搜。你能用Canvas中的drawPath來把這條路徑畫出來(同樣支持Paint的不同繪制模式),也可以用于剪裁畫布和根據(jù)路徑繪制文字收壕。我們有時會用Path來描述一個圖像的輪廓妓灌,所以也會稱為輪廓線(輪廓線僅是Path的一種使用方法,兩者并不等價)
另外路徑有開放和封閉的區(qū)別蜜宪。
Path使用方法詳解
第1組: moveTo虫埂、 setLastPoint、 lineTo 和 close
由于Path的有些知識點無法單獨來講圃验,所以本次采取了一次講一組方法掉伏。
按照慣例,先創(chuàng)建畫筆:
Paint mPaint = new Paint(); // 創(chuàng)建畫筆
mPaint.setColor(Color.BLACK); // 畫筆顏色 - 黑色
mPaint.setStyle(Paint.Style.STROKE); // 填充模式 - 描邊
mPaint.setStrokeWidth(10); // 邊框?qū)挾?- 10
lineTo:
方法預(yù)覽:
public void lineTo (float x, float y)
首先講解的的LineTo澳窑,為啥先講解這個呢斧散?
是因為moveTo、 setLastPoint摊聋、 close都無法直接看到效果鸡捐,借助有具現(xiàn)化效果的lineTo才能讓這些方法現(xiàn)出原形。
lineTo很簡單麻裁,只有一個方法箍镜,作用也很容易理解,line嘛煎源,顧名思義就是一條線色迂。
俗話(數(shù)學書上)說,兩點確定一條直線薪夕,但是看參數(shù)明顯只給了一個點的坐標吧(這不按常理出牌啊)脚草。
再仔細一看赫悄,這個lineTo除了line外還有一個to呢原献,to翻譯過來就是“至”馏慨,到某個地方的意思,lineTo難道是指從某個點到參數(shù)坐標點之間連一條線姑隅?
沒錯写隶,你猜對了,但是這某個點又是哪里呢讲仰?
前面我們提到過Path可以用來描述一個圖像的輪廓慕趴,圖像的輪廓通常都是用一條線構(gòu)成的,所以這里的某個點就是上次操作結(jié)束的點鄙陡,如果沒有進行過操作則默認點為坐標原點冕房。
那么我們就來試一下:
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心(寬高數(shù)據(jù)在onSizeChanged中獲取)
Path path = new Path(); // 創(chuàng)建Path
path.lineTo(200, 200); // lineTo
path.lineTo(200,0);
canvas.drawPath(path, mPaint); // 繪制Path
在示例中我們調(diào)用了兩次lineTo,第一次由于之前沒有過操作趁矾,所以默認點就是坐標原點O耙册,結(jié)果就是坐標原點O到A(200,200)之間連直線(用藍色圈1標注)。
第二次lineTo的時候毫捣,由于上次的結(jié)束位置是A(200,200),所以就是A(200,200)到B(200,0)之間的連線(用藍色圈2標注)详拙。
moveTo 和 setLastPoint:
方法預(yù)覽:
// moveTo
public void moveTo (float x, float y)
// setLastPoint
public void setLastPoint (float dx, float dy)
這兩個方法雖然在作用上有相似之處,但實際上卻是完全不同的兩個東東蔓同,具體參照下表:
廢話不多說饶辙,直接上代碼:
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心
Path path = new Path(); // 創(chuàng)建Path
path.lineTo(200, 200); // lineTo
path.moveTo(200,100); // moveTo
path.lineTo(200,0); // lineTo
canvas.drawPath(path, mPaint); // 繪制Path
這個和上面演示lineTo的方法類似,只不過在兩個lineTo之間添加了一個moveTo斑粱。
moveTo只改變下次操作的起點弃揽,在執(zhí)行完第一次LineTo的時候,本來的默認點位置是A(200,200),但是moveTo將其改變成為了C(200,100),所以在第二次調(diào)用lineTo的時候就是連接C(200,100) 到 B(200,0) 之間的直線(用藍色圈2標注)则北。
下面是setLastPoint的示例:
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心
Path path = new Path(); // 創(chuàng)建Path
path.lineTo(200, 200); // lineTo
path.setLastPoint(200,100); // setLastPoint
path.lineTo(200,0); // lineTo
canvas.drawPath(path, mPaint); // 繪制Path
setLastPoint是重置上一次操作的最后一個點蹋宦,在執(zhí)行完第一次的lineTo的時候,最后一個點是A(200,200),而setLastPoint更改最后一個點為C(200,100),所以在實際執(zhí)行的時候咒锻,第一次的lineTo就不是從原點O到A(200,200)的連線了冷冗,而變成了從原點O到C(200,100)之間的連線了。
在執(zhí)行完第一次lineTo和setLastPoint后惑艇,最后一個點的位置是C(200,100),所以在第二次調(diào)用lineTo的時候就是C(200,100) 到 B(200,0) 之間的連線(用藍色圈2標注)蒿辙。
close
方法預(yù)覽:
public void close ()
close方法用于連接當前最后一個點和最初的一個點(如果兩個點不重合的話),最終形成一個封閉的圖形滨巴。
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心
Path path = new Path(); // 創(chuàng)建Path
path.lineTo(200, 200); // lineTo
path.lineTo(200,0); // lineTo
path.close(); // close
canvas.drawPath(path, mPaint); // 繪制Path
很明顯思灌,兩個lineTo分別代表第1和第2條線,而close在此處的作用就算連接了B(200,0)點和原點O之間的第3條線恭取,使之形成一個封閉的圖形泰偿。
注意:close的作用是封閉路徑,與連接當前最后一個點和第一個點并不等價蜈垮。如果連接了最后一個點和第一個點仍然無法形成封閉圖形裕照,則close什么 也不做调塌。
第2組: addXxx與arcTo
這次內(nèi)容主要是在Path中添加基本圖形,重點區(qū)分addArc與arcTo羔砾。
第一類(基本形狀)
方法預(yù)覽:
// 第一類(基本形狀)
// 圓形
public void addCircle (float x, float y, float radius, Path.Direction dir)
// 橢圓
public void addOval (RectF oval, Path.Direction dir)
// 矩形
public void addRect (float left, float top, float right, float bottom, Path.Direction dir)
public void addRect (RectF rect, Path.Direction dir)
// 圓角矩形
public void addRoundRect (RectF rect, float[] radii, Path.Direction dir)
public void addRoundRect (RectF rect, float rx, float ry, Path.Direction dir)
仔細觀察一下第一類是方法负间,無一例外姜凄,在最后都有一個 Path.Direction,這是一個什么神奇的東東态秧?
Direction的意思是 方向玩祟,趨勢。 點進去看一下會發(fā)現(xiàn)Direction是一個枚舉(Enum)類型屿聋,里面只有兩個枚舉常量润讥,如下:
先偷偷劇透一下這個順時針和逆時針的作用。
這個先劇透這么多撮慨,至于對閉合順序有啥影響脆粥,圖形的渲染等問題等請慢慢看下去
咱們先研究確定閉合順序的問題,添加一個矩形試試看:
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW);
canvas.drawPath(path,mPaint);
將上面代碼的CW改為CCW再運行一次规伐。接下來就是見證奇跡的時刻匣缘,兩次運行結(jié)果一模一樣,有木有很神奇肌厨!(神奇?zhèn)€毛啊??????????)
其實啊,這個東東是自帶隱身技能的吵护,想要讓它現(xiàn)出原形,就要用到咱們剛剛學到的setLastPoint(重置當前最后一個點的位置)祥诽。
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW);
path.setLastPoint(-300,300); // <-- 重置最后一個點的位置
canvas.drawPath(path,mPaint);
可以明顯看到用爪,圖形發(fā)生了奇怪的變化胁镐。為何會如此呢偎血?
我們先分析一下,繪制一個矩形(僅繪制邊線)盯漂,實際上只需要進行四次lineTo操作就行了颇玷,也就是說就缆,只需要知道4個點的坐標竭宰,然后使用moveTo到第一個點,之后依次lineTo就行了(從上面的測試可以看出狞甚,在實際繪制中也確實是這么干的)廓旬。
可是為什么要這么做呢?確定一個矩形最少需要兩個點(對角線的兩個點)涩盾,根據(jù)這兩個點的坐標直接算出四條邊然后畫出來不就行了励背,干嘛還要先計算出四個點坐標,之后再連直線呢终畅?
這個就要涉及一些path的存儲問題了竟闪,前面在path中的定義中說過炼蛤,Path是封裝了由直線和曲線(二次,三次貝塞爾曲線)構(gòu)成的幾何路徑。其中曲線部分用的是貝塞爾曲線绿聘,稍后再講次舌。 然而除了曲線部分就只剩下直線了彼念,對于直線的存儲最簡單的就是記錄坐標點,然后直接連接各個點就行了哲思。雖然記錄矩形只需要兩個點吩案,但是如果只用兩個點來記錄一個矩形的話,就要額外增加一個標志位來記錄這是一個矩形靠益,顯然對于存儲和解析都是很不劃算的事情残揉,將矩形轉(zhuǎn)換為直線,為的就是存儲記錄方便绩卤。
扯了這么多江醇,該回歸正題了陶夜,就是我們的順時針和逆時針在這里是干啥的?
圖形在實際記錄中就是記錄各個的點黔夭,對于一個圖形來說肯定有多個點羽嫡,既然有這么多的點,肯定就需要一個先后順序婚惫,這里順時針和逆時針就是用來確定記錄這些點的順序的。
對于上面這個矩形來說艰管,我們采用的是順時針(CW)蒋川,所以記錄的點的順序就是 A -> B -> C -> D. 最后一個點就是D捺球,我們這里使用setLastPoint改變最后一個點的位置實際上是改變了D的位置。
理解了上面的原理之后餐济,設(shè)想如果我們將順時針改為逆時針(CCW)胆剧,則記錄點的順序應(yīng)該就是 A -> D -> C -> B, 再使用setLastPoint則改變的是B的位置醉冤,我們試試看結(jié)果和我們的猜想是否一致:
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CCW);
path.setLastPoint(-300,300); // <-- 重置最后一個點的位置
canvas.drawPath(path,mPaint);
通過驗證發(fā)現(xiàn)蚁阳,發(fā)現(xiàn)結(jié)果和我們猜想的一樣,但是還有一個潛藏的問題不曉得大家可否注意到颠悬。我們用兩個點的坐標確定了一個矩形定血,矩形起始點(A)就是我們指定的第一個點的坐標澜沟。
需要注意的是,交換坐標點的順序可能就會影響到某些繪制內(nèi)容哦刊苍,例如上面的例子濒析,你可以嘗試交換兩個坐標點,或者指定另外兩個點來作為參數(shù)婴氮,雖然指定的是同一個矩形,但實際繪制出來是不同的哦名船。
參數(shù)中點的順序很重要旨怠!
參數(shù)中點的順序很重要鉴腻!
參數(shù)中點的順序很重要!
重要的話說三遍蜓席,本次是用矩形作為例子的课锌,其他的幾個圖形基本上都包含了曲線,詳情參見后續(xù)的貝塞爾曲線部分雏胃。
重要的話說三遍志鞍,本次是用矩形作為例子的固棚,其他的幾個圖形基本上都包含了曲線,詳情參見后續(xù)的貝塞爾曲線部分厂汗。
第二類(Path)
方法預(yù)覽:
// 第二類(Path)
// path
public void addPath (Path src)
public void addPath (Path src, float dx, float dy)
public void addPath (Path src, Matrix matrix)
這個相對比較簡單黍翎,也很容易理解匣掸,就是將兩個Path合并成為一個。
第三個方法是將src添加到當前path之前先使用Matrix進行變換霎匈。
第二個方法比第一個方法多出來的兩個參數(shù)是將src進行了位移之后再添加進當前path中送爸。
示例:
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心
canvas.scale(1,-1); // <-- 注意 翻轉(zhuǎn)y坐標軸
Path path = new Path();
Path src = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW);
src.addCircle(0,0,100, Path.Direction.CW);
path.addPath(src,0,200);
mPaint.setColor(Color.BLACK); // 繪制合并后的路徑
canvas.drawPath(path,mPaint);
首先我們新建地方兩個Path(矩形和圓形)中心都是坐標原點,我們在將包含圓形的path添加到包含矩形的path之前將其進行移動了一段距離墨吓,最終繪制出來的效果就如上面所示帖烘。
第三類(addArc與arcTo)
方法預(yù)覽:
// 第三類(addArc與arcTo)
// addArc
public void addArc (RectF oval, float startAngle, float sweepAngle)
// arcTo
public void arcTo (RectF oval, float startAngle, float sweepAngle)
public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
參數(shù)表:
PS: sweepAngle取值范圍是 [-360, 360)秘症,不包括360,當 >= 360 或者 < -360 時將不會繪制任何內(nèi)容役耕, 對于360聪廉,你可以用一個接近的值替代,例如: 359.99图云。
從名字就可以看出邻邮,這兩個方法都是與圓弧相關(guān)的筒严,作用都是添加一個圓弧到path中情萤,但既然存在兩個方法,兩者之間肯定是有區(qū)別的:
可以看到addArc有1個方法(實際上是兩個的,但另一個重載方法是API21添加的), 而arcTo有2個方法睁宰,其中一個最后多了一個布爾類型的變量forceMoveTo柒傻。
forceMoveTo是什么作用呢?
這個變量意思為“是否強制使用moveTo”伐债,也就是說峰锁,是否使用moveTo將變量移動到圓弧的起點位移双戳,也就意味著:
示例(addArc):
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心
canvas.scale(1,-1); // <-- 注意 翻轉(zhuǎn)y坐標軸
Path path = new Path();
path.lineTo(100,100);
RectF oval = new RectF(0,0,300,300);
path.addArc(oval,0,270);
// path.arcTo(oval,0,270,true); // <-- 和上面一句作用等價
canvas.drawPath(path,mPaint);
示例(arcTo):
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心
canvas.scale(1,-1); // <-- 注意 翻轉(zhuǎn)y坐標軸
Path path = new Path();
path.lineTo(100,100);
RectF oval = new RectF(0,0,300,300);
path.arcTo(oval,0,270);
// path.arcTo(oval,0,270,false); // <-- 和上面一句作用等價
canvas.drawPath(path,mPaint);
第3組:isEmpty千诬、 isRect膏斤、isConvex、 set 和 offset
這一組比較簡單傲茄,稍微說一下就可以了沮榜。
isEmpty
方法預(yù)覽:
public boolean isEmpty ()
判斷path中是否包含內(nèi)容蟆融。
Path path = new Path();
Log.e("1",path.isEmpty()+"");
path.lineTo(100,100);
Log.e("2",path.isEmpty()+"");
log輸出結(jié)果:
com.sloop.canvas E/1: true
com.sloop.canvas E/2: false
isRect
方法預(yù)覽:
public boolean isRect (RectF rect)
判斷path是否是一個矩形,如果是一個矩形的話山憨,會將矩形的信息存放進參數(shù)rect中弥喉。
path.lineTo(0,400);
path.lineTo(400,400);
path.lineTo(400,0);
path.lineTo(0,0);
RectF rect = new RectF();
boolean b = path.isRect(rect);
Log.e("Rect","isRect:"+b+"| left:"+rect.left+"| top:"+rect.top+"| right:"+rect.right+"| bottom:"+rect.bottom);
log 輸出結(jié)果:
com.sloop.canvas E/Rect: isRect:true| left:0.0| top:0.0| right:400.0| bottom:400.0
set
方法預(yù)覽:
public void set (Path src)
將新的path賦值到現(xiàn)有path由境。
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心
canvas.scale(1,-1); // <-- 注意 翻轉(zhuǎn)y坐標軸
Path path = new Path(); // path添加一個矩形
path.addRect(-200,-200,200,200, Path.Direction.CW);
Path src = new Path(); // src添加一個圓
src.addCircle(0,0,100, Path.Direction.CW);
path.set(src); // 大致相當于 path = src;
canvas.drawPath(path,mPaint);
offset
方法預(yù)覽:
public void offset (float dx, float dy)
public void offset (float dx, float dy, Path dst)
這個的作用也很簡單虏杰,就是對path進行一段平移,它和Canvas中的translate作用很像攻询,但Canvas作用于整個畫布州弟,而path的offset只作用于當前path低零。
但是第二個方法最后怎么會有一個path作為參數(shù)掏婶?
其實第二個方法中最后的參數(shù)das是存儲平移后的path的猎物。
canvas.translate(mWidth / 2, mHeight / 2); // 移動坐標系到屏幕中心
canvas.scale(1,-1); // <-- 注意 翻轉(zhuǎn)y坐標軸
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
從運行效果圖可以看出炕横,雖然我們在dst中添加了一個矩形老厌,但是并沒有表現(xiàn)出來枝秤,所以慷嗜,當dst中存在內(nèi)容時,dst中原有的內(nèi)容會被清空薇溃,而存放平移后的path缭乘。
三、總結(jié)
本想一篇把path寫完薄啥,但是萬萬沒想到居然扯了這么多逛尚。本篇中講解的是直線部分和一些常用方法绰寞,下一篇將著重講解貝塞爾曲線和自相交圖形渲染等相關(guān)問題铣口,敬請期待哦。
學完本篇之后又解鎖了新的境界件缸,可以看看這位大神的文章 Android雷達圖(蜘蛛網(wǎng)圖)繪制