這是一個很簡單的動畫效果,使用屬性動畫便可實現,但願對讀者學習動畫能達到拋磚引玉的效果java
如上是咱們須要作的一個Loading動畫。Loading效果是很常見的一種動畫,最簡單的實現讓設計畫個動態圖便可,或者畫個靜態圖而後使用幀動畫也能夠實現。可是今天咱們用純代碼實現,不用任何圖片資源。
canvas
繪製兩個不一樣半徑的弧形
首先初始化外圓和內園的Recf();性能優化
private RectF mOuterCircleRectF = new RectF();
private RectF mInnerCircleRectF = new RectF();
複製代碼
而後在onDraw方法繪製圓弧:架構
//獲取View的中心
float centerX = getWidth() / 2;
float centerY = getHeight() / 2;
if (lineWidth > centerX) {
throw new IllegalArgumentException("lineWidth值太大了");
}
//外圓半徑,由於咱們的弧形是有寬度的,因此計算半徑的時候應該把這部分減去,否則會有切割的效果
float outR = centerX - lineWidth;
//小圓半徑
float inR = outR * 0.6f - lineWidth;
//設置弧形的距離上下左右的距離,也就是包圍園的矩形。
mOuterCircleRectF.set(centerX - outR, centerY - outR, centerX + outR, centerY + outR);
mInnerCircleRectF.set(centerX - inR, centerY - inR, centerX + inR, centerY + inR);
//繪製外圓
canvas.drawArc(mOuterCircleRectF, mRotateAngle % 360, OUTER_CIRCLE_ANGLE, false, mStrokePaint);
//繪製內圓
canvas.drawArc(mInnerCircleRectF, 270 - mRotateAngle % 360, INTER_CIRCLE_ANGLE, false, mStrokePaint);
複製代碼
代碼很簡單,就像註釋同樣:ide
繪製圓的過程應該放在onDraw方法中,這樣咱們能夠不斷的重繪,也能夠獲取view的真實的寬高性能
固然,咱們還需設置一個畫筆來畫咱們的圓學習
mStrokePaint = new Paint();
mStrokePaint.setStyle(Paint.Style.STROKE);
mStrokePaint.setStrokeWidth(lineWidth);
mStrokePaint.setColor(color);
mStrokePaint.setAntiAlias(true);
mStrokePaint.setStrokeCap(Paint.Cap.ROUND);
mStrokePaint.setStrokeJoin(Paint.Join.ROUND);
複製代碼
圓弧畫好了,而後利用屬性動畫便可實現動畫效果。這裏採用的是ValueAnimator,值屬性動畫,咱們能夠設置一個值範圍,而後讓他在這個範圍內變化。優化
mFloatValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
mFloatValueAnimator.setRepeatCount(Animation.INFINITE);
mFloatValueAnimator.setDuration(ANIMATION_DURATION);
mFloatValueAnimator.setStartDelay(ANIMATION_START_DELAY);
mFloatValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
複製代碼
這個設置很簡單,設置值得範圍,這是無線循環,設置動畫執行的時間,這隻動畫循環時延遲的時間,設置插值器。動畫
讓弧形動起來的原理,就是監聽值屬性動畫的值變化,而後在這個變化的過程當中不斷的改變弧形的角度,而後讓它重繪便可。ui
咱們讓咱們的loadview實現ValueAnimator.AnimatorUpdateListener接口,而後在onAnimationUpdate監聽動畫的變化。咱們初始化值屬性動畫的時候設置了值得範圍爲float型,因此這裏能夠獲取這個變化的值。而後利用這個值能夠改變繪製圓的角度大小,再調用重繪方法,便可實現:
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRotateAngle = 360 * (float)animation.getAnimatedValue();
invalidate();
}
複製代碼
整個思路大體就是這樣。完整代碼以下:
public class LoadingView extends View implements Animatable, ValueAnimator.AnimatorUpdateListener {
private static final long ANIMATION_START_DELAY = 200;
private static final long ANIMATION_DURATION = 1000;
private static final int OUTER_CIRCLE_ANGLE = 270;
private static final int INTER_CIRCLE_ANGLE = 90;
private ValueAnimator mFloatValueAnimator;
private Paint mStrokePaint;
private RectF mOuterCircleRectF;
private RectF mInnerCircleRectF;
private float mRotateAngle;
public LoadingView (Context context) {
this(context, null);
}
public LoadingView (Context context, @Nullable AttributeSet attrs) {
this(context, attrs, -1);
}
public LoadingView (Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, -1);
}
public LoadingView (Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView(context, attrs);
}
float lineWidth;
private void initView(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomLoadingView);
lineWidth = typedArray.getFloat(R.styleable.MyCustomLoadingView_lineWidth, 10.0f);
int color = typedArray.getColor(R.styleable.MyCustomLoadingView_viewColor, context.getColor(R.color.colorAccent));
typedArray.recycle();
initAnimators();
mOuterCircleRectF = new RectF();
mInnerCircleRectF = new RectF();
//初始化畫筆
initPaint(lineWidth, color);
//旋轉角度
mRotateAngle = 0;
}
private void initAnimators() {
mFloatValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
mFloatValueAnimator.setRepeatCount(Animation.INFINITE);
mFloatValueAnimator.setDuration(ANIMATION_DURATION);
mFloatValueAnimator.setStartDelay(ANIMATION_START_DELAY);
mFloatValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
}
/** * 初始化畫筆 */
private void initPaint(float lineWidth, int color) {
mStrokePaint = new Paint();
mStrokePaint.setStyle(Paint.Style.STROKE);
mStrokePaint.setStrokeWidth(lineWidth);
mStrokePaint.setColor(color);
mStrokePaint.setAntiAlias(true);
mStrokePaint.setStrokeCap(Paint.Cap.ROUND);
mStrokePaint.setStrokeJoin(Paint.Join.ROUND);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float centerX = getWidth() / 2;
float centerY = getHeight() / 2;
//最大尺寸
if (lineWidth > centerX) {
throw new IllegalArgumentException("lineWidth值太大了");
}
float outR = centerX - lineWidth;
//小圓尺寸
float inR = outR * 0.6f;
mOuterCircleRectF.set(centerX - outR, centerY - outR, centerX + outR, centerY + outR);
mInnerCircleRectF.set(centerX - inR, centerY - inR, centerX + inR, centerY + inR);
//先保存畫板的狀態
canvas.save();
//外圓
canvas.drawArc(mOuterCircleRectF, mRotateAngle % 360, OUTER_CIRCLE_ANGLE, false, mStrokePaint);
//內圓
canvas.drawArc(mInnerCircleRectF, 270 - mRotateAngle % 360, INTER_CIRCLE_ANGLE, false, mStrokePaint);
//恢復畫板的狀態
canvas.restore();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
startLoading();
}
public void startLoading() {
start();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopLoading();
}
public void stopLoading() {
stop();
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRotateAngle = 360 * (float)animation.getAnimatedValue();
invalidate();
}
protected void computeUpdateValue(float animatedValue) {
mRotateAngle = (int) (360 * animatedValue);
}
@Override
public void start() {
if (mFloatValueAnimator.isStarted()) {
return;
}
mFloatValueAnimator.addUpdateListener(this);
mFloatValueAnimator.setRepeatCount(Animation.INFINITE);
mFloatValueAnimator.setDuration(ANIMATION_DURATION);
mFloatValueAnimator.start();
}
@Override
public void stop() {
mFloatValueAnimator.removeAllUpdateListeners();
mFloatValueAnimator.removeAllListeners();
mFloatValueAnimator.setRepeatCount(0);
mFloatValueAnimator.setDuration(0);
mFloatValueAnimator.end();
}
@Override
public boolean isRunning() {
return mFloatValueAnimator.isRunning();
}
}
複製代碼
attr文件代碼以下:
<declare-styleable name="LoadingView">
<attr name="lineWidth" format="float" />
<attr name="viewColor" format="color" />
</declare-styleable>複製代碼
若是喜歡個人文章,想與一羣資深開發者一塊兒交流學習的話,歡迎加入個人合做羣Android Senior Engineer技術交流羣。有flutter—性能優化—移動架構—資深UI工程師 —NDK相關專業人員和視頻教學資料,後面也有和本篇文章想對應的視頻資料分享
羣號:925019412