Foggy day

Android(kotlin) - custom circle progressBar 본문

Android

Android(kotlin) - custom circle progressBar

jinhan38 2020. 12. 27. 02:09

 

It is the sample of custom circle progressbar that use customview and valueanimation.

If you want to use circle percent Progress, I recommend this sample.

Additional explanations have been omitted. I left a little explanation in the code. But, It is not English.

If you have any questions, please leave a comment.

 

 

 

Video preview

 

 

 

 

 

1. CircleProgress

@SuppressLint("ResourceAsColor")
class CircleProgress : View {


    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(
        context: Context?,
        rectWidth: Float,
        rectHeight: Float,
        color: Int,
        baseColor: Int
    ) : super(context) {
        this.rectWidth = rectWidth
        this.rectHeight = rectHeight
        this.color = color
        this.baseColor = baseColor
    }

    var rectWidth: Float = 0f
    var rectHeight: Float = 0f
    var startAngle = 0f
    var endAngle = 0f
    var baseStartAngle = 0f
    var baseEndAngle = 0f

    var color = 0
    var baseColor = 0
    var endPointColor = R.color.brand_blue
    var colorInnerPoint = R.color.white
    var strokeWidth = 80f
    var strokeInnerPointWidth = 40f

    var visible: Boolean = false


    init {
        //View의 배경색 지정
        setBackgroundColor(resources.getColor(R.color.white, null))
    }


    @SuppressLint("ResourceAsColor", "DrawAllocation")
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        if (!visible) return


        val marginValue = strokeWidth / 2

        //progress 배경
        //left와 top에 strokeWidth/2 값을 준 이유는  stroke의 값 안쪽으로 커지는 것이 아니라
        //left와 top을 기준으로 stroke 값 만큼 두께가 양쪽으로 넓어지기 때문에 화면에 짤려 보이기 때문입니다.
        val baseRect = RectF(
            marginValue,
            marginValue,
            rectWidth - marginValue,
            rectHeight - marginValue
        )

        val basePaint = Paint()
        basePaint.style = Paint.Style.STROKE
        basePaint.color = resources.getColor(baseColor, null)
        basePaint.strokeCap = Paint.Cap.ROUND
        basePaint.strokeWidth = strokeWidth / 2
        canvas?.drawArc(baseRect, baseStartAngle, baseEndAngle, false, basePaint)


        //progress percent
        val rect =
            RectF(marginValue, marginValue, rectWidth - marginValue, rectHeight - marginValue)

        val paint = Paint()
        paint.style = Paint.Style.STROKE
        paint.isAntiAlias = true
        paint.color = resources.getColor(color, null)
        paint.strokeCap = Paint.Cap.ROUND
        paint.strokeWidth = strokeWidth
        canvas?.drawArc(rect, startAngle, endAngle, false, paint)


        //progress의 끝부분에 점 찍기
        //큰 원
        val radius = rect.width() / 2
        val endPointPaint = Paint()
        endPointPaint.style = Paint.Style.FILL
        endPointPaint.color = resources.getColor(endPointColor, null)
        val pointBig: Point =
            calculatePointOnArc(rect.centerX(), rect.centerY(), radius, startAngle + endAngle)
        canvas?.drawCircle(
            pointBig.x.toFloat(),
            pointBig.y.toFloat(),
            strokeWidth / 2,
            endPointPaint
        )

        //작은 원
        endPointPaint.color = resources.getColor(colorInnerPoint, null)
        val pointSmall: Point =
            calculatePointOnArc(rect.centerX(), rect.centerY(), radius, startAngle + endAngle)
        canvas?.drawCircle(
            pointSmall.x.toFloat(),
            pointSmall.y.toFloat(),
            strokeInnerPointWidth / 2 - 10,
            endPointPaint
        )
    }


    /**
     * 원의 끝 지점의 좌표 구하기
     */
    private fun calculatePointOnArc(
        centerX: Float,
        centerY: Float,
        circleRadius: Float,
        endAngle: Float
    ): Point {

        val point = Point()
        val endAngleRadian: Double = endAngle * (Math.PI / 180)

        val pointX: Int = (centerX + circleRadius * cos(endAngleRadian)).roundToInt()
        val pointY: Int = (centerY + circleRadius * sin(endAngleRadian)).roundToInt()

        Log.d("TAG", "calculatePointOnArc: pointX : $pointX ")
        Log.d("TAG", "calculatePointOnArc: pointY : $pointY ")

        point.x = pointX
        point.y = pointY

        return point
    }
}

 

 

 

2. CircleProgressAnimation

class CircleProgressAnimation(
    private var circleProgress: CircleProgress,
    private val startAngle: Int,
    private val toAngle: Int,
    animDuration: Long
) : Animation() {


    init {
        duration = animDuration
        circleProgress.visible = true
    }


    //interpolatedTime은 0부터 1까지 증가하는 값
    override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
        super.applyTransformation(interpolatedTime, t)

        var angle = (startAngle.toFloat() + ((toAngle - startAngle) *interpolatedTime))

        circleProgress.endAngle = angle
        circleProgress.requestLayout()

    }
}

 

 

 

3. activity_circle_progress_anim.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".animation.progress.circle.CircleProgressAnimActivity">


    <FrameLayout
        android:id="@+id/progressWrap"
        android:layout_marginTop="100dp"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center" />


    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_marginTop="100dp"
        android:text="실행" />

</LinearLayout>

 

 

 

4. CircleProgressAnimActivity

class CircleProgressAnimActivity : AppCompatActivity() {

    lateinit var b: ActivityCircleProgressAnimBinding
    var circleWidth = 0f
    var circleHeight = 0f
    lateinit var progressWrap: FrameLayout
    lateinit var animation: Animation
    lateinit var circleProgressProgress: CircleProgress


    @SuppressLint("WrongViewCast")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        b = ActivityCircleProgressAnimBinding.inflate(layoutInflater)
        setContentView(b.root)

        b.button.setOnClickListener {
            circleProgressProgress.startAnimation(animation)
        }

    }


    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)

        progressWrap = findViewById(R.id.progressWrap)
        circleWidth = progressWrap.width.toFloat()
        circleHeight = progressWrap.height.toFloat()
        Log.d("TAG", "onCreate: 사이즈 circleWidth : $circleWidth")
        Log.d("TAG", "onCreate: 사이즈 circleHeight : $circleHeight")

        circleProgressProgress = CircleProgress(
            this,
            circleWidth,
            circleHeight,
            R.color.brand_purple,
            R.color.gray
        )

        progressWrap.addView(circleProgressProgress)

        circleProgressProgress.startAngle = 120f
        circleProgressProgress.endAngle = 250f
        circleProgressProgress.baseStartAngle = 120f
        circleProgressProgress.baseEndAngle = 300f
        circleProgressProgress.color = R.color.brand_pink
        circleProgressProgress.baseColor = R.color.colorGray500
        circleProgressProgress.endPointColor = R.color.brand_green
        circleProgressProgress.postInvalidate()

        animation = CircleProgressAnimation(
            circleProgressProgress,
            0,
            250,
            1000
        )

    }



}