转自 :http://blog.chengyunfeng.com/?p=607
在 G+ 上看到了一个不错的圆形进度条动画。
下图是该动画效果的演示:
是通过两个分别控制圆弧起始绘制位置和圆弧臂长的动画来实现的。
代码如下,实现原理在注释中由说明:
import android.animation.Animator; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.util.Property; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; public class CircularProgressDrawable extends Drawable implements Animatable { <span style="white-space:pre"> </span>/** <span style="white-space:pre"> </span> * 绘制圆弧起始位置角度的动画,这样该圆弧是打圈转的动画 <span style="white-space:pre"> </span> */ <span style="white-space:pre"> </span>private static final Interpolator ANGLE_INTERPOLATOR = new LinearInterpolator(); <span style="white-space:pre"> </span>/** <span style="white-space:pre"> </span> * 绘制圆弧臂长的动画,该动画受 mModeAppearing 控制, 当 mModeAppearing 为 false <span style="white-space:pre"> </span> * 的时候,圆弧的起始点在增加,圆弧的终止点不变,弧长在逐渐减少; 当 mModeAppearing 为 true 的时候, <span style="white-space:pre"> </span> * 圆弧的起始点不变,圆弧的终止点变大,弧长在逐渐增加 <span style="white-space:pre"> </span> */ <span style="white-space:pre"> </span>private static final Interpolator SWEEP_INTERPOLATOR = new DecelerateInterpolator(); <span style="white-space:pre"> </span>/** <span style="white-space:pre"> </span> * 圆弧起始位置动画的间隔,也就是多少毫秒圆弧转一圈,可以把该值扩大10倍来查看动画的慢动作 <span style="white-space:pre"> </span> */ <span style="white-space:pre"> </span>private static final int ANGLE_ANIMATOR_DURATION = 20000; <span style="white-space:pre"> </span>/** <span style="white-space:pre"> </span> * 圆弧臂长的动画间隔,也就是臂长从最小到最大值的变化时间,也可以把该值扩大10倍来查看动画的慢动作 <span style="white-space:pre"> </span> */ <span style="white-space:pre"> </span>private static final int SWEEP_ANIMATOR_DURATION = 6000; <span style="white-space:pre"> </span>/** <span style="white-space:pre"> </span> * 圆弧的最下臂长是多少度 <span style="white-space:pre"> </span> */ <span style="white-space:pre"> </span>private static final int MIN_SWEEP_ANGLE = 30; <span style="white-space:pre"> </span>private final RectF fBounds = new RectF(); <span style="white-space:pre"> </span>/** <span style="white-space:pre"> </span> * 起始位置的动画对象 <span style="white-space:pre"> </span> */ <span style="white-space:pre"> </span>private ObjectAnimator mObjectAnimatorSweep; <span style="white-space:pre"> </span>/** <span style="white-space:pre"> </span> * 臂长的动画对象 <span style="white-space:pre"> </span> */ <span style="white-space:pre"> </span>private ObjectAnimator mObjectAnimatorAngle; <span style="white-space:pre"> </span>/** <span style="white-space:pre"> </span> * 控制臂长是逐渐增加还是逐渐减少 <span style="white-space:pre"> </span> */ <span style="white-space:pre"> </span>private boolean mModeAppearing; <span style="white-space:pre"> </span>private Paint mPaint; <span style="white-space:pre"> </span>/** <span style="white-space:pre"> </span> * 每次臂长增加 、减少 转换的时候, 圆弧起始位置的偏移量会增加 2 倍的最小臂长 <span style="white-space:pre"> </span> */ <span style="white-space:pre"> </span>private float mCurrentGlobalAngleOffset; <span style="white-space:pre"> </span>private float mCurrentGlobalAngle; <span style="white-space:pre"> </span>private float mCurrentSweepAngle; <span style="white-space:pre"> </span>private float mBorderWidth; <span style="white-space:pre"> </span>private boolean mRunning; <span style="white-space:pre"> </span>public CircularProgressDrawable(int color, float borderWidth) { <span style="white-space:pre"> </span>mBorderWidth = borderWidth; <span style="white-space:pre"> </span>mPaint = new Paint(); <span style="white-space:pre"> </span>mPaint.setAntiAlias(true); <span style="white-space:pre"> </span>mPaint.setStyle(Paint.Style.STROKE); <span style="white-space:pre"> </span>mPaint.setStrokeWidth(borderWidth); <span style="white-space:pre"> </span>mPaint.setColor(color); <span style="white-space:pre"> </span>setupAnimations(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public void draw(Canvas canvas) { <span style="white-space:pre"> </span>float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset; <span style="white-space:pre"> </span>float sweepAngle = mCurrentSweepAngle; <span style="white-space:pre"> </span>if (mModeAppearing) { <span style="white-space:pre"> </span>sweepAngle += MIN_SWEEP_ANGLE; <span style="white-space:pre"> </span>} else { <span style="white-space:pre"> </span>startAngle = startAngle + sweepAngle; <span style="white-space:pre"> </span>sweepAngle = 360 - sweepAngle - MIN_SWEEP_ANGLE; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>canvas.drawArc(fBounds, startAngle, sweepAngle, false, mPaint); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public void setAlpha(int alpha) { <span style="white-space:pre"> </span>mPaint.setAlpha(alpha); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public void setColorFilter(ColorFilter cf) { <span style="white-space:pre"> </span>mPaint.setColorFilter(cf); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public int getOpacity() { <span style="white-space:pre"> </span>return PixelFormat.TRANSPARENT; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>private void toggleAppearingMode() { <span style="white-space:pre"> </span>mModeAppearing = !mModeAppearing; <span style="white-space:pre"> </span>if (mModeAppearing) { <span style="white-space:pre"> </span>mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>protected void onBoundsChange(Rect bounds) { <span style="white-space:pre"> </span>super.onBoundsChange(bounds); <span style="white-space:pre"> </span>fBounds.left = bounds.left + mBorderWidth / 2f + .5f; <span style="white-space:pre"> </span>fBounds.right = bounds.right - mBorderWidth / 2f - .5f; <span style="white-space:pre"> </span>fBounds.top = bounds.top + mBorderWidth / 2f + .5f; <span style="white-space:pre"> </span>fBounds.bottom = bounds.bottom - mBorderWidth / 2f - .5f; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>// //////////////////////////////////////////////////////////////////////////// <span style="white-space:pre"> </span>// ////////////// Animation <span style="white-space:pre"> </span>private Property<CircularProgressDrawable, Float> mAngleProperty = new Property<CircularProgressDrawable, Float>( <span style="white-space:pre"> </span>Float.class, "angle") { <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public Float get(CircularProgressDrawable object) { <span style="white-space:pre"> </span>return object.getCurrentGlobalAngle(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public void set(CircularProgressDrawable object, Float value) { <span style="white-space:pre"> </span>object.setCurrentGlobalAngle(value); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>}; <span style="white-space:pre"> </span>private Property<CircularProgressDrawable, Float> mSweepProperty = new Property<CircularProgressDrawable, Float>( <span style="white-space:pre"> </span>Float.class, "arc") { <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public Float get(CircularProgressDrawable object) { <span style="white-space:pre"> </span>return object.getCurrentSweepAngle(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public void set(CircularProgressDrawable object, Float value) { <span style="white-space:pre"> </span>object.setCurrentSweepAngle(value); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>}; <span style="white-space:pre"> </span>private void setupAnimations() { <span style="white-space:pre"> </span>mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, <span style="white-space:pre"> </span>360f); <span style="white-space:pre"> </span>mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR); <span style="white-space:pre"> </span>mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION); <span style="white-space:pre"> </span>mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART); <span style="white-space:pre"> </span>mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE); <span style="white-space:pre"> </span>mObjectAnimatorSweep = ObjectAnimator.ofFloat(this, mSweepProperty, <span style="white-space:pre"> </span>360f - MIN_SWEEP_ANGLE * 2); <span style="white-space:pre"> </span>mObjectAnimatorSweep.setInterpolator(SWEEP_INTERPOLATOR); <span style="white-space:pre"> </span>mObjectAnimatorSweep.setDuration(SWEEP_ANIMATOR_DURATION); <span style="white-space:pre"> </span>mObjectAnimatorSweep.setRepeatMode(ValueAnimator.RESTART); <span style="white-space:pre"> </span>mObjectAnimatorSweep.setRepeatCount(ValueAnimator.INFINITE); <span style="white-space:pre"> </span>mObjectAnimatorSweep.addListener(new Animator.AnimatorListener() { <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public void onAnimationStart(Animator animation) { <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public void onAnimationEnd(Animator animation) { <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public void onAnimationCancel(Animator animation) { <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public void onAnimationRepeat(Animator animation) { <span style="white-space:pre"> </span>toggleAppearingMode(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>}); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public void start() { <span style="white-space:pre"> </span>if (isRunning()) { <span style="white-space:pre"> </span>return; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>mRunning = true; <span style="white-space:pre"> </span>// 为了方便测试,可以注释掉下面两个动画中的一个,来 <span style="white-space:pre"> </span>// 分别查看每个独立的动画是如何运动的 <span style="white-space:pre"> </span>mObjectAnimatorAngle.start(); <span style="white-space:pre"> </span>mObjectAnimatorSweep.start(); <span style="white-space:pre"> </span>invalidateSelf(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public void stop() { <span style="white-space:pre"> </span>if (!isRunning()) { <span style="white-space:pre"> </span>return; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>mRunning = false; <span style="white-space:pre"> </span>mObjectAnimatorAngle.cancel(); <span style="white-space:pre"> </span>mObjectAnimatorSweep.cancel(); <span style="white-space:pre"> </span>invalidateSelf(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public boolean isRunning() { <span style="white-space:pre"> </span>return mRunning; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>public void setCurrentGlobalAngle(float currentGlobalAngle) { <span style="white-space:pre"> </span>mCurrentGlobalAngle = currentGlobalAngle; <span style="white-space:pre"> </span>invalidateSelf(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>public float getCurrentGlobalAngle() { <span style="white-space:pre"> </span>return mCurrentGlobalAngle; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>public void setCurrentSweepAngle(float currentSweepAngle) { <span style="white-space:pre"> </span>mCurrentSweepAngle = currentSweepAngle; <span style="white-space:pre"> </span>invalidateSelf(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>public float getCurrentSweepAngle() { <span style="white-space:pre"> </span>return mCurrentSweepAngle; <span style="white-space:pre"> </span>} }
import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; /** * Simplest custom view possible, using CircularProgressDrawable */ public class CustomView extends View { private CircularProgressDrawable mDrawable; public CustomView(Context context) { this(context, null); } public CustomView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mDrawable = new CircularProgressDrawable(Color.RED, 10); mDrawable.setCallback(this); } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (visibility == VISIBLE) { mDrawable.start(); } else { mDrawable.stop(); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mDrawable.setBounds(0, 0, w, h); } @Override public void draw(Canvas canvas) { super.draw(canvas); mDrawable.draw(canvas); } @Override protected boolean verifyDrawable(Drawable who) { return who == mDrawable || super.verifyDrawable(who); } }