메쉬 그라데이션은 패치의 2D 그리드를 사용하여 복잡한 다방향 색상 전환을 만듭니다. 선형 또는 방사형 그래디언트와 달리 메시 그래디언트는 격자에서 색상을 부드럽게 보간합니다. 메시 그라데이션을 사용하여 사용자 인터페이스에서 유연하고 유기적인 미적 요소를 만드세요.

주요 개념
메시 그라데이션을 구성하려면 그리드 크기, 꼭짓점, 점 간 색상 전환을 정의합니다.
- 그리드 크기: 메시는 세로축과 가로축을 따라 패치로 분할됩니다.
rows및columns그리드에는 (rows+1)×(columns+1)개의 꼭짓점이 포함됩니다. 예를 들어 1×1 메시지는 하나의 패치를 형성하는 4개의 꼭짓점으로 구성됩니다. - 정규화된 좌표: 모든 꼭짓점 위치는
(0f, 0f)가 왼쪽 상단을 나타내고(1f, 1f)가 그림 경계의 오른쪽 하단을 나타내는 정규화된 좌표 시스템을 사용합니다. - 베지어 제어점 (접선): 각 꼭짓점에는 최대 4개의 선택적 베지어 제어점이 포함됩니다. 이러한 접선은 인접한 꼭짓점 사이의 가장자리 곡률을 지정합니다.
Offset.Unspecified를 사용하는 경우 Compose는 패치 간의 원활한 전환을 위해 접선을 추론합니다. 4개의 꼭짓점과 제어점으로 형성된 각 그리드 셀은 베지어 패치를 생성합니다. - 색상 보간: 프레임워크는 기본 꼭짓점 사이의 색상을 계산합니다. 더 부드러운 색상 전환을 위해 Catmull-Rom 보간의 경우
hasBicubicColor을true로 설정하고, 양선형 보간의 경우false로 설정합니다.
MeshGradientPainter로 그리기
Jetpack Compose에서는 MeshGradientPainter를 사용하여 메시 그라데이션을 렌더링합니다. MeshGradientPainter가 캔버스에 그립니다.
간단한 메시 그라데이션 만들기
기본 정적 메시 그라데이션을 만들려면 구성 블록 내에서 setVertex 함수를 사용하여 모서리 점을 배치하고 색상을 할당하여 MeshGradientPainter를 초기화합니다.
val rows = 1 val columns = 1 val gradientPainter = remember { MeshGradientPainter(rows, columns) { // Parameters: row, column, position, color setVertex(0, 0, Offset(0f, 0f), Color.Red) // Top-Left setVertex(0, 1, Offset(1f, 0f), Color.Blue) // Top-Right setVertex(1, 0, Offset(0f, 1f), Color.Green) // Bottom-Left setVertex(1, 1, Offset(1f, 1f), Color.Yellow) // Bottom-Right } } Box( modifier = modifier .aspectRatio(16/9f) .fillMaxWidth() .paint(gradientPainter) )
특정 베지어 제어점 사용
기본적으로 메쉬 생성기는 그리드 전환을 원활하게 유지하기 위해 복잡한 계산을 처리합니다. 하지만 특정 색상 섹션을 선택적으로 밀거나 당기거나 날카롭게 꼬집으려면 단일 꼭짓점에서 접선을 명시적으로 맞춤설정할 수 있습니다.
제어 오프셋은 호스트 꼭짓점의 위치를 기준으로 측정됩니다.
val customTangentPainter = remember { MeshGradientPainter(rows = 1, columns = 1) { // Tweak the top-left vertex to curve outwards to the right and bottom setVertex( row = 0, column = 0, position = Offset(0f, 0f), color = Color.Magenta, rightControlPoint = Offset(0.4f, 0.1f), bottomControlPoint = Offset(0.1f, 0.4f) ) // Other points can remain unspecified to use default inferred fallback tangents setVertex(0, 1, Offset(1f, 0f), Color.Cyan) setVertex(1, 0, Offset(0f, 1f), Color.Blue) setVertex(1, 1, Offset(1f, 1f), Color.Black) } } Box( modifier = modifier .aspectRatio(16/9f) .fillMaxWidth() .paint(customTangentPainter) )
고급 그리드 만들기
이 예에서는 3x3 그리드를 보여줍니다. 즉, 지정해야 하는 점이 16개이며 중간 점은 오프셋이 다르게 설정되어 있습니다.
val points = remember { listOf( Offset(0.0f, 0.0f), Offset(0.3f, 0.0f), Offset(0.7f, 0.0f), Offset(1.0f, 0.0f), Offset(0.0f, 0.3f), Offset(0.2f, 0.4f), Offset(0.7f, 0.2f), Offset(1.0f, 0.3f), Offset(0.0f, 0.7f), Offset(0.3f, 0.8f), Offset(0.7f, 0.6f), Offset(1.0f, 0.7f), Offset(0.0f, 1.0f), Offset(0.3f, 1.0f), Offset(0.7f, 1.0f), Offset(1.0f, 1.0f) ) } val gradientPainter = remember { MeshGradientPainter(rows = 3, columns = 3) { // Row 0 setVertex(0, 0, points[0], yellow) setVertex(0, 1, points[1], orange) setVertex(0, 2, points[2], yellow) setVertex(0, 3, points[3], purple) // Row 1 setVertex(1, 0, points[4], pink) setVertex(1, 1, points[5], yellow) setVertex(1, 2, points[6], pink) setVertex(1, 3, points[7], purple) // Row 2 setVertex(2, 0, points[8], indigo) setVertex(2, 1, points[9], pink) setVertex(2, 2, points[10], purple) setVertex(2, 3, points[11], indigo) // Row 3 setVertex(3, 0, points[12], purple) setVertex(3, 1, points[13], indigo) setVertex(3, 2, points[14], pink) setVertex(3, 3, points[15], yellow) } } Box( modifier = modifier.padding(32.dp) .aspectRatio(16 / 9f) .fillMaxWidth() .paint(gradientPainter) // ... )
메시 그라데이션에 애니메이션 적용
MeshGradientPainter의 block 람다 매개변수는 DrawScope 내에서 실행되므로 변경 가능한 상태를 읽고 관찰할 수 있습니다. 셰이더나 비트맵을 다시 할당하지 않고도 시간에 따라 위치나 색상을 애니메이션으로 만들 수 있습니다.
val infiniteTransition = rememberInfiniteTransition(label = "meshMovement") val animatedOffset by infiniteTransition.animateFloat( initialValue = -0.1f, targetValue = 0.1f, animationSpec = infiniteRepeatable( animation = tween(2500, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "offset" ) val coral = Color(255, 90, 90) val peach = Color(255, 139, 90) val amber = Color(255, 169, 90) val sunshine = Color(255, 212, 90) val indigo = Color(0xFF5856D6) val pink = Color(0xFFFF2D55) val gradientPainter = remember { MeshGradientPainter(rows = 3, columns = 3) { // Row 0 setVertex(0, 0, Offset(0.0f, 0.0f), indigo) setVertex(0, 1, Offset(0.3f, 0.0f), peach) setVertex(0, 2, Offset(0.7f, 0.0f), amber) setVertex(0, 3, Offset(1.0f, 0.0f), sunshine) // Row 1 setVertex(1, 0, Offset(0.0f, 0.3f), pink) setVertex(1, 1, Offset(0.2f, 0.4f) + Offset(animatedOffset, animatedOffset), coral) setVertex(1, 2, Offset(0.7f, 0.2f) + Offset(animatedOffset, animatedOffset), peach) setVertex(1, 3, Offset(1.0f, 0.3f), indigo) // Row 2 setVertex(2, 0, Offset(0.0f, 0.7f), coral) setVertex(2, 1, Offset(0.3f, 0.8f) + Offset(animatedOffset, 0f), pink) setVertex(2, 2, Offset(0.7f, 0.6f) + Offset(animatedOffset, 0f), sunshine) setVertex(2, 3, Offset(1.0f, 0.7f), amber) // Row 3 setVertex(3, 0, Offset(0.0f, 1.0f), sunshine) setVertex(3, 1, Offset(0.3f, 1.0f), amber) setVertex(3, 2, Offset(0.7f, 1.0f), pink) setVertex(3, 3, Offset(1.0f, 1.0f), indigo) } } Box( modifier = modifier.padding(32.dp) .safeContentPadding() .aspectRatio(16 / 9f) .fillMaxWidth() .paint(gradientPainter) )