본문 바로가기
프로젝트/Roulette Wheel View

[Roulette] 룰렛을 그리고 회전시키기 (1) - 룰렛 그리기

by JhDroid 2021. 2. 19.
728x90

개요

  • 안드로이드에서 룰렛을 Custom View를 통해 직접 그리고 회전시켜보는 예제 코드입니다.
  • 코드는 코틀린으로 작성했습니다.
  • 작업된 코드는 아래 github 주소를 확인해주세요.
 

JhDroid/android-roulette-wheel-view

Android draw roulette view. Contribute to JhDroid/android-roulette-wheel-view development by creating an account on GitHub.

github.com

 

룰렛 그리기

1. 커스텀 뷰 클래스 생성

package com.jhdroid.roulette

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View

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

}

 

2. 룰렛 배경 그리기

  • 룰렛의 원을 그리기 전에 룰렛을 그리는 방법을 설명해드리겠습니다.
    • 룰렛 배경과 룰렛의 내부 판(?)은 한 번에 그릴 수 없어서 나눠서 그려야 합니다.

예시 이미지

  • 위 이미지처럼 룰렛을 그리려면 먼저 배경 원을 그리고 내부 원을 그려줘야 합니다.
  • 먼저 룰렛의 배경을 그려보겠습니다.
    • 룰렛의 배경은 바로 그리지 않고 먼저 정사각형을 만들고 그 정사각형 안에 그리는 방식으로 그려야 합니다.
    • 이 정사각형을 통해 내부 원도 그려야 합니다.
class Roulette @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val strokePaint = Paint()
    
    private val rectF = RectF()

    init {
        strokePaint.apply {
            color = Color.BLACK
            style = Paint.Style.STROKE
            strokeWidth = 15f
            isAntiAlias = true
        }
    }

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

        val rectLeft = left.toFloat() + paddingLeft
        val rectRight = right - paddingRight
        val rectTop = height / 2f - rectRight / 2f + paddingTop
        val rectBottom = height / 2f + rectRight / 2f - paddingRight

        rectF.set(rectLeft, rectTop, rectRight, rectBottom)

        canvas?.drawArc(rectF, 0f, 360f, true, strokePaint)
    }
}
  • 코드 설명
    • strokePaint
      • style : 스타일을 Paint.Style.STOKE를 설정해주지 않으면 원의 내부가 채워진 원으로 그려집니다.
      • isAntiAlias : Paint 설정 시 AntiAlias 설정해주면 부드럽게 원을 그려줍니다.
    • rectF
      • left, right, top, bottom : 정사각형의 크기를 설정합니다. 룰렛을 뷰의 중앙에 그려주기 위해 top은 뷰의 중앙에서 가로의 반만큼 위로, bottom은 중앙에서 가로의 반만큼 아래로 설정해줍니다.
        • rectRight == width
    • drawArc(RectF, 시작 각도, 회전 각도, 중앙 사용 여부, Paint())
      • 원을 그릴 떼는 시작 각도에서 회전 각도만큼 시계 방향으로 그립니다.
      • 시작 각도를 0도로 설정하면 오른쪽 중앙에서부터 시계 방향으로 원을 그립니다. 
      • 배경 원은 중앙 사용 여부가 중요하지 않습니다. (true나 false 아무거나 설정하면 됩니다.)

시계 방향으로 원을 그린다

  • 위 과정까지 완료하면 뷰 중앙에 원을 그릴 수 있습니다.
  • 안드로이드 스튜디오 최신 버전을 사용하고 있다면 스튜디오 오른쪽에서 빌드하면서 draw 되는 과정을 확인할 수 있습니다.

 

3. 룰렛 내부 그리기

  • 룰렛 내부는 룰렛의 사이즈(2~8)에 따라서 다르게 그려줘야 합니다.
  • 룰렛 내부는 룰렛 배경처럼 drawArc() 함수를 통해 룰렛 사이즈에 맞게 원을 여러 개 그려야 합니다.
class Roulette @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val rectF = RectF()
    private val strokePaint = Paint()
    private val fillPaint = Paint()

    private var rouletteSize = 2

    init {
        strokePaint.apply {
            color = Color.BLACK
            style = Paint.Style.STROKE
            strokeWidth = 15f
            isAntiAlias = true
        }
    
        fillPaint.apply {
            style = Paint.Style.FILL
            isAntiAlias = true
        }
    }

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

        val rectLeft = left.toFloat() + paddingLeft
        val rectRight = right - paddingRight
        val rectTop = height / 2f - rectRight / 2f + paddingTop
        val rectBottom = height / 2f + rectRight / 2f - paddingRight

        rectF.set(rectLeft, rectTop, rectRight, rectBottom)

        drawRoulette(canvas, rectF)
    }

    private fun drawRoulette(canvas: Canvas?, rectF: RectF) {
        canvas?.drawArc(rectF, 0f, 360f, true, strokePaint)

        if (rouletteSize in 2..8) {
            val sweepAngle = 360f / rouletteSize.toFloat()
            val colors = listOf("#fe4a49", "#2ab7ca", "#fed766", "#e6e6ea", "#f6abb6", "#005b96", "#7bc043", "#f37735")

            for (i in 0 until rouletteSize) {
                fillPaint.color = Color.parseColor(colors[i])

                val startAngle = if (i == 0) 0f else sweepAngle * i
                canvas?.drawArc(rectF, startAngle, sweepAngle, true, fillPaint)
            }
        } else throw RuntimeException("size out of roulette")
    }
}
  • 코드 설명
    • fillPaint
      • style : 스타일을 Paint.Style.FILL 설정을 해주지 않아도 되지만 명시적으로 추가했습니다.
    • rouletteSize
      • 룰렛의 크기를 결정합니다. 이 변수는 나중에 사용자에게 입력받습니다.
      • 룰렛이 다르게 그려지는 것을 확인하고 싶으면 이 변수의 값을 변경하면 됩니다.
      • 룰렛은 최소 2에서 최대 8까지의 수입니다.
    • drawRoulette(Canvas, RectF)
      • 룰렛을 그려주는 함수입니다. 기존에 배경을 그리는 코드를 이 함수로 옮겼습니다.
    • sweepAngle
      • 룰렛의 회전 각도를 담는 변수입니다.
      • 회전 각도는 룰렛 크기에 따라 달라지므로 '360 / 룰렛 사이즈'를 통해 구할 수 있습니다.
    • fillPaint.color
      • 순서대로 색상을 변경하기 위한 코드입니다.
    • startAngle
      • 시작 각도를 담는 변수입니다.
      • 시작 각도는 최초에는 0에서 시작해서 회전 각도만큼 커집니다.
        • 룰렛 사이즈가 4일 때 회전 각도는 90
          • 1 - 0 ~ 90
          • 2 - 90 ~ 180
          • 3 - 180 ~ 270
          • 4 - 270 ~ 360
        • 단 여기서 회전 각도는 90으로 변하면 안 됩니다.
    • drawArc()
      • 내부 원은 중앙 사용을 허용하고 그려줘야 합니다.

  • 이 비교 이미지만 보면 중앙 사용이 무슨 소리인지 이해가 될 거라고 생각됩니다.
  • 여기까지 완료했다면 룰렛판 그리기는 끝났습니다.

 

다음 글은 룰렛 내부에 텍스트를 표시하고 회전 애니메이션을 추가하는 방법입니다.

 

 

* 글에 틀린 부분이 있으면 댓글 부탁드립니다 :)

728x90