After starting to use compose, I found out that some views that needed to be customized before, with dozens or hundreds of lines of code, need to customize layout attributes, need to inherit View, and need to use Paint. If you use Compose, you can directly use 20 lines of one method solve.
To implement a custom arc progress bar as shown in the figure below:
Implemented using Kotlin
In the early days, I wrote such a small demo
complete code: https://github.com/yaoxiawen/CircleProgressBar
custom layout properties
Define the required attributes in attrs.xml (if not created)
<!--圆环形进度条-->
<declare-styleable name="CircleProgressBar">
<attr name="ringMax" format="integer" />
<attr name="progress" format="integer" />
<attr name="startAngle" format="float" />
<attr name="endAngle" format="float" />
<attr name="reverse" format="boolean" />
<attr name="roundCap" format="boolean" />
<attr name="ringWidth" format="dimension" />
<attr name="ringColor" format="color" />
<attr name="ringBackgroundColor" format="color" />
</declare-styleable>
Custom View
Create CircleProgressBar to inherit View
class CircleProgressBar @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)
Parse attributes through AttributeSet
Parse the attribute values passed in the layout through the AttributeSet parameter in the constructor, and all attributes need to have default values
val ta = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar)
max = ta.getInt(R.styleable.CircleProgressBar_ringMax, DEFAULT_MAX)
if (max <= 0) {
max = DEFAULT_MAX
}
startAngle = ta.getFloat(R.styleable.CircleProgressBar_startAngle, DEFAULT_START_ANGLE)
endAngle = ta.getFloat(R.styleable.CircleProgressBar_endAngle, DEFAULT_END_ANGLE)
reverse = ta.getBoolean(R.styleable.CircleProgressBar_reverse, DEFAULT_REVERSE)
roundCap = ta.getBoolean(R.styleable.CircleProgressBar_roundCap, DEFAULT_ROUND_CAP)
progress = ta.getInt(R.styleable.CircleProgressBar_progress, DEFAULT_PROGRESS)
ringWidth =
ta.getDimension(R.styleable.CircleProgressBar_ringWidth, DEFAULT_RING_WIDTH)
ringColor = ta.getColor(R.styleable.CircleProgressBar_ringColor, DEFAULT_RING_COLOR)
ringBackgroungColor = ta.getColor(
R.styleable.CircleProgressBar_ringBackgroundColor,
DEFAULT_RING_BACKGROUND_COLOR
)
ta.recycle()
Initialize Paint
Custom view uses canvas to draw, naturally need to have Paint
mArcPaint = Paint()
mArcPaint.isAntiAlias = true
Draw an arc in onDraw
Use drawArc of canvas to draw an arc, and set strokeCap to Paint.Cap.ROUND in Paint to draw the effect of a round cap.
with(mArcPaint) {
color = ringBackgroungColor
style = Paint.Style.STROKE
strokeWidth = ringWidth
if (roundCap) {
strokeCap = Paint.Cap.ROUND
}
}
//绘制圆环背景
canvas.drawArc(mRectF, startAngle, endAngle - startAngle, false, mArcPaint)
with(mArcPaint) {
color = ringColor
if (roundCap) {
strokeCap = Paint.Cap.ROUND
}
}
var sweepAngle = progress.toFloat() / max * (endAngle - startAngle)
if (reverse) {
sweepAngle = -sweepAngle //逆时针滚动
}
canvas.drawArc(mRectF, startAngle, sweepAngle, false, mArcPaint)
xml layout used in
<com.example.circleprogressbar.CircleProgressBar
android:layout_width="200dp"
android:layout_height="200dp"
app:progress="30"
app:startAngle="-225"
app:endAngle="45"
app:ringWidth="10dp" />
Implemented using Compose
For some similar APIs, use drawArc of Canvas to draw arcs, and set cap = StrokeCap.Round to achieve the effect of a round cap.
@Composable
fun CircleProgress(
modifier: Modifier = Modifier,
progress: Int,
startAngle: Float,
endAngle: Float,
progressBgColor: Color,
progressColor: Color,
) {
Canvas(
modifier = modifier,
onDraw = {
drawArc(
color = progressBgColor,
startAngle = startAngle,
sweepAngle = endAngle - startAngle,
useCenter = false,
style = Stroke(size.width / 11, cap = StrokeCap.Round)
)
drawArc(
color = progressColor,
startAngle = startAngle,
sweepAngle = (endAngle - startAngle) * progress / 100,
useCenter = false,
style = Stroke(size.width / 11, cap = StrokeCap.Round)
)
}
)
}
Call the method to pass in some corresponding parameters, and the parameters can also be directly set with default values.
CircleProgress(
modifier = Modifier.padding(5.dp).size(66.dp),
startAngle = -225f,
endAngle = 45f,
progress = 30,
progressBgColor = Color.LightGray,
progressColor = Color.Blue
)