캔버스 객체에 그리기

이 Codelab은 Kotlin 기반 Android 고급 교육 과정의 일부입니다. Codelab을 순서대로 진행하는 경우 학습 효과를 극대화할 수 있지만 순서를 바꿔 진행해도 괜찮습니다. 모든 과정 Codelab은 Kotlin Codelab의 고급 Android Codelab 방문 페이지에 나열되어 있습니다.

소개

Android에는 뷰에서 맞춤 2D 그래픽 및 애니메이션을 구현하는 데 사용할 수 있는 여러 기술이 있습니다.

드로어블을 사용할 뿐만 아니라 Canvas 클래스의 그리기 메서드를 사용하여 2D 그림을 만들 수 있습니다. Canvas은 그리기 메서드를 제공하는 2D 그리기 노출 영역입니다. 이는 시간이 지남에 따라 사용자에게 표시되는 변경사항으로 인해 앱을 정기적으로 다시 그려야 하는 경우 유용합니다. 이 Codelab에서는 View에 표시되는 캔버스를 만들고 그리는 방법을 알아봅니다.

캔버스에서 실행할 수 있는 작업 유형은 다음과 같습니다.

  • 캔버스 전체를 색상으로 채웁니다.
  • Paint 객체에서 정의한 대로 직사각형, 원호, 경로와 같은 도형을 그립니다. Paint 객체에는 도형 (예: 선, 직사각형, 타원형, 경로)의 그리기 또는 텍스트 글꼴 등 스타일 및 색상 정보가 포함됩니다.
  • 변환, 확장 또는 맞춤 변환과 같은 변환을 적용합니다.
  • 자르기, 즉 캔버스에 도형이나 경로를 적용하여 표시되는 부분을 정의합니다.

Android 드로잉에 관해 어떻게 생각하는지 생각해 보세요.

Android 또는 기타 최신 시스템에 그리기는 추상화 계층 및 하드웨어 최적화까지 포함된 복잡한 프로세스입니다. Android의 그리기 방식은 매력적으로 흥미로운 주제이며 이 내용은 이 Codelab의 범위를 벗어납니다.

이 Codelab과 이 앱의 컨텍스트를 전체 화면 뷰로 캔버스에 그릴 때 다음과 같이 생각할 수 있습니다.

  1. 그리고 있는 그림을 표시하려면 뷰가 필요합니다. Android 시스템에서 제공하는 뷰 중 하나일 수 있습니다. 또는 이 Codelab에서는 앱의 콘텐츠 뷰 역할을 하는 맞춤 뷰(MyCanvasView)를 만듭니다.
  2. 이 뷰는 모든 뷰처럼 자체 캔버스(canvas)와 함께 제공됩니다.
  3. 뷰의 캔버스에 그리는 가장 기본적인 방법은 onDraw() 메서드를 재정의하고 캔버스에 그리는 것입니다.
  4. 그림을 그릴 때는 이전에 그린 내용을 캐시해야 합니다. 데이터를 캐시하는 방법에는 여러 가지가 있으며, 하나는 비트맵 (extraBitmap)에 있습니다. 또 다른 방법은 그린 좌표와 안내에 따라 그린 기록을 저장하는 것입니다.
  5. 캔버스 그리기 API를 사용하여 캐싱 비트맵 (extraBitmap)에 그리려면 캐싱 비트맵을 위한 캐싱 캔버스 (extraCanvas)를 만듭니다.
  6. 그런 다음 캐싱 비트맵(extraCanvas)에 그리면 캐싱 비트맵(extraBitmap)에 그려집니다.
  7. 화면에 그려진 모든 것을 표시하려면 뷰 캔버스 (canvas)에 캐싱 비트맵을 그리도록 지시합니다 (extraBitmap).

기본 요건

  • 활동, 기본 레이아웃으로 앱을 만들고 Android 스튜디오를 사용하여 실행하는 방법
  • 이벤트 핸들러를 뷰와 연결하는 방법.
  • 맞춤 뷰를 만드는 방법

학습할 내용

  • Canvas를 만들고 사용자 터치에 응답하여 그리는 방법

실습할 내용

  • 화면을 터치하는 사용자에 대한 응답으로 화면에 선을 그리는 앱을 만듭니다.
  • 모션 이벤트를 캡처하고 이에 따라 화면의 전체 화면 맞춤 뷰에 표시되는 캔버스에 선을 그리세요.

MiniPaint 앱은 아래 스크린샷과 같이 사용자 터치에 응답하여 선을 표시하기 위해 맞춤 뷰를 사용합니다.

1단계: MiniPaint 프로젝트 만들기

  1. Empty Activity 템플릿을 사용하는 MiniPaint라는 새 Kotlin 프로젝트를 만듭니다.
  2. app/res/values/colors.xml 파일을 열고 다음 두 색상을 추가합니다.
<color name="colorBackground">#FFFF5500</color>
<color name="colorPaint">#FFFFEB3B</color>
  1. styles.xml을 엽니다.
  2. 지정된 AppTheme 스타일의 상위 요소에서 DarkActionBarNoActionBar으로 바꿉니다. 이렇게 하면 작업 표시줄이 삭제되므로 전체 화면을 그릴 수 있습니다.
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

2단계: MyCanvasView 클래스 만들기

이 단계에서는 그리기용 맞춤 뷰 MyCanvasView를 만듭니다.

  1. app/java/com.example.android.minipaint 패키지에서 MyCanvasView라는 New > Kotlin File/Class를 만듭니다.
  2. MyCanvasView 클래스가 View 클래스를 확장하고 context: Context를 전달하도록 합니다. 추천 가져오기를 수락합니다.
import android.content.Context
import android.view.View

class MyCanvasView(context: Context) : View(context) {
}

3단계: MyCanvasView를 콘텐츠 뷰로 설정

MyCanvasView에 그릴 내용을 표시하려면 MainActivity의 콘텐츠 뷰로 설정해야 합니다.

  1. strings.xml를 열고 뷰의 콘텐츠 설명에 사용할 문자열을 정의합니다.
<string name="canvasContentDescription">Mini Paint is a simple line drawing app.
   Drag your fingers to draw. Rotate the phone to clear.</string>
  1. MainActivity.kt을 엽니다.
  2. onCreate()에서 setContentView(R.layout.activity_main)를 삭제합니다.
  3. MyCanvasView 인스턴스를 생성합니다.
val myCanvasView = MyCanvasView(this)
  1. 그 아래에서 myCanvasView의 레이아웃을 위해 전체 화면을 요청합니다. myCanvasViewSYSTEM_UI_FLAG_FULLSCREEN 플래그를 설정하면 됩니다. 이렇게 하면 뷰가 화면을 완전히 채웁니다.
myCanvasView.systemUiVisibility = SYSTEM_UI_FLAG_FULLSCREEN
  1. 콘텐츠 설명을 추가합니다.
myCanvasView.contentDescription = getString(R.string.canvasContentDescription)
  1. 그 아래에서 콘텐츠 뷰를 myCanvasView로 설정합니다.
setContentView(myCanvasView)
  1. 앱을 실행합니다. 캔버스에 크기가 없고 아직 아무것도 그리지 않았기 때문에 완전히 흰색 화면이 표시됩니다.

1단계: onSizeChanged() 재정의

뷰의 크기가 변경될 때마다 Android 시스템에서 onSizeChanged() 메서드를 호출합니다. 뷰는 크기가 없는 상태로 시작되므로 활동이 처음 뷰를 생성하고 확장한 후 뷰 onSizeChanged() 메서드를 호출합니다. 따라서 이 onSizeChanged() 메서드는 뷰의 캔버스를 만들고 설정하는 데 이상적입니다.

  1. MyCanvasView의 클래스 수준에서 캔버스 및 비트맵의 변수를 정의합니다. 이름을 extraCanvasextraBitmap로 지정합니다. 다음은 이전에 그린 내용을 캐싱하기 위한 비트맵 및 캔버스입니다.
private lateinit var extraCanvas: Canvas
private lateinit var extraBitmap: Bitmap
  1. 캔버스의 배경색에 대한 클래스 수준 변수 backgroundColor을 정의하고 앞서 정의한 colorBackground로 초기화합니다.
private val backgroundColor = ResourcesCompat.getColor(resources, R.color.colorBackground, null)
  1. MyCanvasView에서 onSizeChanged() 메서드를 재정의합니다. 이 콜백 메서드는 Android 시스템의 화면 크기를 변경합니다. 즉, 새 너비와 높이가 변경되고(이전의 너비와 높이가 변경됨)
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
   super.onSizeChanged(width, height, oldWidth, oldHeight)
}
  1. onSizeChanged() 내에서 화면 크기인 새 너비와 높이를 사용하여 Bitmap의 인스턴스를 만들고 extraBitmap에 할당합니다. 세 번째 인수는 비트맵 색상 구성입니다. ARGB_8888는 각 색상을 4바이트로 저장하는 것이 좋습니다.
extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
  1. extraBitmap에서 Canvas 인스턴스를 만들어 extraCanvas에 할당합니다.
 extraCanvas = Canvas(extraBitmap)
  1. extraCanvas을 채울 배경색을 지정합니다.
extraCanvas.drawColor(backgroundColor)
  1. onSizeChanged()를 살펴보면 함수가 실행될 때마다 새 비트맵과 캔버스가 만들어집니다. 크기가 변경되었으므로 새 비트맵이 필요합니다. 그러나 메모리 누수가 발생하여 이전 비트맵이 남아 있습니다. 이 문제를 해결하려면 super를 호출한 직후에 이 코드를 추가하여 다음 코드를 만들기 전에 extraBitmap를 재활용하세요.
if (::extraBitmap.isInitialized) extraBitmap.recycle()

2단계: onDraw() 재정의

MyCanvasView의 모든 그리기 작업은 onDraw()에서 발생합니다.

시작하려면 캔버스를 표시하고 onSizeChanged()에서 설정한 배경 색상으로 화면을 채웁니다.

  1. onDraw()를 재정의하고 뷰와 연결된 캔버스에 캐시된 extraBitmap의 콘텐츠를 그립니다. drawBitmap() Canvas 메서드는 여러 버전으로 제공됩니다. 이 코드에서 나중에 설정할 때 왼쪽 상단의 비트맵, x 및 y 좌표 (픽셀) 및 Paintnull를 제공합니다.
override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
canvas.drawBitmap(extraBitmap, 0f, 0f, null)
}


onDraw()에 전달되고 시스템에서 비트맵을 표시하는 데 사용하는 캔버스는 onSizeChanged() 메서드에서 생성하고 비트맵에 그리는 데 사용되는 캔버스와 다릅니다.

  1. 앱을 실행합니다. 전체 화면이 지정된 배경 색상으로 채워져 있어야 합니다.

그림을 그리려면 그리기 시 항목의 스타일 지정 방법을 지정하는 Paint 객체와 그리는 내용을 지정하는 Path이 필요합니다.

1단계: Paint 객체 초기화

  1. MyCanvasView.kt의 최상위 파일 수준에서 획 너비의 상수를 정의합니다.
private const val STROKE_WIDTH = 12f // has to be float
  1. MyCanvasView의 클래스 수준에서 그릴 색상을 보유하기 위한 변수 drawColor를 정의하고 앞서 정의한 colorPaint 리소스로 초기화합니다.
private val drawColor = ResourcesCompat.getColor(resources, R.color.colorPaint, null)
  1. 아래 클래스 수준에서 Paint 객체의 paint 변수를 추가하고 다음과 같이 초기화합니다.
// Set up the paint with which to draw.
private val paint = Paint().apply {
   color = drawColor
   // Smooths out edges of what is drawn without affecting shape.
   isAntiAlias = true
   // Dithering affects how colors with higher-precision than the device are down-sampled.
   isDither = true
   style = Paint.Style.STROKE // default: FILL
   strokeJoin = Paint.Join.ROUND // default: MITER
   strokeCap = Paint.Cap.ROUND // default: BUTT
   strokeWidth = STROKE_WIDTH // default: Hairline-width (really thin)
}
  • paintcolor는 이전에 정의한 drawColor입니다.
  • isAntiAlias는 가장자리 스무딩을 적용할지 정의합니다. isAntiAliastrue로 설정하면 도형에 영향을 주지 않으면서 그려진 모서리를 부드럽게 합니다.
  • true인 경우 isDither는 기기가 다운샘플링되는 것보다 정밀도가 높은 색상에 영향을 미칩니다. 예를 들어 디더링은 이미지의 색상 범위를 256개 이하로 줄이는 가장 일반적인 방법입니다.
  • style는 그림의 유형을 획으로 설정하고, 이는 선입니다. Paint.Style은 그려지는 프리미티브가 같은 색상에 채워져 있거나 스트로크되거나, 둘 다인지를 지정합니다. 기본값은 페인트가 적용되는 객체를 채우는 것입니다. '채우기'는 도형 내부의 색상을 지정하는 반면, '획'은 윤곽선을 따릅니다.
  • Paint.JoinstrokeJoin는 스트로크가 적용된 경로에서 선과 곡선 세그먼트가 결합되는 방식을 지정합니다. 기본값은 MITER입니다.
  • strokeCap는 선 끝의 모양을 상한으로 설정합니다. Paint.Cap는 스트로크와 선의 시작과 끝을 지정합니다. 기본값은 BUTT입니다.
  • strokeWidth는 획의 너비를 픽셀 단위로 지정합니다. 기본값은 헤어라인 너비이며 매우 얇기 때문에 앞에서 정의한 STROKE_WIDTH 상수로 설정됩니다.

2단계: Path 객체 초기화

Path은 사용자가 그리는 경로의 경로입니다.

  1. MyCanvasView에서 path 변수를 추가하고 사용자가 화면에서 터치할 때 그려지는 경로를 저장할 Path 객체로 초기화합니다. Pathandroid.graphics.Path를 가져옵니다.
private var path = Path()

1단계: 디스플레이에서 모션에 응답

뷰의 onTouchEvent() 메서드는 사용자가 디스플레이를 터치할 때마다 호출됩니다.

  1. MyCanvasView에서, onTouchEvent() 메서드를 재정의하여 event에 전달된 xy 좌표를 캐시합니다. 그런 다음 when 표현식을 사용하여 화면에서 터치, 화면 이동, 터치 해제와 관련된 모션 이벤트를 처리합니다. 다음은 화면에 선을 그리는 데 관심이 있는 이벤트입니다. 각 이벤트 유형에 대해 아래 코드와 같이 유틸리티 메서드를 호출합니다. 터치 이벤트의 전체 목록은 MotionEvent 클래스 문서를 참고하세요.
override fun onTouchEvent(event: MotionEvent): Boolean {
   motionTouchEventX = event.x
   motionTouchEventY = event.y

   when (event.action) {
       MotionEvent.ACTION_DOWN -> touchStart()
       MotionEvent.ACTION_MOVE -> touchMove()
       MotionEvent.ACTION_UP -> touchUp()
   }
   return true
}
  1. 클래스 수준에서 현재 터치 이벤트의 x 및 y 좌표 (MotionEvent 좌표)를 캐시하는 누락된 motionTouchEventXmotionTouchEventY 변수를 추가합니다. 0f로 초기화합니다.
private var motionTouchEventX = 0f
private var motionTouchEventY = 0f
  1. 세 함수 touchStart(), touchMove(), touchUp()의 스텁을 만듭니다.
private fun touchStart() {}

private fun touchMove() {}

private fun touchUp() {}
  1. 코드는 빌드되고 실행되지만 색상 배경과 다른 것은 아직 표시되지 않습니다.

2단계: touchStart() 구현

이 메서드는 사용자가 화면을 처음 터치할 때 호출됩니다.

  1. 클래스 수준에서 최신 x 및 y 값을 캐시하는 변수를 추가합니다. 사용자가 움직임을 멈추고 터치를 해제하면 다음 경로 (그리는 선의 다음 세그먼트)의 시작점이 됩니다.
private var currentX = 0f
private var currentY = 0f
  1. 다음과 같이 touchStart() 메서드를 구현합니다. path을 재설정하고 터치 이벤트 (motionTouchEventXmotionTouchEventY)의 x-y 좌표로 이동한 다음, 이 값에 currentXcurrentY을 할당합니다.
private fun touchStart() {
   path.reset()
   path.moveTo(motionTouchEventX, motionTouchEventY)
   currentX = motionTouchEventX
   currentY = motionTouchEventY
}

3단계: touchMove() 구현

  1. 클래스 수준에서 touchTolerance 변수를 추가하고 ViewConfiguration.get(context).scaledTouchSlop로 설정합니다.
private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop

경로를 사용하면 모든 픽셀을 그릴 필요가 없으며 디스플레이 새로고침을 요청할 때마다 필요합니다. 대신 성능 향상을 위해 지점 간 경로를 보간할 수 있습니다.

  • 손가락이 거의 움직이지 않은 경우 그릴 필요가 없습니다.
  • 손가락이 touchTolerance 거리 미만으로 떨어졌다면 그리지 마세요.
  • scaledTouchSlop는 시스템에서 사용자가 스크롤한다고 생각하기 전에 터치가 걸어갈 수 있는 거리를 픽셀 단위로 반환합니다.
  1. touchMove() 메서드를 정의합니다. 이동 거리(dx, dy)를 계산하고 두 지점 사이의 곡선을 만들어 path에 저장하고 실행 중인 currentXcurrentY 계산을 업데이트하고 path를 그립니다. 그런 다음 invalidate()를 호출하여 업데이트된 path로 화면을 강제로 다시 그립니다.
private fun touchMove() {
   val dx = Math.abs(motionTouchEventX - currentX)
   val dy = Math.abs(motionTouchEventY - currentY)
   if (dx >= touchTolerance || dy >= touchTolerance) {
       // QuadTo() adds a quadratic bezier from the last point,
       // approaching control point (x1,y1), and ending at (x2,y2).
       path.quadTo(currentX, currentY, (motionTouchEventX + currentX) / 2, (motionTouchEventY + currentY) / 2)
       currentX = motionTouchEventX
       currentY = motionTouchEventY
       // Draw the path in the extra bitmap to cache it.
       extraCanvas.drawPath(path, paint)
   }
   invalidate()
}

이 메서드에 관한 자세한 내용은 다음과 같습니다.

  1. 이동한 거리(dx, dy)를 계산합니다.
  2. 움직임이 터치 허용 범위를 벗어났다면 경로에 세그먼트를 추가합니다.
  3. 다음 세그먼트의 시작점을 이 세그먼트의 엔드포인트로 설정합니다.
  4. lineTo() 대신 quadTo()를 사용하면 모서리 없이 부드럽게 선을 그립니다. 베지어 곡선을 참고하세요.
  5. invalidate()를 호출하여 최종적으로 onDraw()를 호출하고 뷰를 다시 그립니다.

4단계: touchUp() 구현

사용자가 터치를 해제하면 경로가 다시 그려지지 않도록 경로를 재설정하기만 하면 됩니다. 아무것도 그려지지 않으므로 무효화가 필요하지 않습니다.

  1. touchUp() 메서드를 구현합니다.
private fun touchUp() {
   // Reset the path so it doesn't get drawn again.
   path.reset()
}
  1. 코드를 실행하고 손가락으로 화면에 그리세요. 기기를 회전하면 그리기 상태가 저장되지 않으므로 화면이 삭제됩니다. 이 샘플 앱의 경우 기본적으로 화면을 지우는 간단한 방법을 사용자에게 제공하기 위해서입니다.

5단계: 스케치 주위에 프레임 그리기

사용자가 화면에 그리면 앱이 경로를 구성하고 비트맵 extraBitmap에 저장합니다. onDraw() 메서드는 뷰의 캔버스에 추가 비트맵을 표시합니다. onDraw()에서 더 많은 그림을 그릴 수 있습니다. 예를 들어 비트맵을 그린 후 도형을 그릴 수 있습니다.

이 단계에서는 사진 가장자리에 프레임을 그립니다.

  1. MyCanvasView에서 Rect 객체가 포함된 frame라는 변수를 추가합니다.
private lateinit var frame: Rect
  1. onSizeChanged() 끝에 인셋을 정의하고 새 크기와 인셋을 사용하여 프레임에 사용할 Rect를 만드는 코드를 추가합니다.
// Calculate a rectangular frame around the picture.
val inset = 40
frame = Rect(inset, inset, width - inset, height - inset)
  1. onDraw()에서 비트맵을 그린 후 직사각형을 그립니다.
// Draw a frame around the canvas.
canvas.drawRect(frame, paint)
  1. 앱을 실행합니다. 프레임을 확인합니다.

작업 (선택사항): 경로에 데이터 저장

현재 앱에서 그리기 정보는 비트맵으로 저장됩니다. 이는 훌륭한 해결책이지만 그리기 정보를 저장할 수 있는 유일한 방법은 아닙니다. 그림 기록을 저장하는 방법은 앱과 다양한 요구사항에 따라 다릅니다. 예를 들어 도형을 그리고 있는 경우 도형 목록과 위치를 저장할 수 있습니다. MiniPaint 앱의 경우 경로를 Path로 저장할 수 있습니다. 다음은 사용해 보려는 경우 수행하는 방법에 대한 일반적인 개요입니다.

  1. MyCanvasView에서 extraCanvasextraBitmap의 모든 코드를 삭제합니다.
  2. 지금까지의 경로와 현재 그려진 경로를 위한 변수를 추가합니다.
// Path representing the drawing so far
private val drawing = Path()

// Path representing what's currently being drawn
private val curPath = Path()
  1. onDraw()에서 비트맵을 그리는 대신 저장된 현재 경로를 그립니다.
// Draw the drawing so far
canvas.drawPath(drawing, paint)
// Draw any current squiggle
canvas.drawPath(curPath, paint)
// Draw a frame around the canvas
canvas.drawRect(frame, paint)
  1. touchUp()에서 현재 경로를 이전 경로에 추가하고 현재 경로를 재설정합니다.
// Add the current path to the drawing so far
drawing.addPath(curPath)
// Rewind the current path for the next touch
curPath.reset()
  1. 앱을 실행하면 별다른 차이가 없습니다.

완성된 Codelab의 코드를 다운로드합니다.

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-canvas


또는 저장소를 ZIP 파일로 다운로드하여 압축을 풀고 Android 스튜디오에서 열 수 있습니다.

ZIP 파일 다운로드

  • Canvas은 그리기 메서드를 제공하는 2D 그리기 노출 영역입니다.
  • Canvas는 이 인스턴스를 표시하는 View 인스턴스와 연결할 수 있습니다.
  • Paint 객체에는 도형 (예: 선, 직사각형, 타원형) 및 텍스트를 그리는 방법에 대한 스타일 및 색상 정보가 포함되어 있습니다.
  • 캔버스로 작업하는 일반적인 패턴은 맞춤 뷰를 만들고 onDraw()onSizeChanged() 메서드를 재정의하는 것입니다.
  • onTouchEvent() 메서드를 재정의하여 사용자 터치를 캡처하고 그리기에 응답하세요.
  • 추가 비트맵을 사용하여 시간 경과에 따라 변경되는 그림의 정보를 캐시할 수 있습니다. 또는 도형을 저장할 수도 있고 경로를 저장할 수도 있습니다.

Udacity 과정:

Android 개발자 문서:

이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 통해 작업하는 학생들의 숙제 과제가 나와 있습니다. 강사는 다음을 처리합니다.

  • 필요한 경우 과제를 할당합니다.
  • 학생에게 과제 과제를 제출하는 방법을 알려주세요.
  • 과제 과제를 채점합니다.

강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 다른 적절한 숙제를 할당해도 좋습니다.

이 Codelab을 직접 학습하고 있다면 언제든지 숙제를 통해 지식을 확인해 보세요.

답변

질문 1

다음 중 Canvas 작업에 필요한 구성요소는 무엇인가요? 해당하는 보기를 모두 선택하세요.

Bitmap

Paint

Path

View

질문 2

invalidate() 호출은 일반적으로 어떤 역할을 하나요?

▢ 앱을 무효화하고 다시 시작합니다.

▢ 비트맵에서 그림을 삭제합니다.

▢ 이전 코드를 실행하면 안 됨을 나타냅니다.

▢은 화면을 다시 그려야 한다고 시스템에 알립니다.

질문 3

Canvas, Bitmap, Paint 객체의 함수는 무엇인가요?

▢ 2D 그리기 표면, 화면에 표시된 비트맵, 그리기용 스타일 지정 정보

▢ 3D 그리기 표면, 경로 캐싱을 위한 비트맵, 그리기를 위한 정보 스타일 지정

▢ 2D 그리기 표면, 화면에 표시된 비트맵, 뷰 스타일 지정

▢ 그리기 정보의 경우 캐시 그리기, 그리는 비트맵, 그리기의 스타일 지정 정보

이 과정의 다른 Codelab에 관한 링크는 Kotlin Codelab의 고급 Android 방문 페이지를 참고하세요.