[안드로이드] 커스텀 원형 그래프 만들기, 애니메이션까지

박휘버그·2024년 5월 5일
0

Android

목록 보기
4/4
post-thumbnail

서론

어느날 이런 디자이너에게 이런 요청이 왔다..


원그래프 돌아가는 애니메이션 + 숫자 증가 애니메이션
가능 한 지 ..?

이미 있는 라이브러리를 쓰면 편하지만 디자인을 원하는대로 커스텀하기 힘들어서 직접 만드는 방법을 선택했다 (생각보다 얼마 안 걸림)

해야할 일

CircleGraphView.kt 생성

package com.example.sync_front

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator
import androidx.core.content.res.ResourcesCompat

class CircleGraphView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        strokeWidth = 110f // 원의 선 두께
        textSize = 56f // 텍스트 크기 설정
    }

    private var sweepAngles = FloatArray(4) // 각 섹션의 각도 저장 배열

    init {
        // 각 섹션에 애니메이션 추가
        for (i in 0..3) {
            animateSection(i, 0f, 0f) // 초기화할 때 모든 섹션을 0%로 설정
        }
    }

    fun animateSection(section: Int, fromProgress: Float, toProgress: Float) {
        if (section in 0..3) {
            val animator = ValueAnimator.ofFloat(fromProgress, toProgress)
            animator.duration = 1000 // 애니메이션 지속 시간을 1초로 설정
            animator.interpolator = LinearInterpolator() // 애니메이션 간에 부드럽게 연결
            animator.addUpdateListener { animation ->
                sweepAngles[section] = 360 * (animation.animatedValue as Float / 100)
                invalidate() // 뷰를 다시 그리도록 요청
            }
            animator.start()
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val radius = (Math.min(width, height) / 2).toFloat()
        val rect = RectF(
            width / 2 - radius + paint.strokeWidth / 2, height / 2 - radius + paint.strokeWidth / 2,
            width / 2 + radius - paint.strokeWidth / 2, height / 2 + radius - paint.strokeWidth / 2
        )

        var startAngle = -90f
        val sectionColors =
            arrayOf(R.color.biscay_50, R.color.biscay_30, R.color.biscay_10, R.color.biscay_5)

        val textColors =
            arrayOf(R.color.white, R.color.biscay_70, R.color.biscay_50, R.color.biscay_30)

        for (i in 0 until 4) {
            paint.color = context.getColor(sectionColors[i])
            canvas.drawArc(rect, startAngle, sweepAngles[i], false, paint)

            val percentage = (sweepAngles[i] / 360 * 100).toInt()
            if (percentage > 0) { // 0%가 아닐 경우에만 텍스트를 그립니다.
                val sectionCenterAngle = startAngle + sweepAngles[i] / 2
                val sectionCenterX = (width / 2 + (radius - paint.strokeWidth / 2) * Math.cos(
                    Math.toRadians(sectionCenterAngle.toDouble())
                )).toFloat()
                val sectionCenterY = (height / 2 + (radius - paint.strokeWidth / 2) * Math.sin(
                    Math.toRadians(sectionCenterAngle.toDouble())
                )).toFloat()

                val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
                    color = context.getColor(textColors[i])
                    textAlign = Paint.Align.CENTER
                    textSize = 40f
                    typeface = ResourcesCompat.getFont(context, R.font.spoqahansansneo_bold)
                }
                val text = "${percentage}%"
                canvas.drawText(
                    text,
                    sectionCenterX,
                    sectionCenterY - (textPaint.descent() + textPaint.ascent()) / 2,
                    textPaint
                )
            }

            startAngle += sweepAngles[i]
        }
    }


}

전체적인 코드는 이렇다

클래스 정의 및 초기화

  • CircleGraphView 클래스는 View 클래스를 상속받아 정의됨
  • 이 클래스는 커스텀 뷰를 만들 때 기본적으로 필요한 Context, AttributeSet, 그리고 스타일 속성을 생성자 매개변수로 받음

Paint 객체 설정

  • Paint 객체는 그래픽을 그리기 위해 사용됨.
  • ANTI_ALIAS_FLAG는 그림을 부드럽게 처리함.
  • Paint.Style.STROKE 설정은 선으로만 그림을 그리는 것을 의미하고, strokeWidth는 선의 두께를 설정함.

애니메이션 초기화

init 블록에서는 4개의 섹션(0~3) 각각에 대해 animateSection 함수를 호출하여 초기에 모든 섹션의 각도를 0%로 설정함

  • 이건 우리 어플에 쓰이는 섹션이 최대 4개라서 이렇게 설정한건데 필요에 따라 바뀔 수 있을 거 같다.

animateSection 함수

  • 각 섹션에 대한 애니메이션을 설정
  • ValueAnimator를 사용하여 시작 각도(fromProgress)에서 끝 각도(toProgress)까지 부드럽게 변경
  • 애니메이션 각 업데이트마다 invalidate() 메소드를 호출하여 뷰를 다시 그리도록 요청

onDraw 함수

  • 실제로 원형 그래프를 그리는 로직을 포함
  • RectF는 그래프를 그릴 원의 경계를 정의
  • 각 섹션마다 설정된 색상(sectionColors)으로 그래프의 각 섹션을 그림
  • 각 섹션의 각도(sweepAngles)에 따라 해당 섹션을 그림
  • 각 섹션의 중앙에서 텍스트를 그리기 위해, 섹션의 각도와 중앙 좌표를 계산하여 퍼센테이지 값을 표시
  • 텍스트를 그릴 때 사용된 Paint 객체(textPaint)는 텍스트의 크기, 색상, 정렬, 그리고 폰트 스타일을 설정
val sectionColors =
            arrayOf(R.color.biscay_50, R.color.biscay_30, R.color.biscay_10, R.color.biscay_5)

        val textColors =
            arrayOf(R.color.white, R.color.biscay_70, R.color.biscay_50, R.color.biscay_30)

여기에는 디자이너가 준 컬러를 넣으면 된다.

XML에 CircleGraphView 추가

<com.example.sync_front.CircleGraphView
    android:id="@+id/circle"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<com.example.앱이름.CircleGraphView>
를 원형 그래프 넣고 싶은 부분에 추가한다.

Activity에 코드 작성


package com.example.sync_front.sync

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.sync_front.CircleGraphView
import com.example.sync_front.databinding.ActivitySyncBinding

class SyncActivity : AppCompatActivity() {
    private lateinit var binding: ActivitySyncBinding
    private lateinit var circleGraphView: CircleGraphView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivitySyncBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setupCirCleGraphView()
    }

    private fun setupCirCleGraphView() {
        circleGraphView = binding.circle
        circleGraphView.animateSection(0, 0f, 25f) // 첫 번째 섹션을 0%에서 25%로 애니메이션 적용
        circleGraphView.animateSection(1, 0f, 25f) // 두 번째 섹션을 0%에서 50%로 애니메이션 적용
        circleGraphView.animateSection(2, 0f, 25f) // 세 번째 섹션을 0%에서 75%로 애니메이션 적용
        circleGraphView.animateSection(3, 0f, 25f) // 네 번째 섹션을 0%에서 100%로 애니메이션 적용
    }
}

일단 임시로 하드코딩 했는데 .. 파라미터 함수를 만들어주면 더 깔끔한 코드가 될 거 같다
저렇게 하면!!

완성물

이제 디자이너가 준 대로 원형 그래프에 애니메이션까지 넣을 수 있게 되었다! 야호 ~~

profile
I'm coding bakhwee bug🪳

0개의 댓글