Android繪圖之Path總結

Ghui's Blog發表於2016-07-28

Path作為Android中一種相對複雜的繪圖方式,官方文件中的有些解釋並不是很好理解,這裡作一個相對全面一些的總結,供日後檢視,也分享給大家,共同進步。

1.基本繪圖方法

  1. addArc(RectF oval, float startAngle, float sweepAngle)
    繪製弧線,配合Paint的Style可以實現不同的填充效果
  2. addCircle(float x, float y, float radius, Path.Direction dir)
    繪製圓形,其中第dir引數用來指定繪製時是順時針還是逆時針
  3. addOval(RectF oval, Path.Direction dir)
    繪製橢圓形,其中 oval作為橢圓的外切矩形區域
  4. addRect(RectF rect, Path.Direction dir)
    繪製矩形
  5. addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)
    繪製圓角矩形
  6. lineTo(float x, float y)
    繪製直線
  7. addPath(Path src)
    新增一個新的Path到當前Path
  8. arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
    addArc方法相似,但也有區別,下文細述。
  9. quadTo(float x1, float y1, float x2, float y2)
    繪製二次貝塞爾曲線,其中 (x1,y1)為控制點,(x2,y2)為終點
  10. cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
    繪製三次貝塞爾曲線,其中(x1,y1),(x2,y2)為控制點,(x3,y3)為終點

2.rXXX方法

上面的lineTo,MoveTo,QuadTo,CubicTo方法都有與之對應的rXXX方法:

  1. rLineTo(float dx, float dy)
  2. rMoveTo(float dx, float dy)
  3. rQuadTo(float dx1, float dy1, float dx2, float dy2)
  4. rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)

這些方法與之對應的原方法相比,惟一的區別在於:r方法是基於當前繪製開始點的offest,比如當前paint位於 (100,100)處,則使用rLineTo(100,100)方法繪製出來的直線是從(100,100)到(200,200)的一條直接,由此可見rXXX方法方便用來基於之前的繪製作連續繪製。

3.Path.op方法

//原型
op(Path path, Path.Op op)
//eg
path1.op(path2,Path.Op.DIFFERENCE);

此方法用於對兩個Path物件做相應的運算組合(combine),具體的說是根據不同的op引數及path2引數來影響path1物件,有點類似於數學上的集合運算。請看下面的例子:

Path path1 = new Path();
path1.addCircle(150, 150, 100, Path.Direction.CW);
Path path2 = new Path();
path2.addCircle(200, 200, 100, Path.Direction.CW);
path1.op(path2, Path.Op.DIFFERENCE);
canvas.drawPath(path1, paint1);

效果如下:

通過不斷修改path1.op的第二個引數依次可以得到如下效果:

Path.Op.INTERSECT效果:

Path.Op.UNION效果:

Path.Op.REVERSE_DIFFERENCE效果:

Path.Op.XOR效果:

總結:

  1. Path.Op.DIFFERENCE 減去path1中path1與path2都存在的部分;
    path1 = (path1 – path1 ∩ path2)
  2. Path.Op.INTERSECT 保留path1與path2共同的部分;
    path1 = path1 ∩ path2
  3. Path.Op.UNION 取path1與path2的並集;
    path1 = path1 ∪ path2
  4. Path.Op.REVERSE_DIFFERENCE 與DIFFERENCE剛好相反;
    path1 = path2 – (path1 ∩ path2)
  5. Path.Op.XOR 與INTERSECT剛好相反;
    path1 = (path1 ∪ path2) – (path1 ∩ path2)

4.setFillType

設定path的填充模式.網上關於path的FillType的介紹很少,實際上在官方ApiDemos裡就有個很好的例子:

/**
 * Created by ghui on 10/25/15.
 */
public class PathFillTypeView extends View {
	private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	private Path mPath;

	public PathFillTypeView(Context context) {
		super(context);
		setFocusable(true);
		setFocusableInTouchMode(true);

		mPath = new Path();
		mPath.addCircle(40, 40, 45, Path.Direction.CCW);
		mPath.addCircle(80, 80, 45, Path.Direction.CCW);
		mPath.addCircle(120, 120, 45, Path.Direction.CCW);
	}

	private void showPath(Canvas canvas, int x, int y, Path.FillType ft,
						  Paint paint) {
		canvas.save();
		canvas.translate(x, y);
		canvas.clipRect(0, 0, 160, 160);
		canvas.drawColor(Color.WHITE);
		mPath.setFillType(ft);
		canvas.drawPath(mPath, paint);
		canvas.restore();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		Paint paint = mPaint;
		paint.setColor(Color.RED);
		canvas.drawColor(0xFFCCCCCC);
		canvas.translate(20, 20);
		paint.setAntiAlias(true);
		showPath(canvas, 0, 0, Path.FillType.WINDING, paint);
		showPath(canvas, 160 * 2, 0, Path.FillType.EVEN_ODD, paint);
		showPath(canvas, 0, 160 * 2, Path.FillType.INVERSE_WINDING, paint);
		showPath(canvas, 160 * 2, 160 * 2, Path.FillType.INVERSE_EVEN_ODD, paint);
	}
}

效果如下:


(上面的例子在官方ApiDemo的基礎上做了適當的修改)

總結:

所謂填充指的就是填充內部,setFillType就是用來界定哪裡算內部的演算法。在計算機圖形學中界定一個點是不是在多邊形內部有兩種演算法:

  1. 非零環繞數規則(Nonzero-rule)
  2. 奇偶規則(Even–odd rule)

關於這兩種演算法這裡不作詳細介紹。

5.易混淆的方法

1. addArc 與 arcTo

前者指定在某處畫一條弧線,僅此而已,不會受當前paint的位置所影響。而arcTo方法有兩種形式:

  1. arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
  2. arcTo(RectF oval, float startAngle, float sweepAngle)對於第一種形式的方法,若forceMoveTo引數為false,則與第二種形式的方法沒區別,繪製成的最終圖形會受到落筆點的影響;
    若forceMoveTo引數值為true,則繪製效果與addArc方法沒有區別。
//程式碼1
Path path = new Path();
path.moveTo(100, 100);
path.addArc(200, 200, 400, 400, 0, 150);
canvas.drawPath(path, paint);

程式碼1效果如下圖:

//程式碼2
Path path = new Path();
path.moveTo(100, 100);
path.arcTo(200, 200, 400, 400, 0, 150, false);
canvas.drawPath(path,paint);

程式碼2效果如下圖:

若將程式碼2中的arcTo方法的引數修改為true則繪製的效果與程式碼1相同。

2. reset 與 rewind

reset清除path上的內容,重置path到 path = new Path()的初始狀態。
rewind清除path上的內容,但會保留path上相關的資料結構,以高效的複用。

其它方法

  1. moveTo(float x,float y)
    移動畫筆到 (x,y) 處
  2. offset(float dx, float dy)
    平移當前path,在此path上繪製的任何圖形都會受到影響
  3. close()
    閉合當前路徑 (系統會自動從起點到終點繪製一條直線,使當前路徑閉合)
  4. reset()
    重置path,但不會重置fill-type設定
  5. rewind()
    重置path,但會保留內部資料結構
  6. set(Path src)
    設定新的Path到當前物件
  7. setLastPoint(float x,float y)
    設定當前path的終點
  8. transform(Matrix matrix)
    矩陣變換

相關文章