把mCircleValueAnimator的duration設置大於零可以先畫背景再畫對號,修改畫筆樣式可以把背景化成圈或者純色背景。
package com.lianzhuo.qukanba.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
/**
* create by chenyingjie on 2019/10/29
* desc
*/
public class MarkView extends View {
private Paint mPaint;
private Paint hootPaint;
private PathMeasure mPathMeasure;
/**
* 圓環路徑
*/
private Path mCirclePath;
/**
* 截取的路徑
*/
private Path mDstPath;
/**
* Path 長度
*/
private float mPathLength;
/**
* 動畫估值
*/
private float mAnimatorValue;
/**
* 圓環是否已經加載過
*/
private boolean mIsHasCircle = false;
/**
* View 是個正方形,寬高中小的一個值,根據小的值來定位繪製
*/
private int mRealSize;
/**
* 顏色
*/
private int mStartColor = Color.parseColor("#d0021b");
private int mEndColor = Color.parseColor("#d0021b");
/**
* 畫筆寬度
*/
private float mStrokeWidth = 4f;
/**
* 圓形動畫
*/
private ValueAnimator mCircleValueAnimator;
/**
* 對號
*/
private ValueAnimator mRightMarkValueAnimator;
/**
* 默認大小
*/
private static final int DEFAULT_SIZE = 15;
/**
* 動畫執行時間
*/
public static final int ANIMATOR_TIME = 1000;
public MarkView(Context context) {
this(context, null);
}
public MarkView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
// 初始化畫筆
initPaint();
// 初始化動畫
initCircleAnimator();
// 利用 post 獲取 View 的寬高
// post 內任務,會在第2次執行 onMeasure() 方法後執行
post(() -> {
// 初始化圓環 Path
initCirclePath();
// 初始化線性漸變
// 由於要使用 mRealSize ,放 post 內
initShader();
});
}
/**
* 開啓動畫
*/
public void startAnimator() {
setVisibility(VISIBLE);
hootPaint.setStrokeWidth(mStrokeWidth);
hootPaint.setColor(mStartColor);
mCircleValueAnimator.start();
}
/**
* 設置顏色
*/
public void setColor(@ColorInt int startColor, @ColorInt int endColor) {
this.mStartColor = startColor;
this.mEndColor = endColor;
}
/**
* 設置畫筆粗細
*/
public void setStrokeWidth(float strokeWidth) {
this.mStrokeWidth = strokeWidth;
}
/**
* 測量,強制將 View 設置爲正方形
* 當寬和高有一個爲 wrap_content 時,就將寬高都定爲 150 px
* 當寬或者高有一個小於 150 px 時,都設置爲 150 px
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
// 取寬高中小的,強制設置成爲正方形
int realSize = Math.min(wSpecSize, hSpecSize);
// 寬高模式是否有一個爲 AT_MOST
boolean isAnyOneAtMost = (wSpecMode == MeasureSpec.AT_MOST || hSpecMode == MeasureSpec.AT_MOST);
if (!isAnyOneAtMost) {
// 將寬高中小的值 realSize 與 150px 比較,取大的值
realSize = Math.max(realSize, DEFAULT_SIZE);
setMeasuredDimension(realSize, realSize);
} else {
setMeasuredDimension(DEFAULT_SIZE, DEFAULT_SIZE);
}
}
/**
* 繪製
* @param canvas 畫布
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDstPath == null) {
return;
}
// 繪製已經記錄過的圓圈 Path
if (mIsHasCircle) {
canvas.drawPath(mCirclePath, mPaint);
}
// 刷新當前截取 Path
mDstPath.reset();
// 避免硬件加速的Bug
mDstPath.lineTo(0, 0);
// 截取片段
float stop = mPathLength * mAnimatorValue;
mPathMeasure.getSegment(0, stop, mDstPath, true);
// 繪製截取的片段
canvas.drawPath(mDstPath, hootPaint);
}
/**
* 當View從屏幕消失時,關閉可能在執行的動畫,以免可能出現內存泄漏
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
// 取消圓形動畫
boolean isCircleNeedCancel = (mCircleValueAnimator != null && mCircleValueAnimator.isRunning());
if (isCircleNeedCancel) {
mCircleValueAnimator.cancel();
}
// 取消對號動畫
boolean isRightMarkNeedCancel = (mRightMarkValueAnimator != null && mRightMarkValueAnimator.isRunning());
if (isRightMarkNeedCancel) {
mRightMarkValueAnimator.cancel();
}
}
/**
* 線性漸變
*/
private void initShader() {
// 使用線性漸變
LinearGradient shader = new LinearGradient(0, 0, mRealSize, mRealSize, mStartColor, mEndColor, Shader.TileMode.REPEAT);
hootPaint.setShader(shader);
}
/**
* 畫筆
*/
private void initPaint() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#b3000000"));
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeJoin(Paint.Join.ROUND);
hootPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
hootPaint.setStyle(Paint.Style.STROKE);
hootPaint.setStrokeJoin(Paint.Join.ROUND);
}
/**
* 繪製路徑
*/
private void initCirclePath() {
// 獲取View 的寬
mRealSize = getWidth();
// 添加圓環路徑
mCirclePath = new Path();
float x = mRealSize / 2f;
float y = mRealSize / 2f;
float radius = x / 3 * 2;
mCirclePath.addCircle(x, y, radius, Path.Direction.CW);
// PathMeasure
mPathMeasure = new PathMeasure();
mPathMeasure.setPath(mCirclePath, false);
// 此時爲圓的周長
mPathLength = mPathMeasure.getLength();
// Path dst 用來存儲截取的Path片段
mDstPath = new Path();
}
/**
* 初始化圓形動畫
*/
private void initCircleAnimator() {
// 圓環動畫
mCircleValueAnimator = ValueAnimator.ofFloat(0, 1);
// 動畫過程
mCircleValueAnimator.addUpdateListener(animation -> {
mAnimatorValue = (float) animation.getAnimatedValue();
invalidate();
});
// 動畫時間
mCircleValueAnimator.setDuration(0);
// 插值器
mCircleValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
// 圓環結束後,開啓對號的動畫
mCircleValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mIsHasCircle = true;
initMarkAnimator();
initRightMarkPath();
mRightMarkValueAnimator.start();
}
});
}
/**
* 初始化對號動畫
*/
private void initMarkAnimator() {
mRightMarkValueAnimator = ValueAnimator.ofFloat(0, 1);
// 動畫過程
mRightMarkValueAnimator.addUpdateListener(animation -> {
mAnimatorValue = (float) animation.getAnimatedValue();
invalidate();
});
// 動畫時間
mRightMarkValueAnimator.setDuration(ANIMATOR_TIME);
// 插值器
mRightMarkValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mRightMarkValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
setVisibility(INVISIBLE);
}
});
}
/**
* 關聯對號 Path
*/
private void initRightMarkPath() {
Path path = new Path();
// 對號起點
float startX = (float) (0.3 * mRealSize);
float startY = (float) (0.5 * mRealSize);
path.moveTo(startX, startY);
// 對號拐角點
float cornerX = (float) (0.43 * mRealSize);
float cornerY = (float) (0.66 * mRealSize);
path.lineTo(cornerX, cornerY);
// 對號終點
float endX = (float) (0.75 * mRealSize);
float endY = (float) (0.4 * mRealSize);
path.lineTo(endX, endY);
// 重新關聯Path
mPathMeasure.setPath(path, false);
// 此時爲對號 Path 的長度
mPathLength = mPathMeasure.getLength();
}
}