전개 2: canvas 사용하기


canvas 동작방법


canvas는 onDraw 함수에서 사용할 수 있습니다. 흰 도화지에서 그림을 그려넣는 것입니다.

간단하게 drawRect 메서드를 사용해서 canvas 동작에 대한 이해를 도우려고 합니다.

drawRect()


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)
}

실제로 두 가지를 테스트해보겠습니다.

  • Dp to Px 크기 변환이 잘 이루어지는 지 테스트
  • 화면에 좌표로 사각형이 잘 그려지는 지 테스트

검은색 (커스텀 뷰)

회색 (기본 뷰인 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()


이제는 원형 프로그래스 바를 그릴 차례입니다. drawArc() 메서드를 사용하면 만들 수 있습니다.

canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint)
  • oval : RectF 객체를 가지며, 그림을 그리기 위한 4개의 좌표를 설정하여 사각형을 그린 뒤 내부에 원을 그린다.
  • startAngle : 시작 각도를 나타내며, 단위는 도(degree)이다. 3시 방향이 0도이며, 시계 방향으로 증가
  • sweepAngle : 호를 그릴 각도를 나타내며, 단위는 도(degree)이다. startAngle에서 시계 방향으로 시작하여 sweepAngle 만큼의 각도로 호를 그린다.
  • useCenter
    • true : 시작점과 끝점을 연결하는 선을 그린다.
    • false : 그리지 않는 default 값이다.
  • 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
  }
}

요구사항에 맞게 커스텀 원형 프로그래스 바 제작하기


기본적인 틀을 제작하고 디자인에서 요구하는 요구사항에 맞게 디자인해보려고 한다.

해당 원형 프로그래스 바의 요구사항은 다음과 같다.

  • 지름 : 280 pixel
  • 테두리 넓이 : 10 pixel

우선 뷰의 크기를 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로 바꾸어서 렌더링해보면, 다음과 같이 잘 나오지만 커스텀 뷰를 제작할 때, 이러한 개념을 이해하고 접근해야 원하는 뷰를 제작할 수 있을 겁니다.

profile
Allright!

0개의 댓글

Powered by GraphCDN, the GraphQL CDN