
🧩 문제 상황: Compose에서 Figma gradient가 구현되지 않는다..!
안녕하세요! 오늘은 컴포즈에서 Gradient 구현에 대해 얘기해보려고 해요!
최근 참여중인 프로젝트 Spoony에서 아래와 같은 디자인이 있었어요!

처음에는 단순히 Brush.radialGradient(...)로 해결되기를 제발 바랬어요,,, 하지만 결과는 너무 처참했어요. 그래서 피그마 스펙을 좀더 자세히 뜯어봤습니다. 중심점이 단순한 원처럼 보이지 않았거든요!
border-radius: 40px;
background: radial-gradient(141.42% 141.42% at 100% 0%, #FF5235 54.5%, #FFCEC6 100%);
위의 ActiveTrack의 CSS스펙이에요. 이걸보고 아..단순한 원은 아니구나 했습니다ㅠ
1. Compose의 한계: radial-gradient는 원형만 지원해요
Jetpack Compose의 Brush.radialGradient()는 중심점(center)과 반지름(radius)을 설정하는 원형 그라디언트만 지원해요. 하지만 위의 Figma 스펙은 타원형(Elliptical)이고, 중심이 오른쪽 상단(Top-right)에 위치해요.
| 141.42% 141.42% | 타원의 x축, y축 반지름 비율 → √2 × 100 ≒ 141.42 = 정사각형 대각선 |
| at 100% 0% | 중심점이 오른쪽 상단 (top-right)에 위치 |
CSS의 radial-gradient는 타원형 그라디언트를 지원하지만, Compose의 Brush.radialGradient()는 원형만 지원해요. Compose가 아직 CSS 수준의 복잡한 gradient 표현을 직접 제공하지 않기 때문에 저는 직접 그라디언트를 만들고자 했어요.
2. 직접 그라디언트를 만들자!
원형 radial-gradient를 타원처럼 보이게 만들기
Jetpack Compose는 Canvas 기반의 그래픽스 시스템이기 때문에, Android의 그래픽스 API를 함께 사용할 수 있어요. RadialGradient는 Matrix를 적용해 스케일 변형이 가능하기 때문에 사용해봤어요.
그럼 먼저 원형을 타원형으로 만들어야겠죠? 어떻게 할까요?
- 원형 radial gradient를 먼저 만들고
- y축 방향으로 스케일을 적용해 타원처럼 보이게 처리하자
val scaleY = radiusY / radiusX
matrix.setScale(1f, scaleY, cx, cy)
shader.setLocalMatrix(matrix)
이 방식은 OpenGL이나 벡터 그래픽스에서 흔히 사용하는 기법으로, 실제 UI 프레임워크에도 사용되고 있어요.
3. Modifier 확장함수로 Figma gradient 구현
@Composable
fun Modifier.spoonySliderGradient(
cornerRadius: Dp = 40.dp,
mainColor: Color = Color(0xFFFF5235),
secondColor: Color = Color(0xFFFFCEC6)
) = composed {
this.drawWithCache {
// 라운드 모서리 형태를 위한 Path 생성
val roundPath = Path().apply {
addRoundRect(
RoundRect(
rect = Rect(Offset.Zero, size),
cornerRadius = CornerRadius(cornerRadius.toPx())
)
)
}
// gradient 중심점을 오른쪽 상단으로 지정
val cx = size.width
val cy = 0f
// radial-gradient의 반지름 (x축, y축)을 계산
val radiusX = size.width * 1.4142f // √2 × width
val radiusY = size.height * 1.4142f // √2 × height
// y축 스케일 비율 계산
val scaleY = radiusY / radiusX
// RadialGradient 생성
val shader = RadialGradient(
cx,
cy,
radiusX,
intArrayOf(mainColor.toArgb(), secondColor.toArgb()),
floatArrayOf(0.545f, 1f), // 54.5% ~ 100%
Shader.TileMode.CLAMP
).apply {
// 원형을 y축 방향으로 눌러서 타원처럼 만들기
val matrix = Matrix().apply {
setScale(1f, scaleY, cx, cy)
}
setLocalMatrix(matrix)
}
// Paint 객체 생성 및 shader 바인딩
val paint = Paint().asFrameworkPaint().apply {
isAntiAlias = true
this.shader = shader
}
// 실제 gradient를 그리고 content 위에 출력
onDrawWithContent {
clipPath(roundPath) {
drawIntoCanvas { canvas ->
canvas.nativeCanvas.drawPath(roundPath.asAndroidPath(), paint)
}
}
drawContent()
}
}
}
4. 어떻게 구현한건데..?
먼저 drawWithCache는 Compose에서 제공하는 성능 최적화 도구인데요, 컴포넌트의 크기나 색상이 변하지 않으면 gradient 계산 결과를 캐싱해 재사용해요. 매 프레임마다 비싼 계산을 반복하지 않게 하기위해 사용했어요.
Figma에서 border-radius: 40px 사양이 주어졌기 때문에, 이를 Compose에서 구현하려면 Path를 사용해 모서리가 둥근 사각형 경로를 만들어야 해요. clipPath(...)로 해당 경로를 기준으로 그라디언트를 잘라내야 정확한 외곽 처리를 반영해요.
그리고 setScale(1f, scaleY, cx, cy)는 다음과 같이 동작해요
- x축 스케일: 1.0 (변화 없음)
- y축 스케일: scaleY (압축 또는 확장)
- 변환 중심점: (cx, cy) - 오른쪽 상단
이로 인해 원형 gradient가 y축 방향으로 압축되어 타원형으로 보이게 돼요!
🧪 사용 예시
Box(
modifier = Modifier
.fillMaxWidth(sliderState.value / 100f)
.height(trackHeight)
.then(
// 반지름 0이면 앱 터짐
if ((sliderState.value / 100f) > 0.01f) {
Modifier.spoonySliderGradient()
} else {
Modifier
}
)
.clip(CircleShape)
)
초기의 목적처럼 슬라이더의 ActiveTrack에 적용했어요. .then으로 분기처리를 한 이유는 슬라이더 조정을 통해서 반지름이 0또는 음수가 되는순간 크래시가 나기 때문이에요.. 적용된 모습은 아래와 같아요!


개인적으로 아주 만족스러운 결과물이 나왔습니다ㅠ
그래서 결론.
Figma의 radial-gradient 스펙을 Android Compose에서 어떻게 정확히 해석하고 구현할 수 있는지 작성해보았습니다.
Jetpack Compose는 아직 CSS 수준의 복잡한 gradient 표현을 직접 제공하지는 않지만, Android native API와 수학적 원리를 적절히 활용하면 디자이너의 Figma 스펙을 최대한 반영할 수 있어요. 제 코드가 도움이 되길 바랍니다!
'Android > Compose' 카테고리의 다른 글
| [Android Compose] Effect Handlers 딥다이브 (0) | 2025.06.20 |
|---|---|
| [Android Compose] 상태 읽기 지연(Defer State Reads)으로 리컴포지션 최적화하기 (0) | 2025.06.18 |
| [Android Compose] Figma 그림자를 쉽게 만들어 보자 (0) | 2025.05.14 |
| [Android Compose] TopBar 배경색 전환 애니메이션 구현 방법 (0) | 2025.02.05 |
| [Android Compose] ImePadding 이중 패딩 문제 해결 방법 (+키보드 영역 조정) (7) | 2025.02.05 |