PathMeasure這個(gè)東西還是挺神奇的让禀,我們看到的許多酷炫的動(dòng)畫大多要依靠他,他就像一個(gè)計(jì)算器陨界,你給他一個(gè)path巡揍,他還你路徑總長(zhǎng)、指定長(zhǎng)度的終點(diǎn)坐標(biāo)菌瘪,路徑上某一點(diǎn)的tan腮敌、sin阱当、cos值等等。這次我們來看看怎么用它做一個(gè)飛機(jī)轉(zhuǎn)圈的加載動(dòng)畫糜工,效果如下圖:
先了解PathMeasure的一些方法:
一弊添、初始化
他的初始化有兩種,第一種直接new空的構(gòu)造方法,得到實(shí)例后利用setPath傳入路徑捌木,如:
PathMeasure p=new PathMeasure ();
p.setPath(path,true);
第二種油坝,直接在構(gòu)造時(shí)候傳入path,
PathMeasure p=new PathMeasure (path,true);
我們看到true這個(gè)參數(shù)多次出現(xiàn)刨裆,他代表的是PathMeasure 是否閉合的參數(shù)澈圈,如果為true,那么不管path有沒有閉合帆啃,PathMeasure 都會(huì)閉合瞬女,但是只會(huì)影響PathMeasure 對(duì)path的計(jì)算,而不會(huì)改變path本身努潘。
二诽偷、getLength
顧名思義就是獲取path在計(jì)算后的長(zhǎng)度。下面我們利用getLength看看上面說的true是怎么影響計(jì)算的疯坤。我們先定義一個(gè)自定義view渤刃,如下:
public class MyView extends View {
private Paint paint;
public MyView(Context context) {
this(context,null);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(50,50);
Path path=new Path();
path.moveTo(0,0);
path.lineTo(0,100);
path.lineTo(100,100);
path.lineTo(100,0);
PathMeasure pathMeasure1=new PathMeasure(path,false);
PathMeasure pathMeasure2=new PathMeasure(path,true);
Log.d("yanjin","pathMeasure1的length="+pathMeasure1.getLength()+"--pathMeasure2的length="+pathMeasure2.getLength());
canvas.drawPath(path,paint);
}
}
展示效果如上圖,我們打印的getLength在設(shè)置為true和false的時(shí)候贴膘,會(huì)有不同的數(shù)值卖子,一個(gè)為300,一個(gè)為400刑峡,多出來的100洋闽,大家應(yīng)該也知道在哪來的吧,哈哈哈哈突梦。
三诫舅、nextContour
我們都知道,一個(gè)path就相當(dāng)于一個(gè)集合宫患,他可以不斷地add很多不連續(xù)的路徑PathMeasure只對(duì)連續(xù)的路徑有效果刊懈,那么假如path里面有A/B/C三個(gè)不連續(xù)的線段,怎么計(jì)算他們的值呢娃闲?這里就用到了nextContour函數(shù)虚汛,簡(jiǎn)單的說他就是跳到下一個(gè)線段的作用。比如如下代碼:
public class MyView2 extends View {
private Paint paint;
public MyView2(Context context) {
this(context,null);
}
public MyView2(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public MyView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(150,150);
Path path=new Path();
path.addRect(-50,-50,50,50,Path.Direction.CW);
path.addRect(-100,-100,100,100,Path.Direction.CW);
path.addRect(-120,-120,120,120,Path.Direction.CW);
canvas.drawPath(path,paint);
PathMeasure pathMeasure=new PathMeasure(path,false);//已經(jīng)閉合了皇帮,我們可以傳false卷哩。
do {
float length = pathMeasure.getLength();
Log.d("yanjin","len="+length);
}while (pathMeasure.nextContour());
}
}
輸出的值為:
2019-02-22 15:48:33.787 7889-7889/com.easy.customeasytablayout.customviews D/yanjin: len=400.0
2019-02-22 15:48:33.787 7889-7889/com.easy.customeasytablayout.customviews D/yanjin: len=800.0
2019-02-22 15:48:33.787 7889-7889/com.easy.customeasytablayout.customviews D/yanjin: len=960.0
我們可以得出以下結(jié)論:
1、nextContour函數(shù)得到的path循序與我們path.add時(shí)順序一樣属拾。
2将谊、getLength針對(duì)的是當(dāng)前線段冷溶,不是整個(gè)path。
四尊浓、getSegment函數(shù)
他的定義如下:
public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
getSegment是用來截取一段path的逞频,通過startD與stopD設(shè)置起始點(diǎn)。然后將截取的path存到dst中栋齿,startWithMoveTo表示是否使用moveTo虏劲,將路徑的新起點(diǎn)移動(dòng)到結(jié)果path的起點(diǎn),一般為true褒颈。
進(jìn)過上面的介紹,我們可以先寫一個(gè)常見的加載動(dòng)畫了励堡,動(dòng)畫效果如下:
這個(gè)很常見吧谷丸,下面來講講他的代碼。
先寫自定義CirclePathAnimView代碼
public class CirclePathAnimView extends View {
private float mAnimatorValue;
private PathMeasure mPathMeasure;
private Path mDevPath;
private Paint mPaint;
private ValueAnimator mValueAnimator;
public CirclePathAnimView(Context context) {
this(context, null);
}
public CirclePathAnimView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CirclePathAnimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayerType(LAYER_TYPE_SOFTWARE, null);//關(guān)閉硬件加速
//初始化畫筆
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(getResources().getDimension(R.dimen.dp_3));
mPaint.setColor(getResources().getColor(R.color.colorPrimary));
//畫真正顯示的path
mDevPath = new Path();
//開始動(dòng)畫应结,當(dāng)然當(dāng)前動(dòng)畫你可以單獨(dú)寫成一個(gè)方法
mValueAnimator = ValueAnimator.ofFloat(0, 1);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.setDuration(2000);
mValueAnimator.setRepeatCount(-1);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatorValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
mValueAnimator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int radius = 0;
if (width >= height) {
radius = height / 2 - height / 8;
} else {
radius = width / 2 - width / 8;
}
//繪制path
//先畫圓的path刨疼,但是這個(gè)圓只是用來計(jì)算
Path circlePath = new Path();
circlePath.addCircle(width / 2, height / 2, radius, Path.Direction.CW);
//計(jì)算圓的path的長(zhǎng)度
mPathMeasure = new PathMeasure(circlePath, true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float length = mPathMeasure.getLength();
float stop = length * mAnimatorValue;
//在0到0.5以前,起點(diǎn)不變鹅龄,0.5到1揩慕,起點(diǎn)開始向終點(diǎn)靠攏。
float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * length));
mDevPath.reset();
mPathMeasure.getSegment(start, stop, mDevPath, true);
canvas.drawPath(mDevPath, mPaint);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mValueAnimator.cancel();
mValueAnimator = null;
}
}
我們可以先看構(gòu)造方法扮休,我們先設(shè)置mPaint 迎卤,然后開啟動(dòng)畫,其實(shí)這個(gè)動(dòng)畫可以另寫一個(gè)方法玷坠,手動(dòng)掉一下蜗搔,這里為了方便就寫在這了,可以看到動(dòng)畫的更新監(jiān)聽里面我們獲取動(dòng)畫值之后八堡,調(diào)用invalidate刷新界面樟凄,這樣會(huì)重走onDraw方法,這里講onDraw之前兄渺,先看看onMeasure缝龄。
onMeasure里面我們主要拿到控件自己的寬高,設(shè)置了一個(gè)圓形Path--》circlePath 挂谍,但是這個(gè)circlePath 并沒有被畫出來叔壤,他只是用來被截取的,mPathMeasure 存入這個(gè)circlePath 口叙。
然后動(dòng)畫中每調(diào)用invalidate進(jìn)入onDraw的時(shí)候百新,拿動(dòng)畫值mAnimatorValue*path總長(zhǎng)得到當(dāng)前終點(diǎn),起點(diǎn)的話庐扫,我們采取在0到0.5以前饭望,起點(diǎn)不變仗哨,0.5到1,起點(diǎn)開始向終點(diǎn)靠攏的算法獲得起點(diǎn)铅辞。這樣我們就能調(diào)用截取方法了
mPathMeasure.getSegment(start, stop, mDevPath, true);
截取后原本為空的mDevPath就有數(shù)據(jù)了厌漂,我們就可以把它畫下來了。為了讓他看起來更有意思斟珊,我們?cè)贏ctivity中對(duì)他整個(gè)空間進(jìn)行旋轉(zhuǎn)苇倡,
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main29);
CirclePathAnimView circlePathAnimView = findViewById(R.id.view);
ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(circlePathAnimView,"rotation",0,360);
objectAnimator.setRepeatCount(-1);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setDuration(2500);
objectAnimator.start();
}
就能看到上面的效果了。
說了半天囤踩,答應(yīng)的飛機(jī)呢旨椒?這樣,我先上代碼堵漱。還是那個(gè)自定義View综慎,我只是改了一點(diǎn)點(diǎn)代碼。
public class CirclePathAnimView extends View {
private float mAnimatorValue;
private PathMeasure mPathMeasure;
private Path mDevPath;
private Paint mPaint;
private ValueAnimator mValueAnimator;
private Bitmap airplayBitmap;
public CirclePathAnimView(Context context) {
this(context, null);
}
public CirclePathAnimView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CirclePathAnimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayerType(LAYER_TYPE_SOFTWARE, null);//關(guān)閉硬件加速
//初始化畫筆
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(getResources().getDimension(R.dimen.dp_2));
mPaint.setColor(getResources().getColor(R.color.colorPrimary));
//飛機(jī)圖片
airplayBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.airplay);
//畫真正顯示的path
mDevPath = new Path();
//開始動(dòng)畫勤庐,當(dāng)然當(dāng)前動(dòng)畫你可以單獨(dú)寫成一個(gè)方法
mValueAnimator = ValueAnimator.ofFloat(0, 1);
mValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mValueAnimator.setDuration(3000);
mValueAnimator.setRepeatCount(-1);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatorValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
mValueAnimator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int radius = 0;
if (width >= height) {
radius = height / 2 - height / 8;
} else {
radius = width / 2 - width / 8;
}
//繪制path
//先畫圓的path示惊,但是這個(gè)圓只是用來計(jì)算
Path circlePath = new Path();
circlePath.addCircle(width / 2, height / 2, radius, Path.Direction.CW);
//計(jì)算圓的path的長(zhǎng)度
mPathMeasure = new PathMeasure(circlePath, true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float length = mPathMeasure.getLength();
float stop = length * mAnimatorValue;
//在0到0.5以前,起點(diǎn)不變愉镰,0.5到1米罚,起點(diǎn)開始向終點(diǎn)靠攏。
float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * length));
mDevPath.reset();
mPathMeasure.getSegment(start, stop, mDevPath, true);
canvas.drawPath(mDevPath, mPaint);
Matrix matrix=new Matrix();
mPathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG|PathMeasure.TANGENT_MATRIX_FLAG);
matrix.preTranslate(-airplayBitmap.getWidth()/2,-airplayBitmap.getHeight()/2);
canvas.drawBitmap(airplayBitmap,matrix,mPaint);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mValueAnimator.cancel();
mValueAnimator = null;
}
}
在構(gòu)造方法中丈探,我們先獲取圖片
airplayBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.airplay);
在onDraw中录择,我們把飛機(jī)畫上去。
Matrix matrix=new Matrix();
mPathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG|PathMeasure.TANGENT_MATRIX_FLAG);
matrix.preTranslate(-airplayBitmap.getWidth()/2,-airplayBitmap.getHeight()/2);
canvas.drawBitmap(airplayBitmap,matrix,mPaint);
就這么簡(jiǎn)單碗降。
看是這么簡(jiǎn)單糊肠,但是里面有個(gè)getMatrix函數(shù)我們必須要講一講。
五遗锣、getMatrix函數(shù)
getMatrix函數(shù)可以獲得某一長(zhǎng)度終點(diǎn)的坐標(biāo)以及該坐標(biāo)的正切值的矩陣货裹。
public boolean getMatrix(float distance, Matrix matrix, int flags)
distance指的是path長(zhǎng)度,
matrix指的是容器精偿,計(jì)算后會(huì)把結(jié)果存進(jìn)來弧圆。
flags指的是要存入哪些內(nèi)容,POSITION_MATRIX_FLAG是位置信息笔咽,TANGENT_MATRIX_FLAG是切邊信息搔预。
圖片中箭頭代表飛機(jī),飛機(jī)沒飛一點(diǎn)叶组,就要調(diào)整角度拯田,他的方向基本要與切線一樣,那么根據(jù)圖中所示甩十,角a+角b=90度船庇,角a=角c吭产,所以飛機(jī)頭要掉角c這么多度數(shù),而getMatrix就是能獲取這些正切值鸭轮。結(jié)合畫圖也有傳Matrix的方式臣淤,剛剛好。
有時(shí)間再更新個(gè)支付寶支付成功的動(dòng)畫窃爷,嘻嘻邑蒋。
對(duì)了,不喜勿噴哦按厘!医吊,我的心臟很弱小的。