Android關於Path你所知道的和不知道的一切
零、前言
1.canvas本身提供了很多繪製基本圖形的方法,普通繪製基本滿足
2.但是更高階的繪製canvas便束手無策,但它的一個方法卻將圖形的繪製連線到了另一個次元
3.下面進入Path的世界,[注]:本文只說Path,關於繪製只要使用 Canvas.drawPath(Path,Paint)
即可
4.本文將對Path的 所有API
進行測試。
一、引:認識Path
例1.繪製網格
在Canvas篇我用Path畫過一個網格輔助,在這裡分析一下
moveTo相當於抬筆到某點,lineTo表示畫下到某點
/** * 繪製網格:注意只有用path才能繪製虛線 * * @param step小正方形邊長 * @param winSize 螢幕尺寸 */ public static Path gridPath(int step, Point winSize) { //建立path Path path = new Path(); //每間隔step,將筆點移到(0, step * i),然後畫線到(winSize.x, step * i) for (int i = 0; i < winSize.y / step + 1; i++) { path.moveTo(0, step * i); path.lineTo(winSize.x, step * i); } for (int i = 0; i < winSize.x / step + 1; i++) { path.moveTo(step * i, 0); path.lineTo(step * i, winSize.y); } return path; }
//準備畫筆 mRedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mRedPaint.setColor(Color.RED); mRedPaint.setStrokeWidth(2); mRedPaint.setStyle(Paint.Style.STROKE); //設定虛線效果new float[]{可見長度, 不可見長度},偏移值 mRedPaint.setPathEffect(new DashPathEffect(new float[]{10, 5}, 0)); //繪製 Path path = HelpPath.gridPath(50, mWinSize); canvas.drawPath(path, mRedPaint);

path畫線.png
例2.繪製N角星
曾經花了半天研究五角星的構造,通過兩個圓,發現了N角星繪製的通法
又用半天用JavaScript的Canvas實現了在瀏覽器上的繪製,當然Android也不示弱:

mmexport1541469593236.jpg
1).通用n角星路徑繪製:(基本上都是一些點位和角度的計算,然後連線)
/** * n角星路徑 * * @param num 幾角星 * @param R外接圓半徑 * @param r內接圓半徑 * @return n角星路徑 */ public static Path nStarPath(int num, float R, float r) { Path path = new Path(); float perDeg = 360 / num; float degA = perDeg / 2 / 2; float degB = 360 / (num - 1) / 2 - degA / 2 + degA; path.moveTo( (float) (Math.cos(rad(degA + perDeg * 0)) * R + R * Math.cos(rad(degA))), (float) (-Math.sin(rad(degA + perDeg * 0)) * R + R)); for (int i = 0; i < num; i++) { path.lineTo( (float) (Math.cos(rad(degA + perDeg * i)) * R + R * Math.cos(rad(degA))), (float) (-Math.sin(rad(degA + perDeg * i)) * R + R)); path.lineTo( (float) (Math.cos(rad(degB + perDeg * i)) * r + R * Math.cos(rad(degA))), (float) (-Math.sin(rad(degB + perDeg * i)) * r + R)); } path.close(); return path; } /** * 角度制化為弧度制 * * @param deg 角度 * @return 弧度 */ public static float rad(float deg) { return (float) (deg * Math.PI / 180); }
2).當外接圓和內切圓的半徑成一定的關係,可形成正多角星,和正多邊形
正多角星:
/** * 畫正n角星的路徑: * * @param num 角數 * @param R外接圓半徑 * @return 畫正n角星的路徑 */ public static Path regularStarPath(int num, float R) { float degA, degB; if (num % 2 == 1) {//奇數和偶數角區別對待 degA = 360 / num / 2 / 2; degB = 180 - degA - 360 / num / 2; } else { degA = 360 / num / 2; degB = 180 - degA - 360 / num / 2; } float r = (float) (R * Math.sin(rad(degA)) / Math.sin(rad(degB))); return nStarPath(num, R, r); }
正多邊形:
/** * 畫正n邊形的路徑 * * @param num 邊數 * @param R外接圓半徑 * @return 畫正n邊形的路徑 */ public static Path regularPolygonPath(int num, float R) { float r = (float) (R * (Math.cos(rad(360 / num / 2))));//!!一點解決 return nStarPath(num, R, r); } /** * 角度制化為弧度制 * * @param deg 角度 * @return 弧度 */ public static float rad(float deg) { return (float) (deg * Math.PI / 180); }

n角星
這兩個小栗子作為引,應該對Path的能為有一定的瞭解了吧,下面將正式對Path做系統地介紹
二、Path的詳細介紹
Path定位:
是一個類,直接繼承自Object,原始碼行數879(一盞茶的功夫就看完了),算個小類
但
native方法很多,說明它跟底層打交道的,感覺不好惹
下面看一下Path的公共方法:(基本建立相關、新增相關、設定相關,其他)
注:為了好看,以下所有演示為橫屏且canvas的座標原點移至(800,500),所有藍線為輔助線

Path一覽.png
1.moveTo----lineTo----close
moveTo:抬筆到某點
lineTo:畫線到某點
close:閉合首位
Path path = new Path(); path.moveTo(0, 0); path.lineTo(100, 200);

畫線.png
Path path = new Path(); path.moveTo(0, 0); path.lineTo(100, 200); path.lineTo(200, 100);

畫線2.png
Path path = new Path(); path.moveTo(0, 0); path.lineTo(100, 200); path.lineTo(200, 100); path.close();

close.png
2.rMoveTo----rLineTo
rMoveTo:從路徑尾部為起點,抬筆
rLineTo:從路徑尾部為起點,畫直線
其實也不難理解,就是點的參考系從canvas左上角移變成路徑尾部,看一下就知道了:
Path path = new Path(); path.rMoveTo(0,0); path.rLineTo(100, 200); path.rLineTo(200, 100); path.close();

rlineto.png
3.繪製弧:arcTo(矩形範圍,起點,終點,)
RectF rectF = new RectF(100, 100, 500, 300); path.moveTo(0, 0); //arcTo(矩形範圍,起點,終點,是否獨立--預設false) //path.arcTo(rectF, 0, 45, true); path.arcTo(rectF, 0, 45, false);

繪製弧線.png
剩下的貝塞爾曲線這個大頭放在本篇最後
三、路徑新增:addXXX
可以看出齊刷刷的Direction,先看看它是什麼鬼:
是一個列舉,只有CW(順時針)和CCW(逆時針),這裡暫且按下,都使用CW,後文詳述:
public enum Direction { /** clockwise */ CW(0),// must match enum in SkPath.h---順時針 /** counter-clockwise */ CCW (1);// must match enum in SkPath.h---逆時針 Direction(int ni) { nativeInt = ni; } final int nativeInt; }
1.加矩形路徑:
1).普通矩形:addRect(左,上,右,下)
RectF rectF = new RectF(100, 100, 500, 300); path.addRect(rectF, Path.Direction.CW);//順時針畫矩形
2).圓角矩形:addRoundRect(矩形域,圓角x,圓角y)
RectF rectF = new RectF(100, 100, 500, 300); path.addRoundRect(rectF, 50, 50, Path.Direction.CW);//順時針畫圓角矩形
3).用4點控制圓角:addRoundRect(矩形域,8數,方向)
RectF rectF = new RectF(100, 100, 500, 300); path.addRoundRect(rectF, new float[]{ 150, 150,//左上圓角x,y 0, 0,//右上圓角x,y 450, 250,//右下圓角x,y 250, 200//左下圓角x,y }, Path.Direction.CW);//順時針畫

矩形相關.png
2.加橢圓路徑:addOval(矩形域,方向)
RectF rectF = new RectF(100, 100, 500, 300); path.addOval(rectF, Path.Direction.CW);

繪製橢圓.png
3.加圓路徑:addCircle(圓心x,圓心y,方向)
path.addCircle(100,100,100,Path.Direction.CW);

圓.png
4.加弧線路徑:addArc(矩形域,起始角度終止角度)
RectF rectF = new RectF(100, 100, 500, 300); path.addArc(rectF,0,145);

弧線.png
5.新增路徑:
1).普通新增addPath(Path)
path.addCircle(100,100,100,Path.Direction.CW); Path otherPath = new Path(); otherPath.moveTo(0, 0); otherPath.lineTo(100, 100); path.addPath(otherPath);
2).偏移新增:addPath(Path,偏移x,偏移y)
path.addCircle(100,100,100,Path.Direction.CW); Path otherPath = new Path(); otherPath.moveTo(0, 0); otherPath.lineTo(100, 100); path.addPath(otherPath,200,200);
3).矩陣變換新增:addPath(Path,Matrix)
path.addCircle(100,100,100,Path.Direction.CW); Path otherPath = new Path(); otherPath.moveTo(0, 0); otherPath.lineTo(100, 100); Matrix matrix = new Matrix(); matrix.setValues(new float[]{ 1, 0, 100, 0, .5f, 150, 0, 0, 1 }); path.addPath(otherPath, matrix);

新增路徑.png
四、其他操作:
1.細碎小點綜述:
path.reset();//清空path,保留填充型別 //path.rewind();//清空path,保留資料結構 path.isEmpty()//是否為空 path.isRect(new RectF()); path.isConvex(); path.isInverseFillType(); path.set(otherPath);//清空path後新增新Path //path.offset(200,200);//平移 //path.transform(matrix);//矩陣變換 Path tempPath = new Path(); //path.offset(200, 200, tempPath);//基於path平移注入tempPath,path不變 path.transform(matrix, tempPath);//基於path變換注入tempPath,path不變 canvas.drawPath(path, mRedPaint); canvas.drawPath(tempPath, mRedPaint);
2.順時針CW和逆時針CCW的區別
1).setLastPoint(x,y):設定最後一點
Path相當於將點按順序儲存,setLastPoint(x,y)方法則是將最後一個點換掉
RectF rectF = new RectF(100, 100, 500, 300); path.addRect(rectF, Path.Direction.CW);//順時針畫矩形 path.setLastPoint(200, 200); canvas.drawPath(path, mRedPaint);

順時針.png
RectF rectF = new RectF(100, 100, 500, 300); path.addRect(rectF, Path.Direction.CCW);//順時針畫矩形 path.setLastPoint(200, 200); canvas.drawPath(path, mRedPaint);

逆時針.png
3.邊界計算:
Path starPath = CommonPath.nStarPath(6, 100, 50); RectF rectF = new RectF();//自備矩形區域 starPath.computeBounds(rectF, true); canvas.drawPath(starPath, mRedPaint); canvas.drawRect(rectF,mHelpPaint);
檢視矩形路徑區域.png

檢視矩形路徑區域.png
五、路徑的填充
1.初識路徑的填充:
1)左圖:兩個都是順時針:
mRedPaint.setStyle(Paint.Style.FILL); RectF rectF = new RectF(100, 100, 500, 300); path.addRect(rectF, Path.Direction.CW);//順時針畫矩形 path.addRect(200, 0, 400, 400, Path.Direction.CW);//順時針畫矩形
2)右圖:橫的順時針,豎的逆時針
mRedPaint.setStyle(Paint.Style.FILL); RectF rectF = new RectF(100, 100, 500, 300); path.addRect(rectF, Path.Direction.CW);//順時針畫矩形 path.addRect(200, 0, 400, 400, Path.Direction.CCW);//逆時針畫矩形

填充.png
感覺向兩個水渦,同向加劇,反向中間就抵消了
2.填充的環繞原則:---在自然科學(如數學,物理學)中的概念
非零環繞原則(WINDING)----預設
反零環繞原則(INVERSE_WINDING)
奇偶環繞原則(EVEN_ODD)
反奇偶環繞原則(INVERSE_EVEN_ODD)
public enum FillType { WINDING(0), EVEN_ODD(1), INVERSE_WINDING (2), INVERSE_EVEN_ODD(3); FillType(int ni) { nativeInt = ni; } final int nativeInt; }
Path.FillType fillType = path.getFillType();//獲取型別 path.setFillType(Path.FillType.XXXXXX)//設定型別
//繪製的測試五角星 path.moveTo(100, 200); path.lineTo(500, 200); path.lineTo(200, 400); path.lineTo(300, 50); path.lineTo(400, 400); path.close();
1).非零環繞數規則:WINDING
根據我個人的理解(僅供參考):在非零環繞數規則下
判斷一點在不在圖形內:從點引射線P, 相交的路徑方向與射線成銳角+1 相交的路徑方向與射線成鈍角-1 結果0,不在,否則,在

非零環繞.png
2).奇偶環繞數規則:EVEN_ODD
根據我個人的理解(僅供參考):奇偶環繞數規則
判斷一點在不在圖形內(非定點): 從點引射線P,看與圖形交點個數 奇數在,偶數,不在

奇偶環繞.png
3).反非零環繞數規則和反奇偶環繞數規則:
就是和上面相比,該填充的不填充,不填充的填充

反環繞.png
這樣看來圖形的順時針或逆時針繪製對於填充是非常重要的
綜合來說奇偶原則比較簡單粗暴,但非零原則作為預設方式體現了它的通用性
六、布林運算OP:(兩個路徑之間的運算)
如果說環繞原則是一個Path的自我糾結,那麼OP就是兩個路徑之間的勾心鬥角
Path right = new Path(); Path left = new Path(); left.addCircle(0, 0, 100, Path.Direction.CW); right.addCircle(100, 0, 100, Path.Direction.CW); //left.op(right, Path.Op.DIFFERENCE);//差集----暈,咬了一口硫酸 //left.op(right, Path.Op.REVERSE_DIFFERENCE);//反差集----賠了夫人又折兵 //left.op(right, Path.Op.INTERSECT);//交集----與你不同的都不是我 //left.op(right, Path.Op.UIO/">NION);//並集----在一起,在一起 left.op(right, Path.Op.XOR);//異或集---我恨你,我也恨你 canvas.drawPath(left, mRedPaint);

op.png
七、Path動畫:PathMeasure
init方法裡:
//測量路徑 PathMeasure pathMeasure = new PathMeasure(mStarPath, false); //使用ValueAnimator ValueAnimator pathAnimator = ValueAnimator.ofFloat(1, 0); pathAnimator.setDuration(5000); pathAnimator.addUpdateListener(animation -> { float value = (Float) animation.getAnimatedValue(); //使用畫筆虛線效果+偏移 DashPathEffect effect = new DashPathEffect( new float[]{pathMeasure.getLength(), pathMeasure.getLength()}, value * pathMeasure.getLength()); mRedPaint.setPathEffect(effect); invalidate(); }); pathAnimator.start();
OnDraw方法裡:
canvas.drawPath(mStarPath, mRedPaint);

路徑動畫.gif
八、貝塞爾曲線簡述:
如果說Path是Canvas為了高階繪製留下的窗子那麼貝塞爾曲線則Path為了更高階的繪製而留下的門
由於操作的複雜性,這裡並不過渡深入,以後有需求的話會專門開一篇
1.簡單認識:(圖來源網路)
一階貝塞爾 | 二階貝塞爾 | 三階貝塞爾 |
---|---|---|
![]() |
![]() |
![]() |
2.二階貝塞爾曲線示例:
public class Bezier2View extends View { private Paint mHelpPaint;//輔助畫筆 private Paint mPaint;//貝塞爾曲線畫筆 private Path mBezierPath;//貝塞爾曲線路徑 //起點 private PointF start = new PointF(0, 0); //終點 private PointF end = new PointF(400, 0); //控制點 private PointF control = new PointF(200, 200); private Picture mPicture;//座標系和網格的Canvas元件 private Point mCoo;//座標系 public Bezier2View(Context context) { this(context, null); } public Bezier2View(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } private void init() { //貝塞爾曲線畫筆 mPaint = new Paint(); mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(Color.parseColor("#88EC17F3")); mPaint.setStrokeWidth(8); //輔助線畫筆 resetHelpPaint(); recordBg();//初始化時錄製座標系和網格--避免在Ondraw裡重複呼叫 mBezierPath = new Path(); } /** * 初始化時錄製座標系和網格--避免在Ondraw裡重複呼叫 */ private void recordBg() { //準備螢幕尺寸 Point winSize = new Point(); mCoo = new Point(800, 500); Utils.loadWinSize(getContext(), winSize); Paint gridPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPicture = new Picture(); Canvas recordCanvas = mPicture.beginRecording(winSize.x, winSize.y); //繪製輔助網格 HelpDraw.drawGrid(recordCanvas, winSize, gridPaint); //繪製座標系 HelpDraw.drawCoo(recordCanvas, mCoo, winSize, gridPaint); mPicture.endRecording(); } /** * 重置輔助畫筆 */ private void resetHelpPaint() { mHelpPaint = new Paint(); mHelpPaint.setColor(Color.BLUE); mHelpPaint.setStrokeWidth(2); mHelpPaint.setStyle(Paint.Style.STROKE); mHelpPaint.setPathEffect(new DashPathEffect(new float[]{10, 5}, 0)); mHelpPaint.setStrokeCap(Paint.Cap.ROUND); } @Override public boolean onTouchEvent(MotionEvent event) { // 根據觸控位置更新控制點,並提示重繪 control.x = event.getX() - mCoo.x; control.y = event.getY() - mCoo.y; invalidate(); return true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); canvas.translate(mCoo.x, mCoo.y); drawHelpElement(canvas);//繪製輔助工具--控制點和基準選 // 繪製貝塞爾曲線 mBezierPath.moveTo(start.x, start.y); mBezierPath.quadTo(control.x, control.y, end.x, end.y); canvas.drawPath(mBezierPath, mPaint); mBezierPath.reset();//清空mBezierPath canvas.restore(); canvas.drawPicture(mPicture); } /** * 繪製輔助工具--控制點和基準選 * * @param canvas */ private void drawHelpElement(Canvas canvas) { // 繪製資料點和控制點 mHelpPaint.setColor(Color.parseColor("#8820ECE2")); mHelpPaint.setStrokeWidth(20); canvas.drawPoint(start.x, start.y, mHelpPaint); canvas.drawPoint(end.x, end.y, mHelpPaint); canvas.drawPoint(control.x, control.y, mHelpPaint); // 繪製輔助線 resetHelpPaint(); canvas.drawLine(start.x, start.y, control.x, control.y, mHelpPaint); canvas.drawLine(end.x, end.y, control.x, control.y, mHelpPaint); } }
效果如下:(模擬器+錄屏軟體+AS有點卡,手機上演示很流暢的)

二階貝塞爾.gif
3.三階貝塞爾的簡單演示:
mRedPaint.setStrokeWidth(5); mRedPaint.setStrokeCap(Paint.Cap.ROUND); path.moveTo(0, 0);//定點1_x,定點1_y //(控制點1_X,控制點1_y,控制點2_x,控制點2_y,定點2_x,定點2_y) path.cubicTo(100, 100, 300, -300, 600, 0);

三階貝塞爾.png
好了,Path完結散花
後記:捷文規範
1.本文成長記錄及勘誤表
專案原始碼 | 日期 | 備註 |
---|---|---|
V0.1--無 | 2018-11-6 | ofollow,noindex">Android關於Path你所知道的和不知道的一切 |
2.更多關於我
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
我的github | 我的簡書 | 我的CSDN | 個人網站 |
3.宣告
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援