Search code examples
javaandroidprogress-barandroid-custom-view

Custom Shape for CircularProgressIndicator


I know I can have a CircularProgressIndicator to indicate some progress, but can I also give it a custom shape like this:

Expected outcome

The goal would be a semi circle with some left out elements but still would fill correctly. I think I need to write some custom view for the "100%" progress part, but how to get the custom drawable for this?


Solution

  • You can use this as a starting point for the custom view. Depending on the requirement you can modify the values to get the desired UI.

    class MyCircularProgressView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyle: Int = 0,
    ) : View(context, attrs, defStyle) {
        private var stroke = 25F
        var size: Int = 200
            set(value) {
                field = value
                stroke = value * 0.125F
                invalidate()
            }
        var backgroundStartAngle: Float = 30F
            set(value) {
                field = value
                invalidate()
            }
        var backgroundSweepAngle: Float = 300F
            set(value) {
                field = value    
                invalidate()
            }
        var progressStartAngle: Float = 150F
            set(value) {
                field = value
                invalidate()
            }
        var progressSweepAngle: Float = 180F
            set(value) {
                field = value
                invalidate()
            }
    
        private val backgroundPaint = Paint()
        private val progressPaint = Paint()
    
        init {
            context.theme.obtainStyledAttributes(attrs, R.styleable.MyCircularProgressView, 0, 0).apply {
                try {
                  // Handle attributes passed from XMl here
                } finally {
                    recycle()
                }
            }
    
            backgroundPaint.apply {
                color = 0xFFE0E0E0.toInt()
                strokeCap = Paint.Cap.ROUND
                style = Paint.Style.STROKE
                strokeWidth = stroke
            }
            progressPaint.apply {
                color = 0xFF0000EE.toInt()
                strokeCap = Paint.Cap.ROUND
                style = Paint.Style.STROKE
                strokeWidth = stroke
            }
        }
    
        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
            canvas.drawArc(
                stroke / 2F,
                stroke / 2F,
                size.toFloat(),
                size.toFloat(),
                backgroundStartAngle % 360F,
                backgroundSweepAngle % 360F,
                false,
                backgroundPaint,
            )
            canvas.drawArc(
                stroke / 2F,
                stroke / 2F,
                size.toFloat(),
                size.toFloat(),
                progressStartAngle % 360F,
                progressSweepAngle % 360F,
                false,
                progressPaint,
            )
        }
    
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            setMeasuredDimension(size + stroke.toInt(), size + stroke.toInt())
        }
    }
    

    Output UI

    Screenshot