Android 繪圖學(xué)習(xí)
android繪圖之Paint(1)
android繪圖之Canvas基礎(chǔ)(2)
Android繪圖之Path(3)
Android繪圖之drawText繪制文本相關(guān)(4)
Android繪圖之Canvas概念理解(5)
Android繪圖之Canvas變換(6)
Android繪圖之Canvas狀態(tài)保存和恢復(fù)(7)
Android繪圖之PathEffect (8)
Android繪圖之LinearGradient線性漸變(9)
Android繪圖之SweepGradient(10)
Android繪圖之RadialGradient 放射漸變(11)
Android繪制之BitmapShader(12)
Android繪圖之ComposeShader,PorterDuff.mode及Xfermode(13)
Android繪圖之drawText,getTextBounds,measureText,FontMetrics,基線(14)
Android繪圖之貝塞爾曲線簡(jiǎn)介(15)
Android繪圖之PathMeasure(16)
Android 動(dòng)態(tài)修改漸變 GradientDrawable
1 PathMeasure概述
首先思考一個(gè)問(wèn)題,任意繪制一條曲線,或者一個(gè)圓弧,或者一個(gè)不規(guī)則圖形,又或者利用Path 同時(shí)繪制多條曲線,如何獲取某個(gè)點(diǎn)的坐標(biāo),曲線的方向,對(duì)于簡(jiǎn)單的圖形根據(jù)已知內(nèi)容很容易得到坐標(biāo),對(duì)于類(lèi)似貝塞爾曲線或者不規(guī)則圖形想要或者坐標(biāo),角度信息很困難,今天就來(lái)講解Path中用于獲取上述信息的一個(gè)工具類(lèi)。
PathMeasure是一個(gè)用來(lái)測(cè)量Path的工具類(lèi),可以從Path中獲取固定位置的坐標(biāo),橫切信息。
構(gòu)造函數(shù):
/**
* Create an empty PathMeasure object. To uses this to measure the length
* of a path, and/or to find the position and tangent along it, call
* setPath.
*/
public PathMeasure() ;
/**
* Create a PathMeasure object associated with the specified path object
* (already created and specified). The measure object can now return the
* path's length, and the position and tangent of any position along the
* path.
*
* Note that once a path is associated with the measure object, it is
* undefined if the path is subsequently modified and the the measure object
* is used. If the path is modified, you must call setPath with the path.
*
* @param path The path that will be measured by this object
* @param forceClosed If true, then the path will be considered as "closed"
* even if its contour was not explicitly closed.
*/
public PathMeasure(Path path, boolean forceClosed) ;
主要有兩個(gè)構(gòu)造函數(shù):
PathMeasure()
無(wú)參構(gòu)造函數(shù),構(gòu)造一個(gè)空的PathMeasure,想要使用必須和一個(gè)Path進(jìn)行關(guān)聯(lián),可以利用setPath函數(shù)和Path設(shè)置關(guān)聯(lián)。如果關(guān)聯(lián)的Path被修改了,也需要重新調(diào)用setPath進(jìn)行關(guān)聯(lián)。
PathMeasure(Path path, boolean forceClosed)
參數(shù)說(shuō)明:
path:關(guān)聯(lián)的Path,如果想要重新關(guān)聯(lián)新的path,可以調(diào)用函數(shù)setPath
forceClosed:設(shè)置測(cè)量Path時(shí)是否強(qiáng)制閉合path(有的Path不是閉合的,如果為true,那么測(cè)量時(shí)就會(huì)測(cè)量閉合的path,所以使用時(shí)要注意,forceClose為true時(shí)測(cè)量的長(zhǎng)度可能更大。如果關(guān)聯(lián)的Path被修改了,也需要重新調(diào)用setPath進(jìn)行關(guān)聯(lián)。
所以可以得出:
設(shè)置關(guān)聯(lián)的Path之后,Path就不會(huì)再修改,就算原Path修改了,引用的Path也不會(huì)修改,想要應(yīng)用原Path的修改就需要重新setPath。
forceClosed 代碼示例
public class ViewDemo25 extends View {
private Path mPath;
private Paint mPaint1;
private Paint mPaint2;
public ViewDemo25(Context context) {
this(context,null,0);
}
public ViewDemo25(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public ViewDemo25(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint1.setStrokeWidth(7);
mPaint1.setStyle(Paint.Style.STROKE);
mPaint1.setColor(Color.RED);
mPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint2.setStrokeWidth(7);
mPaint2.setColor(Color.RED);
mPath = new Path();
mPath.moveTo(100,100);
mPath.lineTo(600,300);
mPath.lineTo(300,500);
PathMeasure pathMeasure1 = new PathMeasure(mPath, true);
PathMeasure pathMeasure2 = new PathMeasure(mPath, false);
System.out.println("===========pathMeasure1========"+pathMeasure1.getLength());
System.out.println("===========pathMeasure2========"+pathMeasure2.getLength());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath,mPaint1);
}
}

結(jié)果值:
===========pathMeasure1========1346.2852
===========pathMeasure2========899.0716
Path是個(gè)非閉合曲線,forceClose為true獲取到的Path長(zhǎng)度明顯大于forceClose為flase時(shí)的值。
2 PathMeasure 方法說(shuō)明

- getLength: 用于獲取 Path 的總長(zhǎng)度,
- getMatrix(float distance,Matrix matrix,int flags):獲取指定長(zhǎng)度的位置(distance)坐標(biāo)及該點(diǎn)Matrix
- getPosTan(float distance,float[] pos,float[] tan):獲取指定位置的坐標(biāo)和tan值,tan值代表曲線方向,也就是切線值,
- getSegment(float startD,float stopD,Path dst,boolean startWithMoveTo):獲取兩點(diǎn)之間的path片段,
- setPath 設(shè)置 PathMeasure 與 Path 關(guān)聯(lián)。
- isClosed :用于判斷 Path 是否閉合,但需要注意一點(diǎn),它不單單指Path本身的閉合,還指如果 關(guān)聯(lián) Path 的時(shí)候設(shè)置 forceClosed 為 true 的話,方法也返回true。
- nextContour():獲取下一條曲線的Path,因?yàn)镻ath可能包含多個(gè)相互之間不連接的Path。
3 setpath(Path path ,boolean forceClosed)
設(shè)置PathMeasure關(guān)聯(lián)的Path,如果已設(shè)置的Path改變了,需要重新調(diào)用setPath,已設(shè)置的Path不會(huì)更新。
forceClosed:設(shè)置測(cè)量Path時(shí)是否強(qiáng)制閉合path(有的Path不是閉合的,如果為true,那么測(cè)量時(shí)就會(huì)測(cè)量閉合的path,所以使用時(shí)要注意,forceClose為true時(shí)測(cè)量的長(zhǎng)度可能更大。
無(wú)論傳入的forceClosed為true或者false,都不會(huì)影響原Path的繪制。
代碼示例:
private void init() {
mPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint1.setStrokeWidth(7);
mPaint1.setStyle(Paint.Style.STROKE);
mPaint1.setColor(Color.RED);
mPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint2.setStrokeWidth(7);
mPaint2.setColor(Color.RED);
mPath = new Path();
mPath.moveTo(100,100);
mPath.lineTo(600,300);
mPath.lineTo(300,500);
PathMeasure pathMeasure1 = new PathMeasure();
pathMeasure1.setPath(mPath, true);
PathMeasure pathMeasure2 = new PathMeasure();
pathMeasure2.setPath(mPath, false);
System.out.println("===========pathMeasure1===1111====="+pathMeasure1.getLength());
System.out.println("===========pathMeasure2====2222===="+pathMeasure2.getLength());
mPath.lineTo(200,400);
System.out.println("===========pathMeasure1===33333====="+pathMeasure1.getLength());
System.out.println("===========pathMeasure2====4444===="+pathMeasure2.getLength());
pathMeasure1.setPath(mPath, true);
pathMeasure2.setPath(mPath, false);
System.out.println("===========pathMeasure1===55555====="+pathMeasure1.getLength());
System.out.println("===========pathMeasure2====66666===="+pathMeasure2.getLength());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath,mPaint1);
}

結(jié)果:
===========pathMeasure1===1111=====1346.2852
===========pathMeasure2====2222====899.0716
修改path后沒(méi)有調(diào)用setPath的結(jié)果:
===========pathMeasure1===33333=====1346.2852
===========pathMeasure2====4444====899.0716
修改path后調(diào)用setPath的結(jié)果:
===========pathMeasure1===55555=====1356.7207
===========pathMeasure2====66666====1040.4929
上面的例子首先測(cè)試了forceClosed 為true和false時(shí)的不同,接著測(cè)試了在生成PathMeasure實(shí)例后,修改關(guān)聯(lián)的Path,再次獲取PathMeasure信息,信息沒(méi)有改變,之后重新調(diào)用setPath后,信息才改變。
所以如果測(cè)量過(guò)程中PathMeasure關(guān)聯(lián)的Path發(fā)生改變,需要重新調(diào)用setPath方法進(jìn)行關(guān)聯(lián)。
4 nextContour
/**
* Move to the next contour in the path. Return true if one exists, or
* false if we're done with the path.
*/
public boolean nextContour();
由于后續(xù)需要,先講解nextContour,看到這個(gè)函數(shù)取下一條曲線,也就是說(shuō)明了一個(gè)Path可以有多條曲線。默認(rèn)情況 getLength , getSegment 等PathMeasure方法,都只會(huì)在其中第一條線段上運(yùn)行, nextContour 會(huì)跳轉(zhuǎn)到下一條曲線到方法,如果跳轉(zhuǎn)成功,則返回 true, 如果跳轉(zhuǎn)失敗,則返回 false。
代碼示例:
mPath = new Path();
mPath.moveTo(100,100);
mPath.lineTo(600,300);
mPath.lineTo(300,500);
mPath.addRect(400,400,900,900, Path.Direction.CW);
PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(mPath,false);
System.out.println("======getLength1111===="+pathMeasure.getLength());
if (pathMeasure.nextContour()){
System.out.println("======getLength2222===="+pathMeasure.getLength());
}

輸出結(jié)果:
======getLength1111====899.0716
======getLength2222====2000.0
nextContour()返回true,移動(dòng)到下一條曲線成功,使PathMeasure的方法作用于第二條曲線上。
5 getLength
/**
* Return the total length of the current contour, or 0 if no path is
* associated with this measure object.
*/
public float getLength()
返回當(dāng)前輪廓的總長(zhǎng)度,根據(jù)nextContour()的介紹,知道返回的長(zhǎng)度默認(rèn)只是對(duì)第一條曲線的長(zhǎng)度的計(jì)算。如果想要計(jì)算Path中所有曲線的總長(zhǎng)度,需要循環(huán)調(diào)用nextContour,然后計(jì)算每條線段的長(zhǎng)度,然后相加得到。
6 isClosed
用于判斷 Path 是否閉合,但需要注意一點(diǎn),它不單單指Path本身的閉合,還指如果 關(guān)聯(lián) Path 的時(shí)候設(shè)置 forceClosed 為 true 的話,方法也返回true。
mPath = new Path();
mPath.moveTo(100,100);
mPath.lineTo(600,300);
mPath.lineTo(300,500);
mPath.addRect(400,400,900,900, Path.Direction.CW);
PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(mPath,false);
System.out.println("======isclosed111===="+pathMeasure.isClosed());
if (pathMeasure.nextContour()){
System.out.println("======isclosed222===="+pathMeasure.isClosed());
}
setPath是forceClosed傳入false,結(jié)果為:
======isclosed111====false
======isclosed222====true
如果forceClosed傳入true:
======isclosed111====true
======isclosed222====true
所以isClosed判斷曲線是否閉合受forceClosed的影響。
7 getSegment
getSegment獲取Path中的一段Path。
/**
* Given a start and stop distance, return in dst the intervening
* segment(s). If the segment is zero-length, return false, else return
* true. startD and stopD are pinned to legal values (0..getLength()).
* If startD >= stopD then return false (and leave dst untouched).
* Begin the segment with a moveTo if startWithMoveTo is true.
*
* <p>On {@link android.os.Build.VERSION_CODES#KITKAT} and earlier
* releases, the resulting path may not display on a hardware-accelerated
* Canvas. A simple workaround is to add a single operation to this path,
* such as <code>dst.rLineTo(0, 0)</code>.</p>
*/
public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) ;
參數(shù)說(shuō)明:
startD,stopD:開(kāi)始截取和結(jié)束截取的位置,兩個(gè)參數(shù)都是距離Path起點(diǎn)的長(zhǎng)度。
如果startD 大于stopD直接返回false,如果截取的長(zhǎng)度等于0直接返回false,startD和stopD都大于0小于Path的總長(zhǎng)度。
dst:如果截取成功,截取到的Path將被加入到dst中,不會(huì)影響dst中原有的曲線。
startWithMoveTo:起始點(diǎn)是否使用moveTo,如果startWithMoveTo為true,截取的Path放入dst時(shí)將會(huì)使用moveto,這樣可以保證截取的Path起始點(diǎn)不變化。
返回值:返回true表示截取片段成功,返回false表示不成功,不操作dst。
注意:如果系統(tǒng)版本低于等于4.4,默認(rèn)開(kāi)啟硬件加速的情況下,更改 dst 的內(nèi)容后可能繪制會(huì)出現(xiàn)問(wèn)題,請(qǐng)關(guān)閉硬件加速或者給 dst 添加一個(gè)單個(gè)操作,例如: dst.rLineTo(0, 0)
首先測(cè)試截取成功,是替換dst,還是把截取到的Path添加到dst中。
原始圖形:
private void init() {
mPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint1.setStrokeWidth(7);
mPaint1.setStyle(Paint.Style.STROKE);
mPaint1.setColor(Color.BLUE);
mPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint2.setStrokeWidth(7);
mPaint2.setStyle(Paint.Style.STROKE);
mPaint2.setColor(Color.RED);
dst = new Path();
dst.moveTo(300,500);
dst.lineTo(700,700);
mPath = new Path();
mPath.addRect(200,200,900,900, Path.Direction.CW);
PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(mPath,true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath,mPaint1);
canvas.drawPath(dst,mPaint2);
}

想要截取右下角的Path,示意圖如下:

上面的圖形是個(gè)正方形,開(kāi)始繪制的點(diǎn)(200,200),每個(gè)邊長(zhǎng)700,所以截取的segment開(kāi)始點(diǎn)距離Path的開(kāi)始點(diǎn)為700+350=1050,結(jié)束點(diǎn)為1400+350=1750。
private void init() {
mPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint1.setStrokeWidth(7);
mPaint1.setStyle(Paint.Style.STROKE);
mPaint1.setColor(Color.BLUE);
mPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint2.setStrokeWidth(7);
mPaint2.setStyle(Paint.Style.STROKE);
mPaint2.setColor(Color.RED);
dst = new Path();
dst.moveTo(300,500);
dst.lineTo(700,700);
mPath = new Path();
mPath.addRect(200,200,900,900, Path.Direction.CW);
PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(mPath,true);
pathMeasure.getSegment(1050, 1750, dst, true);
}

截取成功,然后添加截取到的Path到dst,dst中原來(lái)的Path完全沒(méi)有受影響。
如果設(shè)置startWithMoveTo為false:

可以看到如果startWithMoveTo 為 false,獲取到的segment片段,加入到dst時(shí),會(huì)將片段的起始點(diǎn)移動(dòng)到 dst 的最后一個(gè)點(diǎn),以保證 dst 的連續(xù)性,簡(jiǎn)單點(diǎn)說(shuō)會(huì)忽略截取到的Path的第一個(gè)點(diǎn)替換成dst內(nèi)部已有的最后一個(gè)點(diǎn)。
總結(jié):
如果 startWithMoveTo 為 true, 獲取到的片段Path會(huì)因?yàn)檎{(diào)用了moveTo保持原狀,如果 startWithMoveTo 為 false,則會(huì)將截取出來(lái)的 Path 片段的起始點(diǎn)移動(dòng)到 dst 的最后一個(gè)點(diǎn),以保證 dst 的連續(xù)性。
8 getPosTan
/**
* Pins distance to 0 <= distance <= getLength(), and then computes the
* corresponding position and tangent. Returns false if there is no path,
* or a zero-length path was specified, in which case position and tangent
* are unchanged.
*
* @param distance The distance along the current contour to sample
* @param pos If not null, returns the sampled position (x==[0], y==[1])
* @param tan If not null, returns the sampled tangent (x==[0], y==[1])
* @return false if there was no path associated with this measure object
*/
public boolean getPosTan(float distance, float pos[], float tan[])
獲取Path路徑上距離起點(diǎn)位置distance距離的坐標(biāo)信息和tan值。
參數(shù)說(shuō)明:
distance:距離Path起點(diǎn)的位置長(zhǎng)度,
pos:獲取該點(diǎn)的坐標(biāo)值
tan:改點(diǎn)的正切值,tan0是鄰邊邊長(zhǎng),tan1是對(duì)邊邊長(zhǎng)
用ps鋼筆工具自己畫(huà)個(gè)箭頭。

代碼示例:
private void init() {
mPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint1.setStrokeWidth(7);
mPaint1.setStyle(Paint.Style.STROKE);
mPaint1.setColor(Color.BLUE);
mPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint2.setStrokeWidth(7);
mPaint2.setStyle(Paint.Style.STROKE);
mPaint2.setColor(Color.RED);
dst = new Path();
dst.moveTo(300,500);
dst.lineTo(700,700);
mPath = new Path();
mPath.addCircle(500,500,300, Path.Direction.CW);
PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(mPath,false);
float zhouchang = (float) (2*Math.PI*300);
System.out.println("=======circle周長(zhǎng)========"+ zhouchang);
float[] pos = new float[2];
float[] tan = new float[2] ;
pathMeasure.getPosTan(zhouchang*3/4,pos,tan);
System.out.println("=========pos==========="+pos[0]+" "+pos[1]);
System.out.println("=========tan==========="+tan[0]+" "+tan[1]);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath,mPaint1);
canvas.drawPoint(500,500,mPaint2);
}

輸出結(jié)果:有稍微的誤差是由于精度問(wèn)題導(dǎo)致
=======circle周長(zhǎng)========1884.9556
=========pos===========500.56152 200.00053
=========tan===========0.9999983 0.0018716537
tan的值有什么用呢,我們可以利用切線值得到當(dāng)前線段的方向,也就是傾斜的角度。
float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
Math中 atan2 可以根據(jù)正切值得到角度值(也可以自己計(jì)算)弧度,然后將弧度轉(zhuǎn)換為具體的度數(shù)。
如果不利用獲取到的tan,直接根據(jù)坐標(biāo)點(diǎn)繪制圖片
private void init() {
mPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint1.setStrokeWidth(7);
mPaint1.setStyle(Paint.Style.STROKE);
mPaint1.setColor(Color.BLUE);
mPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint2.setStrokeWidth(7);
mPaint2.setStyle(Paint.Style.STROKE);
mPaint2.setColor(Color.RED);
dst = new Path();
dst.moveTo(300,500);
dst.lineTo(700,700);
jiantou = BitmapFactory.decodeResource(getResources(),R.drawable.jiantou);
mPath = new Path();
mPath.addCircle(500,500,300, Path.Direction.CW);
final PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(mPath,false);
final float zhouchang = (float) (2*Math.PI*300);
System.out.println("=======circle周長(zhǎng)========"+ zhouchang);
float[] pos = new float[2];
float[] tan = new float[2] ;
pathMeasure.getPosTan(zhouchang*3/4,pos,tan);
System.out.println("=========pos==========="+pos[0]+" "+pos[1]);
System.out.println("=========tan==========="+tan[0]+" "+tan[1]);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,zhouchang);
valueAnimator.setDuration(10000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(-1);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float correntLen = (float) animation.getAnimatedValue();
float[] pos = new float[2];
float[] tan = new float[2] ;
pathMeasure.getPosTan(correntLen,pos,tan);
posX = pos[0];
posY = pos[1];
invalidate();
}
});
valueAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath,mPaint1);
canvas.drawPoint(500,500,mPaint2);
canvas.drawBitmap(jiantou,posX,posY,mPaint2);
}

圖片從左上角開(kāi)始繪制,左上角會(huì)沿著坐標(biāo)旋轉(zhuǎn),看著很不舒服。
對(duì)圖片進(jìn)行操作:
利用獲取的tan值對(duì)圖片進(jìn)行旋轉(zhuǎn)和平移操作,注意這里的旋轉(zhuǎn)操作時(shí)建立在圖片的初始箭頭方向?yàn)樗较蛴业模绻较蜃?,則計(jì)算圖片旋轉(zhuǎn)方向時(shí)時(shí)不相同的(和繪制方向無(wú)關(guān))。
private void init() {
mPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint1.setStrokeWidth(7);
mPaint1.setStyle(Paint.Style.STROKE);
mPaint1.setColor(Color.BLUE);
mPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint2.setStrokeWidth(7);
mPaint2.setStyle(Paint.Style.STROKE);
mPaint2.setColor(Color.RED);
dst = new Path();
dst.moveTo(300,500);
dst.lineTo(700,700);
mMatrix = new Matrix();
jiantou = BitmapFactory.decodeResource(getResources(),R.drawable.jiantou);
mPath = new Path();
mPath.addCircle(500,500,300, Path.Direction.CCW);
final PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(mPath,false);
final float zhouchang = (float) (2*Math.PI*300);
System.out.println("=======circle周長(zhǎng)========"+ zhouchang);
float[] pos = new float[2];
float[] tan = new float[2] ;
pathMeasure.getPosTan(zhouchang*3/4,pos,tan);
System.out.println("=========pos==========="+pos[0]+" "+pos[1]);
System.out.println("=========tan==========="+tan[0]+" "+tan[1]);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,zhouchang);
valueAnimator.setDuration(10000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(-1);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float correntLen = (float) animation.getAnimatedValue();
float[] pos = new float[2];
float[] tan = new float[2] ;
pathMeasure.getPosTan(correntLen,pos,tan);
posX = pos[0];
posY = pos[1];
mMatrix.reset();
//得到當(dāng)前坐標(biāo)的方向趨勢(shì)對(duì)應(yīng)的角度
float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
//下面兩個(gè)比較重要,以圖片中心為旋轉(zhuǎn)點(diǎn)對(duì)圖片進(jìn)行旋轉(zhuǎn),把圖片中心平移到Path上
mMatrix.postRotate(degrees, jiantou.getWidth() / 2, jiantou.getHeight() / 2);
mMatrix.postTranslate(pos[0] - jiantou.getWidth() / 2, pos[1] - jiantou.getHeight() / 2);
invalidate();
}
});
valueAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath,mPaint1);
canvas.drawPoint(500,500,mPaint2);
canvas.drawBitmap(jiantou,mMatrix,mPaint2);
}
}

9 getMatrix
/**
* Pins distance to 0 <= distance <= getLength(), and then computes the
* corresponding matrix. Returns false if there is no path, or a zero-length
* path was specified, in which case matrix is unchanged.
*
* @param distance The distance along the associated path
* @param matrix Allocated by the caller, this is set to the transformation
* associated with the position and tangent at the specified distance
* @param flags Specified what aspects should be returned in the matrix.
*/
public boolean getMatrix(float distance, Matrix matrix, int flags)
上面的例子利用getPosTan獲取正切值,然后計(jì)算位置和旋轉(zhuǎn)角度過(guò)程很麻煩,所以google提供了getmatrix直接獲取變換后的Matrix矩陣供我們使用。
參數(shù)說(shuō)明:
distance:距離Path起始點(diǎn)的距離
matrix:得到的matrix矩陣
flags:規(guī)定哪些信息添加到Matrix中,flags有兩個(gè)值POSITION_MATRIX_FLAG,TANGENT_MATRIX_FLAG。POSITION_MATRIX_FLAG表示把位置信息添加到matrix中,TANGENT_MATRIX_FLAG表示把正切信息添加到matrix中。
可以分別添加POSITION_MATRIX_FLAG和TANGENT_MATRIX_FLAG,如果想同時(shí)添加這兩者需要POSITION_MATRIX_FLAG|TANGENT_MATRIX_FLAG。
利用getMatrix實(shí)現(xiàn)箭頭功能。
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float correntLen = (float) animation.getAnimatedValue();
float[] pos = new float[2];
float[] tan = new float[2] ;
pathMeasure.getPosTan(correntLen,pos,tan);
mMatrix.reset();
pathMeasure.getMatrix(correntLen,mMatrix,PathMeasure.TANGENT_MATRIX_FLAG|PathMeasure.POSITION_MATRIX_FLAG);
//矩陣對(duì)旋轉(zhuǎn)角度默認(rèn)為圖片的左上角,使用 preTranslate 調(diào)整原點(diǎn)為圖片中心
mMatrix.preTranslate(-jiantou.getWidth() / 2, -jiantou.getHeight() / 2);
invalidate();
}
});
