I want to develop a custom component which draws part of the circle based on different values. e.g draw 1/4 cirle, 1/2 circle etc. The component needs to be animated to disp
You have to draw the circle view, and after that you should create an animation to it.
Creating the circle view:
public class Circle extends View {
private static final int START_ANGLE_POINT = 90;
private final Paint paint;
private final RectF rect;
private float angle;
public Circle(Context context, AttributeSet attrs) {
super(context, attrs);
final int strokeWidth = 40;
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(strokeWidth);
//Circle color
paint.setColor(Color.RED);
//size 200x200 example
rect = new RectF(strokeWidth, strokeWidth, 200 + strokeWidth, 200 + strokeWidth);
//Initial Angle (optional, it can be zero)
angle = 120;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawArc(rect, START_ANGLE_POINT, angle, false, paint);
}
public float getAngle() {
return angle;
}
public void setAngle(float angle) {
this.angle = angle;
}
}
Creating the animation class to set the new angle:
public class CircleAngleAnimation extends Animation {
private Circle circle;
private float oldAngle;
private float newAngle;
public CircleAngleAnimation(Circle circle, int newAngle) {
this.oldAngle = circle.getAngle();
this.newAngle = newAngle;
this.circle = circle;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation transformation) {
float angle = oldAngle + ((newAngle - oldAngle) * interpolatedTime);
circle.setAngle(angle);
circle.requestLayout();
}
}
Put circle into your layout:
<com.package.Circle
android:id="@+id/circle"
android:layout_width="300dp"
android:layout_height="300dp" />
And finally starting the animation:
Circle circle = (Circle) findViewById(R.id.circle);
CircleAngleAnimation animation = new CircleAngleAnimation(circle, 240);
animation.setDuration(1000);
circle.startAnimation(animation);
The result is:
As extra from @JohnCordeiro answer. I have added parameters from xml to reuse the circle and to fill the circle if needed.
class RecordingCircle(context: Context, attrs: AttributeSet) : View(context, attrs) {
private val paint: Paint
private val rect: RectF
private val fillPaint: Paint
private val fillRect: RectF
var angle: Float
var startAngle: Float
init {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RecordingCircle)
startAngle = typedArray.getFloat(R.styleable.RecordingCircle_startAngle, 0f)
val offsetAngle = typedArray.getFloat(R.styleable.RecordingCircle_offsetAngle, 0f)
val color = typedArray.getColor(R.styleable.RecordingCircle_color, ResourcesCompat.getColor(resources, R.color.recording, null))
val strokeWidth = typedArray.getFloat(R.styleable.RecordingCircle_strokeWidth, 20f)
val circleSize = typedArray.getDimension(R.styleable.RecordingCircle_cicleSize, 100f)
val fillColor = typedArray.getColor(R.styleable.RecordingCircle_fillColor, 0)
typedArray.recycle()
paint = Paint().apply {
setAntiAlias(true)
setStyle(Paint.Style.STROKE)
setStrokeWidth(strokeWidth)
setColor(color)
}
rect = RectF(
strokeWidth,
strokeWidth,
(circleSize - strokeWidth),
(circleSize - strokeWidth)
)
fillPaint = Paint().apply {
setAntiAlias(true)
setStyle(Paint.Style.FILL)
setColor(fillColor)
}
val offsetFill = strokeWidth
fillRect = RectF(
offsetFill,
offsetFill,
(circleSize - offsetFill),
(circleSize - offsetFill)
)
//Initial Angle (optional, it can be zero)
angle = offsetAngle
}
override protected fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (fillColor > 0) {
canvas.drawArc(rect, 0f, 360f, false, fillPaint)
}
canvas.drawArc(rect, startAngle, angle, false, paint)
}
}
And on the xml:
<com.myapp.RecordingCircle android:id="@+id/cameraRecordButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:offsetAngle="360"
app:color="@color/light_grey"
app:strokeWidth="10"
app:cicleSize="@dimen/camera_record_button"
app:fillColor="@color/recording_bg" />
<com.myapp.RecordingCircle android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:startAngle="270"
app:color="@color/recording"
app:strokeWidth="10"
app:cicleSize="@dimen/camera_record_button" />
Here the result: Note the semi-transparent fill of the button
Added code for calculating correct circle measurements
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import androidx.core.content.ContextCompat
class Circle(context: Context, attrs: AttributeSet) : View(context, attrs) {
private val paint: Paint
private val rect: RectF
var angle = 0f
companion object {
private val START_ANGLE_POINT = 270f
}
init {
val strokeWidth = resources.getDimension(R.dimen.toast_circle_stroke_width)
paint = Paint().apply {
setAntiAlias(true)
setStyle(Paint.Style.STROKE)
setStrokeWidth(strokeWidth)
setColor(Color.RED)
}
val circleSize = resources.getDimension(R.dimen.toast_circle_size)
rect = RectF(
strokeWidth,
strokeWidth,
circleSize + strokeWidth,
circleSize + strokeWidth
)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val circleSize = resources.getDimension(R.dimen.toast_circle_size).toInt()
val strokeWidth = resources.getDimension(R.dimen.toast_circle_stroke_width).toInt()
super.onMeasure(
MeasureSpec.makeMeasureSpec(circleSize + 2 * strokeWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(circleSize + 2 * strokeWidth, MeasureSpec.EXACTLY));
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawArc(rect, START_ANGLE_POINT, angle, false, paint)
}
}