cesards
2/22/2015 - 2:31 AM

CircularProgressDrawable.java

/**
 * 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);
  }
}
public class CircularProgressDrawable extends Drawable
    implements Animatable {

  private static final Interpolator ANGLE_INTERPOLATOR      = new LinearInterpolator();
  private static final Interpolator SWEEP_INTERPOLATOR      = new DecelerateInterpolator();
  private static final int          ANGLE_ANIMATOR_DURATION = 2000;
  private static final int          SWEEP_ANIMATOR_DURATION = 600;
  private static final int          MIN_SWEEP_ANGLE         = 30;
  private final        RectF        fBounds                 = new RectF();

  private ObjectAnimator mObjectAnimatorSweep;
  private ObjectAnimator mObjectAnimatorAngle;
  private boolean        mModeAppearing;
  private Paint          mPaint;
  private float          mCurrentGlobalAngleOffset;
  private float          mCurrentGlobalAngle;
  private float          mCurrentSweepAngle;
  private float          mBorderWidth;
  private boolean        mRunning;

  public CircularProgressDrawable(int color, float borderWidth) {
    mBorderWidth = borderWidth;

    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(borderWidth);
    mPaint.setColor(color);

    setupAnimations();
  }

  @Override
  public void draw(Canvas canvas) {
    float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset;
    float sweepAngle = mCurrentSweepAngle;
    if (!mModeAppearing) {
      startAngle = startAngle + sweepAngle;
      sweepAngle = 360 - sweepAngle - MIN_SWEEP_ANGLE;
    } else {
      sweepAngle += MIN_SWEEP_ANGLE;
    }
    canvas.drawArc(fBounds, startAngle, sweepAngle, false, mPaint);
  }

  @Override
  public void setAlpha(int alpha) {
    mPaint.setAlpha(alpha);
  }

  @Override
  public void setColorFilter(ColorFilter cf) {
    mPaint.setColorFilter(cf);
  }

  @Override
  public int getOpacity() {
    return PixelFormat.TRANSPARENT;
  }

  private void toggleAppearingMode() {
    mModeAppearing = !mModeAppearing;
    if (mModeAppearing) {
      mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360;
    }
  }

  @Override
  protected void onBoundsChange(Rect bounds) {
    super.onBoundsChange(bounds);
    fBounds.left = bounds.left + mBorderWidth / 2f + .5f;
    fBounds.right = bounds.right - mBorderWidth / 2f - .5f;
    fBounds.top = bounds.top + mBorderWidth / 2f + .5f;
    fBounds.bottom = bounds.bottom - mBorderWidth / 2f - .5f;
  }

  //////////////////////////////////////////////////////////////////////////////
  ////////////////            Animation

  private Property<SmoothCircularIndeterminateProgressBarDrawable, Float> mAngleProperty
      = new Property<SmoothCircularIndeterminateProgressBarDrawable, Float>(Float.class, "angle") {
    @Override
    public Float get(SmoothCircularIndeterminateProgressBarDrawable object) {
      return object.getCurrentGlobalAngle();
    }

    @Override
    public void set(SmoothCircularIndeterminateProgressBarDrawable object, Float value) {
      object.setCurrentGlobalAngle(value);
    }
  };

  private Property<SmoothCircularIndeterminateProgressBarDrawable, Float> mSweepProperty
      = new Property<SmoothCircularIndeterminateProgressBarDrawable, Float>(Float.class, "arc") {
    @Override
    public Float get(SmoothCircularIndeterminateProgressBarDrawable object) {
      return object.getCurrentSweepAngle();
    }

    @Override
    public void set(SmoothCircularIndeterminateProgressBarDrawable object, Float value) {
      object.setCurrentSweepAngle(value);
    }
  };

  private void setupAnimations() {
    mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, 360f);
    mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);
    mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);
    mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);
    mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);

    mObjectAnimatorSweep = ObjectAnimator.ofFloat(this, mSweepProperty, 360f - MIN_SWEEP_ANGLE * 2);
    mObjectAnimatorSweep.setInterpolator(SWEEP_INTERPOLATOR);
    mObjectAnimatorSweep.setDuration(SWEEP_ANIMATOR_DURATION);
    mObjectAnimatorSweep.setRepeatMode(ValueAnimator.RESTART);
    mObjectAnimatorSweep.setRepeatCount(ValueAnimator.INFINITE);
    mObjectAnimatorSweep.addListener(new Animator.AnimatorListener() {
      @Override
      public void onAnimationStart(Animator animation) {

      }

      @Override
      public void onAnimationEnd(Animator animation) {

      }

      @Override
      public void onAnimationCancel(Animator animation) {

      }

      @Override
      public void onAnimationRepeat(Animator animation) {
        toggleAppearingMode();
      }
    });
  }

  @Override
  public void start() {
    if (isRunning()) {
      return;
    }
    mRunning = true;
    mObjectAnimatorAngle.start();
    mObjectAnimatorSweep.start();
    invalidateSelf();
  }

  @Override
  public void stop() {
    if (!isRunning()) {
      return;
    }
    mRunning = false;
    mObjectAnimatorAngle.cancel();
    mObjectAnimatorSweep.cancel();
    invalidateSelf();
  }

  @Override
  public boolean isRunning() {
    return mRunning;
  }

  public void setCurrentGlobalAngle(float currentGlobalAngle) {
    mCurrentGlobalAngle = currentGlobalAngle;
    invalidateSelf();
  }

  public float getCurrentGlobalAngle() {
    return mCurrentGlobalAngle;
  }

  public void setCurrentSweepAngle(float currentSweepAngle) {
    mCurrentSweepAngle = currentSweepAngle;
    invalidateSelf();
  }

  public float getCurrentSweepAngle() {
    return mCurrentSweepAngle;
  }

}