많은 앱에서 화면에 표시되는 내용을 정확하게 제어할 수 있어야 합니다. 화면의 적절한 위치에 상자나 원을 배치하는 작은 작업일 수도 있고 다양한 스타일의 그래픽 요소를 정교하게 정렬해야 하는 작업일 수도 있습니다.
수정자와 DrawScope
를 사용한 기본 그리기
Compose에서 맞춤 항목을 그리는 핵심 방법은 Modifier.drawWithContent
, Modifier.drawBehind
, Modifier.drawWithCache
와 같은 수정자를 사용하는 것입니다.
예를 들어 컴포저블 뒤에 무언가를 그리려면 drawBehind
수정자를 사용하여 그리기 명령어를 실행하면 됩니다.
Spacer( modifier = Modifier .fillMaxSize() .drawBehind { // this = DrawScope } )
그리기를 실행하는 컴포저블만 필요하다면 Canvas
컴포저블을 사용하면 됩니다. Canvas
컴포저블은 Modifier.drawBehind
주변의 편리한 래퍼입니다. 다른 Compose UI 요소와 동일한 방식으로 레이아웃에 Canvas
를 배치합니다. Canvas
내에서 스타일과 위치를 정밀하게 제어하는 요소를 그릴 수 있습니다.
모든 그리기 수정자는 자체 상태를 유지하는 범위가 지정된 그리기 환경인 DrawScope
를 노출합니다. 이렇게 하면 그래픽 요소 그룹의 매개변수를 설정할 수 있습니다. DrawScope
는 DrawScope
의 현재 크기를 지정하는 Size
객체인 size
와 같은 몇 가지 유용한 필드를 제공합니다.
무언가를 그리려면 DrawScope
에서 여러 그리기 함수 중 하나를 사용하면 됩니다. 예를 들어 다음 코드는 화면 왼쪽 상단에 직사각형을 그립니다.
Canvas(modifier = Modifier.fillMaxSize()) { val canvasQuadrantSize = size / 2F drawRect( color = Color.Magenta, size = canvasQuadrantSize ) }
다양한 그리기 수정자에 관한 자세한 내용은 그래픽 수정자 문서를 참고하세요.
좌표계
화면에 무언가를 그리려면 항목의 오프셋(x
및 y
)과 크기를 알아야 합니다. DrawScope
의 대다수 그리기 메서드에서는 위치와 크기가 기본 매개변수 값으로 제공됩니다. 기본 매개변수는 일반적으로
항목을 캔버스의 [0, 0]
지점에 배치하며 전체 그리기 영역을 채우는 기본 size
를 제공합니다. 위 예를 보면 직사각형이 왼쪽 상단에 배치된 것을 확인할 수 있습니다. 항목의 크기와 위치를 조정하려면 Compose의 좌표계를 알아야 합니다.
좌표계의 원점([0,0]
)은 그리기 영역에서 맨 왼쪽 상단 픽셀에 있습니다. x
는 오른쪽으로 이동할수록 증가하고 y
는 아래쪽으로 이동할수록 증가합니다.
예를 들어 캔버스 영역의 오른쪽 상단부터 왼쪽 하단까지 대각선을 그리려면 DrawScope.drawLine()
함수를 사용하고 다음과 같이 상응하는 x, y 위치로 시작 및 끝 오프셋을 지정하면 됩니다.
Canvas(modifier = Modifier.fillMaxSize()) { val canvasWidth = size.width val canvasHeight = size.height drawLine( start = Offset(x = canvasWidth, y = 0f), end = Offset(x = 0f, y = canvasHeight), color = Color.Blue ) }
기본 변환
DrawScope
는 그리기 명령어 실행 위치나 방식을 변경하는 변환을 제공합니다.
배율
DrawScope.scale()
을 사용하여 그리기 작업의 크기를 배율로 늘립니다. scale()
과 같은 작업은 상응하는 람다 내 모든 그리기 작업에 적용됩니다. 예를 들어 다음 코드는 scaleX
를 10배, scaleY
를 15배 늘립니다.
Canvas(modifier = Modifier.fillMaxSize()) { scale(scaleX = 10f, scaleY = 15f) { drawCircle(Color.Blue, radius = 20.dp.toPx()) } }
변환
DrawScope.translate()
를 사용하여 그리기 작업을 위, 아래, 왼쪽, 오른쪽으로 이동합니다. 예를 들어 다음 코드는 그림을 오른쪽으로 100픽셀, 위로 300픽셀 이동합니다.
Canvas(modifier = Modifier.fillMaxSize()) { translate(left = 100f, top = -300f) { drawCircle(Color.Blue, radius = 200.dp.toPx()) } }
회전
DrawScope.rotate()
를 사용하여 피벗 지점을 중심으로 그리기 작업을 회전합니다. 예를 들어 다음 코드는 직사각형을 45도 회전합니다.
Canvas(modifier = Modifier.fillMaxSize()) { rotate(degrees = 45F) { drawRect( color = Color.Gray, topLeft = Offset(x = size.width / 3F, y = size.height / 3F), size = size / 3F ) } }
인셋
DrawScope.inset()
을 사용하여 현재 DrawScope
의 기본 매개변수를 조정해 그리기 경계를 변경하고 그림을 적절히 변환합니다.
Canvas(modifier = Modifier.fillMaxSize()) { val canvasQuadrantSize = size / 2F inset(horizontal = 50f, vertical = 30f) { drawRect(color = Color.Green, size = canvasQuadrantSize) } }
이 코드는 사실상 그리기 명령어에 패딩을 추가합니다.
여러 변환
그림에 여러 변환을 적용하려면 DrawScope.withTransform()
함수를 사용합니다. 이 함수는 원하는 모든 변경사항을 결합하는 단일 변환을 만들어 적용합니다. withTransform()
을 사용하면 Compose가 중첩된 각 변환을 계산하고 저장할 필요 없이 모든 변환이 단일 작업으로 함께 실행되므로 개별 변환을 중첩 호출하는 것보다 더 효율적입니다.
예를 들어 다음 코드는 직사각형에 변환과 회전을 모두 적용합니다.
Canvas(modifier = Modifier.fillMaxSize()) { withTransform({ translate(left = size.width / 5F) rotate(degrees = 45F) }) { drawRect( color = Color.Gray, topLeft = Offset(x = size.width / 3F, y = size.height / 3F), size = size / 3F ) } }
일반적인 그리기 작업
텍스트 그리기
Compose에서 텍스트를 그리려면 일반적으로 Text
컴포저블을 사용하면 됩니다. 그러나 DrawScope
에 있거나 맞춤설정으로 텍스트를 수동으로 그리려면 DrawScope.drawText()
메서드를 사용하세요.
텍스트를 그리려면 rememberTextMeasurer
를 사용하여 TextMeasurer
를 만들고 이 측정기를 사용하여 drawText
를 호출합니다.
val textMeasurer = rememberTextMeasurer() Canvas(modifier = Modifier.fillMaxSize()) { drawText(textMeasurer, "Hello") }
텍스트 측정
텍스트 그리기는 다른 그리기 명령어와 약간 다르게 작동합니다. 일반적으로 그리기 명령어에는 도형/이미지를 그릴 수 있는 크기(너비, 높이)가 제공됩니다. 텍스트의 경우 렌더링된 텍스트의 크기를 제어하는 몇 가지 매개변수(예: 글꼴 크기, 글꼴, 합자, 글자 간격)가 있습니다.
Compose에서는 TextMeasurer
를 사용하여 위의 요소에 따라 측정된 텍스트 크기에 액세스할 수 있습니다. 텍스트 뒤에 배경을 그리려면 측정된 정보를 사용하여 텍스트가 차지하는 영역의 크기를 가져올 수 있습니다.
val textMeasurer = rememberTextMeasurer() Spacer( modifier = Modifier .drawWithCache { val measuredText = textMeasurer.measure( AnnotatedString(longTextSample), constraints = Constraints.fixedWidth((size.width * 2f / 3f).toInt()), style = TextStyle(fontSize = 18.sp) ) onDrawBehind { drawRect(pinkColor, size = measuredText.size.toSize()) drawText(measuredText) } } .fillMaxSize() )
다음 코드 스니펫은 텍스트에 분홍색 배경을 생성합니다.
제약 조건이나 글꼴 크기, 측정된 크기에 영향을 미치는 모든 속성을 조정하면 새로운 크기가 보고됩니다. width
및 height
에 모두 고정된 크기를 설정할 수 있습니다. 그러면 텍스트가 설정된 TextOverflow
를 따릅니다. 예를 들어 다음 코드는 컴포저블 영역의 1⁄3 높이와 1⁄3 너비로 텍스트를 렌더링하고 TextOverflow
를 TextOverflow.Ellipsis
로 설정합니다.
val textMeasurer = rememberTextMeasurer() Spacer( modifier = Modifier .drawWithCache { val measuredText = textMeasurer.measure( AnnotatedString(longTextSample), constraints = Constraints.fixed( width = (size.width / 3f).toInt(), height = (size.height / 3f).toInt() ), overflow = TextOverflow.Ellipsis, style = TextStyle(fontSize = 18.sp) ) onDrawBehind { drawRect(pinkColor, size = measuredText.size.toSize()) drawText(measuredText) } } .fillMaxSize() )
이제 생략 부호가 끝에 있는 텍스트가 제약 조건으로 그려집니다.
이미지 그리기
DrawScope
로 ImageBitmap
을 그리려면 ImageBitmap.imageResource()
를 사용하여 이미지를 로드한 다음 drawImage
를 호출합니다.
val dogImage = ImageBitmap.imageResource(id = R.drawable.dog) Canvas(modifier = Modifier.fillMaxSize(), onDraw = { drawImage(dogImage) })
기본 도형 그리기
DrawScope
에는 도형 그리기 함수가 많이 있습니다. 도형을 그리려면 drawCircle
과 같은 사전 정의된 그리기 함수 중 하나를 사용합니다.
val purpleColor = Color(0xFFBA68C8) Canvas( modifier = Modifier .fillMaxSize() .padding(16.dp), onDraw = { drawCircle(purpleColor) } )
API |
출력 |
경로 그리기
경로는 일련의 수학적 명령으로, 실행 후에 그리기가 생성됩니다. DrawScope
는 DrawScope.drawPath()
메서드를 사용하여 경로를 그릴 수 있습니다.
예를 들어 삼각형을 그리고 싶다고 가정해 보겠습니다. 그리기 영역의 크기를 사용하여 lineTo()
및 moveTo()
와 같은 함수로 경로를 생성할 수 있습니다.
그런 다음 새로 만들어진 이 경로로 drawPath()
를 호출하여 삼각형을 가져옵니다.
Spacer( modifier = Modifier .drawWithCache { val path = Path() path.moveTo(0f, 0f) path.lineTo(size.width / 2f, size.height / 2f) path.lineTo(size.width, 0f) path.close() onDrawBehind { drawPath(path, Color.Magenta, style = Stroke(width = 10f)) } } .fillMaxSize() )
Canvas
객체에 액세스
DrawScope
를 사용하면 Canvas
객체에 직접 액세스할 수 없습니다. DrawScope.drawIntoCanvas()
를 사용하여, 함수를 호출할 수 있는 Canvas
객체 자체에 액세스할 수 있습니다.
예를 들어 캔버스에 그리려는 맞춤 Drawable
이 있으면 캔버스에 액세스하고 Drawable#draw()
를 호출하여 Canvas
객체를 전달할 수 있습니다.
val drawable = ShapeDrawable(OvalShape()) Spacer( modifier = Modifier .drawWithContent { drawIntoCanvas { canvas -> drawable.setBounds(0, 0, size.width.toInt(), size.height.toInt()) drawable.draw(canvas.nativeCanvas) } } .fillMaxSize() )
자세히 알아보기
Compose에서 그리기에 관한 자세한 내용은 다음 리소스를 참고하세요.
- 그래픽 수정자: 다양한 유형의 그리기 수정자에 관해 알아보세요.
- 브러시: 콘텐츠 페인팅을 맞춤설정하는 방법을 알아보세요.
- Compose의 맞춤 레이아웃 및 그래픽 - Android Dev Summit 2022: 레이아웃과 그래픽을 사용하여 Compose에서 맞춤 UI를 빌드하는 방법을 알아보세요.
- JetLagged 샘플: 맞춤 그래프를 그리는 방법을 보여주는 Compose 샘플입니다.
추천 서비스
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- 그래픽 수정자
- Compose의 그래픽
- Jetpack Compose의 정렬 선