1.canvas自己提供了不少繪製基本圖形的方法,普通繪製基本知足
2.可是更高級的繪製canvas便一籌莫展,但它的一個方法卻將圖形的繪製鏈接到了另外一個次元
3.下面進入Path的世界,[注]:本文只說Path,關於繪製只要使用Canvas.drawPath(Path,Paint)
便可
4.本文將對Path的全部API
進行測試。git
在Canvas篇我用Path畫過一個網格輔助,在這裏分析一下
moveTo至關於擡筆到某點,lineTo表示畫下到某點github
/**
* 繪製網格:注意只有用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);
複製代碼
曾經花了半天研究五角星的構造,經過兩個圓,發現了N角星繪製的通法
又用半天用JavaScript的Canvas實現了在瀏覽器上的繪製,固然Android也不示弱:編程
/**
* 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);
}
複製代碼
正多角星:canvas
/**
* 畫正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);
}
複製代碼
這兩個小栗子做爲引,應該對Path的能爲有必定的瞭解了吧,下面將正式對Path作系統地介紹bash
Path定位: 是一個類,直接繼承自Object,源碼行數879(一盞茶的功夫就看完了),算個小類
但
native方法不少,說明它跟底層打交道的,感受很差惹
下面看一下Path的公共方法:(基本建立相關、添加相關、設置相關,其餘)
注:爲了好看,如下全部演示爲橫屏且canvas的座標原點移至(800,500),全部藍線爲輔助線
微信
moveTo:擡筆到某點
lineTo:畫線到某點
close:閉合首位網絡
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(100, 200);
複製代碼
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(100, 200);
path.lineTo(200, 100);
複製代碼
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(100, 200);
path.lineTo(200, 100);
path.close();
複製代碼
rMoveTo:從路徑尾部爲起點,擡筆
rLineTo:從路徑尾部爲起點,畫直線
其實也不難理解,就是點的參考系從canvas左上角移變成路徑尾部,看一下就知道了:數據結構
Path path = new Path();
path.rMoveTo(0,0);
path.rLineTo(100, 200);
path.rLineTo(200, 100);
path.close();
複製代碼
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);
複製代碼
剩下的貝塞爾曲線這個大頭放在本篇最後ide
能夠看出齊刷刷的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;
}
複製代碼
RectF rectF = new RectF(100, 100, 500, 300);
path.addRect(rectF, Path.Direction.CW);//順時針畫矩形
複製代碼
RectF rectF = new RectF(100, 100, 500, 300);
path.addRoundRect(rectF, 50, 50, Path.Direction.CW);//順時針畫圓角矩形
複製代碼
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);//順時針畫
複製代碼
RectF rectF = new RectF(100, 100, 500, 300);
path.addOval(rectF, Path.Direction.CW);
複製代碼
path.addCircle(100,100,100,Path.Direction.CW);
複製代碼
RectF rectF = new RectF(100, 100, 500, 300);
path.addArc(rectF,0,145);
複製代碼
path.addCircle(100,100,100,Path.Direction.CW);
Path otherPath = new Path();
otherPath.moveTo(0, 0);
otherPath.lineTo(100, 100);
path.addPath(otherPath);
複製代碼
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);
複製代碼
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);
複製代碼
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);
複製代碼
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);
複製代碼
RectF rectF = new RectF(100, 100, 500, 300);
path.addRect(rectF, Path.Direction.CCW);//順時針畫矩形
path.setLastPoint(200, 200);
canvas.drawPath(path, mRedPaint);
複製代碼
Path starPath = CommonPath.nStarPath(6, 100, 50);
RectF rectF = new RectF();//自備矩形區域
starPath.computeBounds(rectF, true);
canvas.drawPath(starPath, mRedPaint);
canvas.drawRect(rectF,mHelpPaint);
複製代碼
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);//順時針畫矩形
複製代碼
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);//逆時針畫矩形
複製代碼
感受向兩個水渦,同向加重,反向中間就抵消了
非零環繞原則(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();
複製代碼
根據我我的的理解(僅供參考):在非零環繞數規則下
判斷一點在不在圖形內:從點引射線P,
遇到順時針邊+1
遇到逆時針邊-1
結果0,不在,不然,在
複製代碼
根據我我的的理解(僅供參考):奇偶環繞數規則
判斷一點在不在圖形內(非定點):
從點引射線P,看與圖形交點個數
奇數在,偶數,不在
複製代碼
就是和上面相比,該填充的不填充,不填充的填充
這樣看來圖形的順時針或逆時針繪製對於填充是很是重要的
綜合來講奇偶原則比較簡單粗暴,但非零原則做爲默認方式體現了它的通用性
若是說環繞原則是一個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.UNION);//並集----在一塊兒,在一塊兒
left.op(right, Path.Op.XOR);//異或集---我恨你,我也恨你
canvas.drawPath(left, mRedPaint);
複製代碼
7、Path動畫:PathMeasure
//測量路徑
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();
複製代碼
canvas.drawPath(mStarPath, mRedPaint);
複製代碼
若是說Path是Canvas爲了高級繪製留下的窗子那麼貝塞爾曲線則Path爲了更高級的繪製而留下的門
因爲操做的複雜性,這裏並不過渡深刻,之後有需求的話會專門開一篇
一階貝塞爾 | 二階貝塞爾 | 三階貝塞爾 |
---|---|---|
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有點卡,手機上演示很流暢的)
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);
複製代碼
好了,Path完結散花
項目源碼 | 日期 | 備註 |
---|---|---|
V0.1--無 | 2018-11-6 | Android關於Path你所知道的和不知道的一切 |
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
個人github | 個人簡書 | 個人CSDN | 我的網站 |
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持