canvas는 onDraw 함수에서 사용할 수 있습니다. 흰 도화지에서 그림을 그려넣는 것입니다.
간단하게 drawRect 메서드를 사용해서 canvas 동작에 대한 이해를 도우려고 합니다.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val paint = Paint()
val rectF = RectF(10.px, 10.px, 20.px, 20.px)
canvas.drawRect(rectF, paint)
// canvas.drawRect(10f, 10f, 20f, 20f, paint) 동일한 코드
// RectF 는 좌표를 나타내는 객체이므로, 독립적으로 객체를 만들어서 사용하는 편
}
<com.android.test_motivoo.pie_chart.custom_view.CustomView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
이렇게하고 실행해보면, 하얀 화면만 나옵니다. 그 이유는 하얀 사각형을 보여주니 당연하게 하얀 화면밖에 안보이는 이유입니다. 사각형의 검은색 배경을 넣어보도록 하겠습니다. 코드는 다음과 같습니다.
val paint = Paint().apply {
color = ContextCompat.getColor(context, R.color.black)
}
실제로 두 가지를 테스트해보겠습니다.
검은색 (커스텀 뷰)
회색 (기본 뷰인 View)
커스텀 뷰 옆에 View를 넣어보았습니다. 둘의 크기가 동일한 것을 알 수 있습니다. 둘의 크기는 동일하게 10dp 입니다. 커스텀 뷰가 그려지는 방식은 좌표를 사용하는 데, 다음과 같은 사각형을 그리는 것을 알 수 있습니다.
💙 Paint() 객체와 RectF() 객체는 최대한 onDraw() 에서 호출을 지양해야 합니다.
그 이유는 onDraw는 그림이 다시 그려지는 구간에서 계속해서 호출되기 때문에, 쓸데 없는 객체를 생성하여 메모리 누수가 발생할 수 있습니다. 다음과 같이 사용해주면 좋을 것 같네요.
private var paint:Paint = Paint()
private var rectF: RectF = RectF(10f, 10f, 20f, 20f)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawRect(rectF, paint)
}
이제는 원형 프로그래스 바를 그릴 차례입니다. drawArc() 메서드를 사용하면 만들 수 있습니다.
canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint)
private var paint:Paint = Paint()
private var rectF: RectF = RectF(10f, 10f, 110f, 110f)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawArc(rectF,-90f,360f,false,paint)
}
커스텀으로 만들어야 하는 호는 다음과 같이 180도의 반원을 가지며, 밑에 부분이 30도 만큼 증가되어있는 호와 같은 모양을 만들어야 한다.
이 부분은 간단하게 만들 수 있을 것 같다.
startAngle = 150f / sweepAngle = 240f 의 값을 설정한다면 간단하게 만들어질 것 같다.
canvas.drawArc(rectF, 150f, 240f, false, paint)
그런데 내가 생각하는 것은 테두리만 그려지고 내부는 색상이 채워지지 않는 것을 생각했는데, 좀 다르다. 이것은 paint의 설정 중 style 을 STORKE로 주어야 한다.
init {
paint.apply {
style = Paint.Style.STROKE
strokeWidth = 1.px
}
}
기본적인 틀을 제작하고 디자인에서 요구하는 요구사항에 맞게 디자인해보려고 한다.
해당 원형 프로그래스 바의 요구사항은 다음과 같다.
우선 뷰의 크기를 360 pixel 로 설정하였다. 이는 전개 1: 커스텀 뷰/onMeasure로 뷰의 크기(너비와 높이) 설정하기 의 과정을 보면 이해할 수 있다.
그 다음 원의 지름이 280 pixel 로 맞추고 원이 360x360pixel 뷰의 가운데에 오도록 맞추었다. 여기서 우선 테두리 넓이는 1 pixel 진행했다.
init {
paint.apply {
style = Paint.Style.STROKE
strokeWidth = 1.px
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setBackgroundColor(ContextCompat.getColor(context, R.color.red))
if (measureMode(widthMeasureSpec) && measureMode(heightMeasureSpec)) {
setMeasuredDimension(
MeasureSpec.getMode(widthMeasureSpec),
MeasureSpec.getMode(heightMeasureSpec)
)
} else {
setMeasuredDimension(360.px.toInt(), 360.px.toInt())
}
}
private fun measureMode(measureSpec: Int): Boolean =
MeasureSpec.getMode(measureSpec) == MeasureSpec.EXACTLY
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawArc(40.px,40.px,320.px,320.px,150F,240f,false,paint)
}
<com.android.test_motivoo.pie_chart.custom_view.CustomView
android:id="@+id/custom_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
이제 테두리 넓이의 요구사항에 맞게 제작해보겠습니다. 테두리가 어떻게 그려지는 지 보여주기 위해서 따로 그림으로 나타내어 보았습니다.
만약에, 테두리가 80 pixel이라고 한다고 가정하겠습니다. 그리고 호와 뷰의 크기의 거리 차이는 40 pixel 만 남은 상황이라고 생각한다면, 아래의 그림처럼 보입니다. 테두리가 80 pixel이면, 뷰의 크기를 넘어가는 것이 아닌가? 라는 의문점이 생겼지만 아래의 그림처럼 가운데를 기준으로 테두리가 반반 늘어나는 것을 볼 수 있습니다. 그러므로 사이의 크기가 40 pixel이 남았지만 테두리 넓이의 반인 80/2 = 40 pixel 로 뷰 크기를 넘지 않는 것을 알 수 있었습니다.
strokeWidth = 10.px
그래서 strokeWidth를 10 pixel로 바꾸어서 렌더링해보면, 다음과 같이 잘 나오지만 커스텀 뷰를 제작할 때, 이러한 개념을 이해하고 접근해야 원하는 뷰를 제작할 수 있을 겁니다.