Skip to content

Instantly share code, notes, and snippets.

@Cheesebaron
Created July 9, 2015 08:42
Show Gist options
  • Save Cheesebaron/f8f2de331a9b55c39086 to your computer and use it in GitHub Desktop.
Save Cheesebaron/f8f2de331a9b55c39086 to your computer and use it in GitHub Desktop.
CircularProgressDrawable, based on castorflex's Java implementation here: https://gist.github.com/castorflex/4e46a9dc2c3a4245a28e
[Register("dk.ostebaronen.droid.CircularProgressDrawable")]
public class CircularProgressDrawable
: Drawable
, IAnimatable
{
private static readonly IInterpolator AngleInterpolator = new LinearInterpolator();
private static readonly IInterpolator SweepInterpolator = new DecelerateInterpolator();
private static readonly RectF _bounds = new RectF();
private const int AngleAnimatorDuration = 2000;
private const int SweepAnimatorDuration = 600;
private const int MinSweepAngle = 30;
private readonly Paint _paint;
private readonly float _borderWidth;
private ObjectAnimator _objectAnimatorSweep;
private ObjectAnimator _objectAnimatorAngle;
private bool _modeAppearing;
private float _currentGlobalAngleOffset;
private float _currentGlobalAngle;
private float _currentSweepAngle;
public float CurrentGlobalAngle
{
[Export("getCurrentGlobalAngle")]
get { return _currentGlobalAngle; }
[Export("setCurrentGlobalAngle")]
set
{
_currentGlobalAngle = value;
InvalidateSelf();
}
}
public float CurrentSweepAngle
{
[Export("getCurrentSweepAngle")]
get { return _currentSweepAngle; }
[Export("setCurrentSweepAngle")]
set
{
_currentSweepAngle = value;
InvalidateSelf();
}
}
public CircularProgressDrawable(Color color, float borderWidth)
{
_borderWidth = borderWidth;
_paint = new Paint(PaintFlags.AntiAlias) {
Color = color,
StrokeWidth = borderWidth
};
_paint.SetStyle(Paint.Style.Stroke);
SetupAnimations();
}
public override void Draw(Canvas canvas)
{
var startAngle = CurrentGlobalAngle - _currentGlobalAngleOffset;
var sweepAngle = CurrentSweepAngle;
if (!_modeAppearing)
{
startAngle = startAngle + sweepAngle;
sweepAngle = 360 - sweepAngle - MinSweepAngle;
}
else
sweepAngle += MinSweepAngle;
canvas.DrawArc(_bounds, startAngle, sweepAngle, false, _paint);
}
public override void SetAlpha(int alpha) { _paint.Alpha = alpha; }
public override void SetColorFilter(ColorFilter cf) { _paint.SetColorFilter(cf); }
public override int Opacity
{
get { return (int)Format.Transparent; }
}
protected override void OnBoundsChange(Rect bounds)
{
base.OnBoundsChange(bounds);
_bounds.Left = bounds.Left + _borderWidth/2f + .5f;
_bounds.Right = bounds.Right - _borderWidth / 2f - .5f;
_bounds.Top = bounds.Top + _borderWidth / 2f + .5f;
_bounds.Bottom = bounds.Bottom - _borderWidth / 2f - .5f;
}
public void Start()
{
if (IsRunning)
return;
IsRunning = true;
_objectAnimatorAngle.Start();
_objectAnimatorSweep.Start();
InvalidateSelf();
}
public void Stop()
{
if (!IsRunning)
return;
IsRunning = false;
_objectAnimatorAngle.Cancel();
_objectAnimatorSweep.Cancel();
InvalidateSelf();
}
public bool IsRunning { get; private set; }
private void ToggleAppearingMode()
{
_modeAppearing = !_modeAppearing;
if (_modeAppearing)
_currentGlobalAngleOffset = (_currentGlobalAngleOffset + MinSweepAngle*2)%360;
}
private void SetupAnimations()
{
_objectAnimatorAngle = ObjectAnimator.OfFloat(this, "CurrentGlobalAngle", 360f);
_objectAnimatorAngle.SetInterpolator(AngleInterpolator);
_objectAnimatorAngle.SetDuration(AngleAnimatorDuration);
_objectAnimatorAngle.RepeatMode = ValueAnimatorRepeatMode.Restart;
_objectAnimatorAngle.RepeatCount = ValueAnimator.Infinite;
_objectAnimatorSweep = ObjectAnimator.OfFloat(this, "CurrentSweepAngle",
360f - MinSweepAngle*2);
_objectAnimatorSweep.SetInterpolator(SweepInterpolator);
_objectAnimatorSweep.SetDuration(SweepAnimatorDuration);
_objectAnimatorSweep.RepeatMode = ValueAnimatorRepeatMode.Restart;
_objectAnimatorSweep.RepeatCount = ValueAnimator.Infinite;
_objectAnimatorSweep.AnimationRepeat += (sender, args) => {
ToggleAppearingMode();
};
}
}
[Register("dk.ostebaronen.droid.CircularProgressView")]
public class CircularProgressView : View
{
private readonly CircularProgressDrawable _drawable;
protected CircularProgressView(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer) {}
public CircularProgressView(Context context)
: this(context, null) { }
public CircularProgressView(Context context, IAttributeSet attrs)
: this(context, attrs, 0) { }
public CircularProgressView(Context context, IAttributeSet attrs, int defStyleAttr)
: base(context, attrs, defStyleAttr)
{
_drawable = new CircularProgressDrawable(Color.Red, 10);
_drawable.SetCallback(this);
}
protected override void OnVisibilityChanged(View changedView, ViewStates visibility)
{
base.OnVisibilityChanged(changedView, visibility);
if (visibility == ViewStates.Visible)
_drawable.Start();
else
_drawable.Stop();
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
_drawable.SetBounds(0, 0, w, h);
}
public override void Draw(Canvas canvas)
{
base.Draw(canvas);
_drawable.Draw(canvas);
}
protected override bool VerifyDrawable(Drawable who)
{
return who == _drawable || base.VerifyDrawable(who);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment