이 Codelab은 Kotlin 기반 Android 고급 교육 과정의 일부입니다. Codelab을 순서대로 진행하는 경우 학습 효과를 극대화할 수 있지만 순서를 바꿔 진행해도 괜찮습니다. 모든 과정 Codelab은 Kotlin Codelab의 고급 Android Codelab 방문 페이지에 나열되어 있습니다.
소개
Android에는 뷰에서 맞춤 2D 그래픽 및 애니메이션을 구현하는 데 사용할 수 있는 여러 기술이 있습니다.
드로어블을 사용할 뿐만 아니라 Canvas
클래스의 그리기 메서드를 사용하여 2D 그림을 만들 수 있습니다. Canvas
은 그리기 메서드를 제공하는 2D 그리기 노출 영역입니다. 이는 시간이 지남에 따라 사용자에게 표시되는 변경사항으로 인해 앱을 정기적으로 다시 그려야 하는 경우 유용합니다. 이 Codelab에서는 View
에 표시되는 캔버스를 만들고 그리는 방법을 알아봅니다.
캔버스에서 실행할 수 있는 작업 유형은 다음과 같습니다.
- 캔버스 전체를 색상으로 채웁니다.
Paint
객체에서 정의한 대로 직사각형, 원호, 경로와 같은 도형을 그립니다.Paint
객체에는 도형 (예: 선, 직사각형, 타원형, 경로)의 그리기 또는 텍스트 글꼴 등 스타일 및 색상 정보가 포함됩니다.- 변환, 확장 또는 맞춤 변환과 같은 변환을 적용합니다.
- 자르기, 즉 캔버스에 도형이나 경로를 적용하여 표시되는 부분을 정의합니다.
Android 드로잉에 관해 어떻게 생각하는지 생각해 보세요.
Android 또는 기타 최신 시스템에 그리기는 추상화 계층 및 하드웨어 최적화까지 포함된 복잡한 프로세스입니다. Android의 그리기 방식은 매력적으로 흥미로운 주제이며 이 내용은 이 Codelab의 범위를 벗어납니다.
이 Codelab과 이 앱의 컨텍스트를 전체 화면 뷰로 캔버스에 그릴 때 다음과 같이 생각할 수 있습니다.
- 그리고 있는 그림을 표시하려면 뷰가 필요합니다. Android 시스템에서 제공하는 뷰 중 하나일 수 있습니다. 또는 이 Codelab에서는 앱의 콘텐츠 뷰 역할을 하는 맞춤 뷰(
MyCanvasView
)를 만듭니다. - 이 뷰는 모든 뷰처럼 자체 캔버스(
canvas
)와 함께 제공됩니다. - 뷰의 캔버스에 그리는 가장 기본적인 방법은
onDraw()
메서드를 재정의하고 캔버스에 그리는 것입니다. - 그림을 그릴 때는 이전에 그린 내용을 캐시해야 합니다. 데이터를 캐시하는 방법에는 여러 가지가 있으며, 하나는 비트맵 (
extraBitmap
)에 있습니다. 또 다른 방법은 그린 좌표와 안내에 따라 그린 기록을 저장하는 것입니다. - 캔버스 그리기 API를 사용하여 캐싱 비트맵 (
extraBitmap
)에 그리려면 캐싱 비트맵을 위한 캐싱 캔버스 (extraCanvas
)를 만듭니다. - 그런 다음 캐싱 비트맵(
extraCanvas
)에 그리면 캐싱 비트맵(extraBitmap
)에 그려집니다. - 화면에 그려진 모든 것을 표시하려면 뷰 캔버스 (
canvas
)에 캐싱 비트맵을 그리도록 지시합니다 (extraBitmap
).
기본 요건
- 활동, 기본 레이아웃으로 앱을 만들고 Android 스튜디오를 사용하여 실행하는 방법
- 이벤트 핸들러를 뷰와 연결하는 방법.
- 맞춤 뷰를 만드는 방법
학습할 내용
Canvas
를 만들고 사용자 터치에 응답하여 그리는 방법
실습할 내용
- 화면을 터치하는 사용자에 대한 응답으로 화면에 선을 그리는 앱을 만듭니다.
- 모션 이벤트를 캡처하고 이에 따라 화면의 전체 화면 맞춤 뷰에 표시되는 캔버스에 선을 그리세요.
MiniPaint 앱은 아래 스크린샷과 같이 사용자 터치에 응답하여 선을 표시하기 위해 맞춤 뷰를 사용합니다.
1단계: MiniPaint 프로젝트 만들기
- Empty Activity 템플릿을 사용하는 MiniPaint라는 새 Kotlin 프로젝트를 만듭니다.
app/res/values/colors.xml
파일을 열고 다음 두 색상을 추가합니다.
<color name="colorBackground">#FFFF5500</color>
<color name="colorPaint">#FFFFEB3B</color>
styles.xml
을 엽니다.- 지정된
AppTheme
스타일의 상위 요소에서DarkActionBar
를NoActionBar
으로 바꿉니다. 이렇게 하면 작업 표시줄이 삭제되므로 전체 화면을 그릴 수 있습니다.
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
2단계: MyCanvasView 클래스 만들기
이 단계에서는 그리기용 맞춤 뷰 MyCanvasView
를 만듭니다.
app/java/com.example.android.minipaint
패키지에서MyCanvasView
라는 New > Kotlin File/Class를 만듭니다.MyCanvasView
클래스가View
클래스를 확장하고context: Context
를 전달하도록 합니다. 추천 가져오기를 수락합니다.
import android.content.Context
import android.view.View
class MyCanvasView(context: Context) : View(context) {
}
3단계: MyCanvasView를 콘텐츠 뷰로 설정
MyCanvasView
에 그릴 내용을 표시하려면 MainActivity
의 콘텐츠 뷰로 설정해야 합니다.
strings.xml
를 열고 뷰의 콘텐츠 설명에 사용할 문자열을 정의합니다.
<string name="canvasContentDescription">Mini Paint is a simple line drawing app.
Drag your fingers to draw. Rotate the phone to clear.</string>
MainActivity.kt
을 엽니다.onCreate()
에서setContentView(R.layout.activity_main)
를 삭제합니다.MyCanvasView
인스턴스를 생성합니다.
val myCanvasView = MyCanvasView(this)
- 그 아래에서
myCanvasView
의 레이아웃을 위해 전체 화면을 요청합니다.myCanvasView
에SYSTEM_UI_FLAG_FULLSCREEN
플래그를 설정하면 됩니다. 이렇게 하면 뷰가 화면을 완전히 채웁니다.
myCanvasView.systemUiVisibility = SYSTEM_UI_FLAG_FULLSCREEN
- 콘텐츠 설명을 추가합니다.
myCanvasView.contentDescription = getString(R.string.canvasContentDescription)
- 그 아래에서 콘텐츠 뷰를
myCanvasView
로 설정합니다.
setContentView(myCanvasView)
- 앱을 실행합니다. 캔버스에 크기가 없고 아직 아무것도 그리지 않았기 때문에 완전히 흰색 화면이 표시됩니다.
1단계: onSizeChanged() 재정의
뷰의 크기가 변경될 때마다 Android 시스템에서 onSizeChanged()
메서드를 호출합니다. 뷰는 크기가 없는 상태로 시작되므로 활동이 처음 뷰를 생성하고 확장한 후 뷰 onSizeChanged()
메서드를 호출합니다. 따라서 이 onSizeChanged()
메서드는 뷰의 캔버스를 만들고 설정하는 데 이상적입니다.
MyCanvasView
의 클래스 수준에서 캔버스 및 비트맵의 변수를 정의합니다. 이름을extraCanvas
및extraBitmap
로 지정합니다. 다음은 이전에 그린 내용을 캐싱하기 위한 비트맵 및 캔버스입니다.
private lateinit var extraCanvas: Canvas
private lateinit var extraBitmap: Bitmap
- 캔버스의 배경색에 대한 클래스 수준 변수
backgroundColor
을 정의하고 앞서 정의한colorBackground
로 초기화합니다.
private val backgroundColor = ResourcesCompat.getColor(resources, R.color.colorBackground, null)
MyCanvasView
에서onSizeChanged()
메서드를 재정의합니다. 이 콜백 메서드는 Android 시스템의 화면 크기를 변경합니다. 즉, 새 너비와 높이가 변경되고(이전의 너비와 높이가 변경됨)
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
super.onSizeChanged(width, height, oldWidth, oldHeight)
}
onSizeChanged()
내에서 화면 크기인 새 너비와 높이를 사용하여Bitmap
의 인스턴스를 만들고extraBitmap
에 할당합니다. 세 번째 인수는 비트맵 색상 구성입니다.ARGB_8888
는 각 색상을 4바이트로 저장하는 것이 좋습니다.
extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
extraBitmap
에서Canvas
인스턴스를 만들어extraCanvas
에 할당합니다.
extraCanvas = Canvas(extraBitmap)
extraCanvas
을 채울 배경색을 지정합니다.
extraCanvas.drawColor(backgroundColor)
onSizeChanged()
를 살펴보면 함수가 실행될 때마다 새 비트맵과 캔버스가 만들어집니다. 크기가 변경되었으므로 새 비트맵이 필요합니다. 그러나 메모리 누수가 발생하여 이전 비트맵이 남아 있습니다. 이 문제를 해결하려면super
를 호출한 직후에 이 코드를 추가하여 다음 코드를 만들기 전에extraBitmap
를 재활용하세요.
if (::extraBitmap.isInitialized) extraBitmap.recycle()
2단계: onDraw() 재정의
MyCanvasView
의 모든 그리기 작업은 onDraw()
에서 발생합니다.
시작하려면 캔버스를 표시하고 onSizeChanged()
에서 설정한 배경 색상으로 화면을 채웁니다.
onDraw()
를 재정의하고 뷰와 연결된 캔버스에 캐시된extraBitmap
의 콘텐츠를 그립니다.drawBitmap()
Canvas
메서드는 여러 버전으로 제공됩니다. 이 코드에서 나중에 설정할 때 왼쪽 상단의 비트맵, x 및 y 좌표 (픽셀) 및Paint
의null
를 제공합니다.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawBitmap(extraBitmap, 0f, 0f, null)
}
onDraw()
에 전달되고 시스템에서 비트맵을 표시하는 데 사용하는 캔버스는 onSizeChanged()
메서드에서 생성하고 비트맵에 그리는 데 사용되는 캔버스와 다릅니다.
- 앱을 실행합니다. 전체 화면이 지정된 배경 색상으로 채워져 있어야 합니다.
그림을 그리려면 그리기 시 항목의 스타일 지정 방법을 지정하는 Paint
객체와 그리는 내용을 지정하는 Path
이 필요합니다.
1단계: Paint 객체 초기화
- MyCanvasView.kt의 최상위 파일 수준에서 획 너비의 상수를 정의합니다.
private const val STROKE_WIDTH = 12f // has to be float
MyCanvasView
의 클래스 수준에서 그릴 색상을 보유하기 위한 변수drawColor
를 정의하고 앞서 정의한colorPaint
리소스로 초기화합니다.
private val drawColor = ResourcesCompat.getColor(resources, R.color.colorPaint, null)
- 아래 클래스 수준에서
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)
}
paint
의color
는 이전에 정의한drawColor
입니다.isAntiAlias
는 가장자리 스무딩을 적용할지 정의합니다.isAntiAlias
을true
로 설정하면 도형에 영향을 주지 않으면서 그려진 모서리를 부드럽게 합니다.true
인 경우isDither
는 기기가 다운샘플링되는 것보다 정밀도가 높은 색상에 영향을 미칩니다. 예를 들어 디더링은 이미지의 색상 범위를 256개 이하로 줄이는 가장 일반적인 방법입니다.style
는 그림의 유형을 획으로 설정하고, 이는 선입니다.Paint.Style
은 그려지는 프리미티브가 같은 색상에 채워져 있거나 스트로크되거나, 둘 다인지를 지정합니다. 기본값은 페인트가 적용되는 객체를 채우는 것입니다. '채우기'는 도형 내부의 색상을 지정하는 반면, '획'은 윤곽선을 따릅니다.Paint.Join
의strokeJoin
는 스트로크가 적용된 경로에서 선과 곡선 세그먼트가 결합되는 방식을 지정합니다. 기본값은MITER
입니다.strokeCap
는 선 끝의 모양을 상한으로 설정합니다.Paint.Cap
는 스트로크와 선의 시작과 끝을 지정합니다. 기본값은BUTT
입니다.strokeWidth
는 획의 너비를 픽셀 단위로 지정합니다. 기본값은 헤어라인 너비이며 매우 얇기 때문에 앞에서 정의한STROKE_WIDTH
상수로 설정됩니다.
2단계: Path 객체 초기화
Path
은 사용자가 그리는 경로의 경로입니다.
MyCanvasView
에서path
변수를 추가하고 사용자가 화면에서 터치할 때 그려지는 경로를 저장할Path
객체로 초기화합니다.Path
의android.graphics.Path
를 가져옵니다.
private var path = Path()
1단계: 디스플레이에서 모션에 응답
뷰의 onTouchEvent()
메서드는 사용자가 디스플레이를 터치할 때마다 호출됩니다.
MyCanvasView
에서,onTouchEvent()
메서드를 재정의하여event
에 전달된x
및y
좌표를 캐시합니다. 그런 다음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
}
- 클래스 수준에서 현재 터치 이벤트의 x 및 y 좌표 (
MotionEvent
좌표)를 캐시하는 누락된motionTouchEventX
및motionTouchEventY
변수를 추가합니다.0f
로 초기화합니다.
private var motionTouchEventX = 0f
private var motionTouchEventY = 0f
- 세 함수
touchStart()
,touchMove()
,touchUp()
의 스텁을 만듭니다.
private fun touchStart() {}
private fun touchMove() {}
private fun touchUp() {}
- 코드는 빌드되고 실행되지만 색상 배경과 다른 것은 아직 표시되지 않습니다.
2단계: touchStart() 구현
이 메서드는 사용자가 화면을 처음 터치할 때 호출됩니다.
- 클래스 수준에서 최신 x 및 y 값을 캐시하는 변수를 추가합니다. 사용자가 움직임을 멈추고 터치를 해제하면 다음 경로 (그리는 선의 다음 세그먼트)의 시작점이 됩니다.
private var currentX = 0f
private var currentY = 0f
- 다음과 같이
touchStart()
메서드를 구현합니다.path
을 재설정하고 터치 이벤트 (motionTouchEventX
및motionTouchEventY
)의 x-y 좌표로 이동한 다음, 이 값에currentX
및currentY
을 할당합니다.
private fun touchStart() {
path.reset()
path.moveTo(motionTouchEventX, motionTouchEventY)
currentX = motionTouchEventX
currentY = motionTouchEventY
}
3단계: touchMove() 구현
- 클래스 수준에서
touchTolerance
변수를 추가하고ViewConfiguration.get(context).scaledTouchSlop
로 설정합니다.
private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop
경로를 사용하면 모든 픽셀을 그릴 필요가 없으며 디스플레이 새로고침을 요청할 때마다 필요합니다. 대신 성능 향상을 위해 지점 간 경로를 보간할 수 있습니다.
- 손가락이 거의 움직이지 않은 경우 그릴 필요가 없습니다.
- 손가락이
touchTolerance
거리 미만으로 떨어졌다면 그리지 마세요. scaledTouchSlop
는 시스템에서 사용자가 스크롤한다고 생각하기 전에 터치가 걸어갈 수 있는 거리를 픽셀 단위로 반환합니다.
touchMove()
메서드를 정의합니다. 이동 거리(dx
,dy
)를 계산하고 두 지점 사이의 곡선을 만들어path
에 저장하고 실행 중인currentX
및currentY
계산을 업데이트하고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()
}
이 메서드에 관한 자세한 내용은 다음과 같습니다.
- 이동한 거리(
dx, dy
)를 계산합니다. - 움직임이 터치 허용 범위를 벗어났다면 경로에 세그먼트를 추가합니다.
- 다음 세그먼트의 시작점을 이 세그먼트의 엔드포인트로 설정합니다.
lineTo()
대신quadTo()
를 사용하면 모서리 없이 부드럽게 선을 그립니다. 베지어 곡선을 참고하세요.invalidate()
를 호출하여 최종적으로onDraw()
를 호출하고 뷰를 다시 그립니다.
4단계: touchUp() 구현
사용자가 터치를 해제하면 경로가 다시 그려지지 않도록 경로를 재설정하기만 하면 됩니다. 아무것도 그려지지 않으므로 무효화가 필요하지 않습니다.
touchUp()
메서드를 구현합니다.
private fun touchUp() {
// Reset the path so it doesn't get drawn again.
path.reset()
}
- 코드를 실행하고 손가락으로 화면에 그리세요. 기기를 회전하면 그리기 상태가 저장되지 않으므로 화면이 삭제됩니다. 이 샘플 앱의 경우 기본적으로 화면을 지우는 간단한 방법을 사용자에게 제공하기 위해서입니다.
5단계: 스케치 주위에 프레임 그리기
사용자가 화면에 그리면 앱이 경로를 구성하고 비트맵 extraBitmap
에 저장합니다. onDraw()
메서드는 뷰의 캔버스에 추가 비트맵을 표시합니다. onDraw()
에서 더 많은 그림을 그릴 수 있습니다. 예를 들어 비트맵을 그린 후 도형을 그릴 수 있습니다.
이 단계에서는 사진 가장자리에 프레임을 그립니다.
MyCanvasView
에서Rect
객체가 포함된frame
라는 변수를 추가합니다.
private lateinit var frame: Rect
onSizeChanged()
끝에 인셋을 정의하고 새 크기와 인셋을 사용하여 프레임에 사용할Rect
를 만드는 코드를 추가합니다.
// Calculate a rectangular frame around the picture.
val inset = 40
frame = Rect(inset, inset, width - inset, height - inset)
onDraw()
에서 비트맵을 그린 후 직사각형을 그립니다.
// Draw a frame around the canvas.
canvas.drawRect(frame, paint)
- 앱을 실행합니다. 프레임을 확인합니다.
작업 (선택사항): 경로에 데이터 저장
현재 앱에서 그리기 정보는 비트맵으로 저장됩니다. 이는 훌륭한 해결책이지만 그리기 정보를 저장할 수 있는 유일한 방법은 아닙니다. 그림 기록을 저장하는 방법은 앱과 다양한 요구사항에 따라 다릅니다. 예를 들어 도형을 그리고 있는 경우 도형 목록과 위치를 저장할 수 있습니다. MiniPaint 앱의 경우 경로를 Path
로 저장할 수 있습니다. 다음은 사용해 보려는 경우 수행하는 방법에 대한 일반적인 개요입니다.
MyCanvasView
에서extraCanvas
및extraBitmap
의 모든 코드를 삭제합니다.- 지금까지의 경로와 현재 그려진 경로를 위한 변수를 추가합니다.
// Path representing the drawing so far
private val drawing = Path()
// Path representing what's currently being drawn
private val curPath = Path()
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)
touchUp()
에서 현재 경로를 이전 경로에 추가하고 현재 경로를 재설정합니다.
// Add the current path to the drawing so far
drawing.addPath(curPath)
// Rewind the current path for the next touch
curPath.reset()
- 앱을 실행하면 별다른 차이가 없습니다.
완성된 Codelab의 코드를 다운로드합니다.
$ git clone https://github.com/googlecodelabs/android-kotlin-drawing-canvas
또는 저장소를 ZIP 파일로 다운로드하여 압축을 풀고 Android 스튜디오에서 열 수 있습니다.
Canvas
은 그리기 메서드를 제공하는 2D 그리기 노출 영역입니다.Canvas
는 이 인스턴스를 표시하는View
인스턴스와 연결할 수 있습니다.Paint
객체에는 도형 (예: 선, 직사각형, 타원형) 및 텍스트를 그리는 방법에 대한 스타일 및 색상 정보가 포함되어 있습니다.- 캔버스로 작업하는 일반적인 패턴은 맞춤 뷰를 만들고
onDraw()
및onSizeChanged()
메서드를 재정의하는 것입니다. onTouchEvent()
메서드를 재정의하여 사용자 터치를 캡처하고 그리기에 응답하세요.- 추가 비트맵을 사용하여 시간 경과에 따라 변경되는 그림의 정보를 캐시할 수 있습니다. 또는 도형을 저장할 수도 있고 경로를 저장할 수도 있습니다.
Udacity 과정:
Android 개발자 문서:
Canvas
클래스Bitmap
클래스View
클래스Paint
클래스Bitmap.config
구성Path
클래스- 베지어 곡선 위키백과 페이지
- 캔버스 및 드로어블
- 그래픽 아키텍처 도움말 (고급)
- 드로어블
- onDraw()
- onSizeChanged()
MotionEvent
ViewConfiguration.get(context).scaledTouchSlop
이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 통해 작업하는 학생들의 숙제 과제가 나와 있습니다. 강사는 다음을 처리합니다.
- 필요한 경우 과제를 할당합니다.
- 학생에게 과제 과제를 제출하는 방법을 알려주세요.
- 과제 과제를 채점합니다.
강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 다른 적절한 숙제를 할당해도 좋습니다.
이 Codelab을 직접 학습하고 있다면 언제든지 숙제를 통해 지식을 확인해 보세요.
답변
질문 1
다음 중 Canvas
작업에 필요한 구성요소는 무엇인가요? 해당하는 보기를 모두 선택하세요.
▢ Bitmap
▢ Paint
▢ Path
▢ View
질문 2
invalidate()
호출은 일반적으로 어떤 역할을 하나요?
▢ 앱을 무효화하고 다시 시작합니다.
▢ 비트맵에서 그림을 삭제합니다.
▢ 이전 코드를 실행하면 안 됨을 나타냅니다.
▢은 화면을 다시 그려야 한다고 시스템에 알립니다.
질문 3
Canvas
, Bitmap
, Paint
객체의 함수는 무엇인가요?
▢ 2D 그리기 표면, 화면에 표시된 비트맵, 그리기용 스타일 지정 정보
▢ 3D 그리기 표면, 경로 캐싱을 위한 비트맵, 그리기를 위한 정보 스타일 지정
▢ 2D 그리기 표면, 화면에 표시된 비트맵, 뷰 스타일 지정
▢ 그리기 정보의 경우 캐시 그리기, 그리는 비트맵, 그리기의 스타일 지정 정보
이 과정의 다른 Codelab에 관한 링크는 Kotlin Codelab의 고급 Android 방문 페이지를 참고하세요.