先上效果圖:
一、PathMeasure
在繪製畫布時,我們通常會使用Path對象來勾勒出繪製的路徑,而PathMeasure可以讓我們得到路徑上的一些信息,比如獲取路徑上點的座標,截取路徑上的某一段小路徑等。。
它主要有既個方法:
setPath(Path path, boolean forceClosed);
與一個Path對象綁定,forceClose爲true時,不管關聯的Path是否是閉合的,都會被閉合。
getLength()
獲取路徑的長度
getPosTan(float distance, float pos[],float tan[])
distance 爲一個 0 - getLength() 之間的值,根據這個距離值計算出當前點的xy座標和正切值封裝到 pos和tan當中;
boolean getSegment (float startD, float stopD, Path dst, boolean startWithMoveTo)
判斷截取路徑成功時返回true;
startD爲開始截取位置距離 Path 起點的長度
stopD 結束截取位置距離 Path 起點的長度
取值範圍: 0 <= startD < stopD <= Path總長度getLength(),超過範圍截取不成功截取的 Path 將會添加到 dst 中
startWithMoveTo 起始點是否使用 moveTo,用於保證截取的 Path 第一個點位置不變(一般爲true)
在默認開啓硬件加速的情況下,更改 dst 的內容後可能繪製會出現問題,可以給 dst 添加一個操作:dst.lineTo(0, 0)
二、自定義動畫控件
大概思路就是:
創建一個勾勒空心圓的Path對象
再根據上面的Path對象創建PathMeasure對象
最後再通過屬性動畫不斷動態獲取startD和stopD的值,並更新View
public class LoadingCircleView extends View {
private Path path;
private PathMeasure pathMeasure;
private Path dst; // 被截取的路徑
private Paint paint; // 畫筆
// View的寬高
private int height;
private int width;
private float radius; // 空心圓的半徑
private float mLength; // path路徑的長度
ValueAnimator valueAnimator; // 屬性動畫
private float mAnimatorValue; // 屬性動畫返回的百分比
private float stop; // 截取路徑時的stopD值
private float start; // 截取路徑時的startD值
public LoadingCircleView(Context context) {
super(context);
init(); // 初始化
}
public LoadingCircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LoadingCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LoadingCircleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
radius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,21,getContext().getResources().getDisplayMetrics()); // 初始化半徑
path = new Path();
dst = new Path();
paint = new Paint();
pathMeasure = new PathMeasure();
// 設置畫筆屬性
paint.setAntiAlias(true);
paint.setColor(0xbfe46d32);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,3.5f,getContext().getResources().getDisplayMetrics()));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
// 勾勒空心圓
path.reset();
path.addCircle(width/2,height/2,radius, Path.Direction.CW);
// 生成pathMeasure對象
pathMeasure.setPath(path,true);
// 獲取path的長度
mLength = pathMeasure.getLength();
// 通過屬性動畫取得百分比值,並更新View
valueAnimator = ValueAnimator.ofFloat(0,1);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 獲取動畫進行的百分比
mAnimatorValue = (float) animation.getAnimatedValue();
postInvalidate(); // 更新界面
}
});
// 設置動畫的屬性
valueAnimator.setDuration(2100);
valueAnimator.setRepeatCount(1000);
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 只對EXACTLY的條件的寬高處理
setMeasuredDimension(Math.min(widthSize,heightSize),Math.min(widthSize,heightSize));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 爲加強動畫效果,每次對畫布旋轉不同角度
canvas.rotate(360.0f*mAnimatorValue-45.0f,width/2,height/2);
// 初始化截取的路徑
dst.reset();
dst.lineTo(0,0); // 消除硬件加速的影響
// 更新截取的開始值和結束值:當mAnimatorValue爲0或1時,兩個值相等
stop = mAnimatorValue*mLength;
start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * mLength));
// 截取路徑後,並繪製路徑
pathMeasure.getSegment(start, stop, dst, true);
canvas.drawPath(dst,paint);
}
}