안녕하세요! Android 개발자 한민재 입니다. 오늘은 SOPT에서 진행한 합동세미나중, Jetpack Compose로 TopBar에 스크롤 애니메이션을 적용해보면서 알게 된 내용들을 공유하려 해요.
💡 구현 목표
- 스크롤 전에는 투명 배경의 TopBar예요
- 스크롤 후에는 흰색 배경으로 자연스럽게 전환돼요
- 배경색 변화에 따라 아이콘 색상이 자동으로 대응해요
🛠 구현 방법
비교적 과거에 작성한 코드니 부족한 부분은 눈감아 주세요ㅠ
1. 스크롤 상태 감지하기
val scrollState = rememberLazyListState()
val isScrolledPastImage = scrollState.firstVisibleItemIndex > 0 ||
scrollState.firstVisibleItemScrollOffset > 0
LazyColumn의 스크롤 상태를 확인해서 이미지 영역을 지났는지 체크해요.
2. 배경색 애니메이션 적용하기
val backgroundColor by animateColorAsState(
targetValue = if (isScrolledPastImage) Color.White else Color.White.copy(alpha = 0f),
animationSpec = tween(durationMillis = 800),
label = "배경색 애니메이션"
)
3. TopBar 컴포넌트 만들기
@Composable
fun ProductTopBar(
onBackClick: () -> Unit,
onHomeClick: () -> Unit,
backgroundColor: Color,
modifier: Modifier = Modifier
) {
val iconTint = if (backgroundColor.luminance() < 0.5f) Color.White else Color.Black
Row(
modifier = modifier
.zIndex(1f)
.fillMaxWidth()
.background(backgroundColor)
.padding(top = 45.dp, start = 16.dp, end = 16.dp, bottom = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// TopBar 내용
}
}
🚀 적용된 모습
⚠️ 구현하면서 만난 이슈들
1. z-index 관련 문제
Scaffold 안에서 TopBar와 content 영역이 있을 때, TopBar가 위에 겹쳐 보여야 했는데 잘 되지 않았어요. Scaffold의 구조상 TopBar와 content가 따로 구분되어 있어서 생긴 문제예요.
// TopBar는 위에 보이도록 zIndex를 설정해요
modifier = Modifier.zIndex(1f)
// 콘텐츠는 아래에 보이도록 설정해요
LazyColumn(
modifier = Modifier.zIndex(0f)
)
2. 아이콘 색상 처리하기
배경이 투명에서 흰색으로 바뀔 때 아이콘이 안 보이는 구간이 생기지 않도록, 배경색의 밝기를 기준으로 아이콘 색상을 정했어요.
val iconTint = if (backgroundColor.luminance() < 0.5f) Color.White else Color.Black
🔍 animateColorAsState 옵션 살펴보기
기본 파라미터
- targetValue: 최종 색상 값이에요
- animationSpec: 애니메이션 동작 방식이에요
- label: 디버깅용 레이블이에요
- finishedListener: 애니메이션 끝났을 때 실행될 동작이에요
animationSpec 종류
- tween - 시간 기준 애니메이션이에요
tween(
durationMillis = 800,
easing = FastOutSlowInEasing
)
- spring - 물리 기반 애니메이션이에요
spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessMedium
)
- keyframes - 구간별 애니메이션이에요
keyframes {
durationMillis = 1000
Color.White.copy(alpha = 0f) at 0
Color.White.copy(alpha = 0.5f) at 500
Color.White.copy(alpha = 1f) at 1000
}
💫 더 개선할 수 있는 부분들
1. alpha 값 활용하기
val backgroundAlpha = if (isScrolledPastImage) 1f else 0f
val backgroundColor by animateColorAsState(
targetValue = Color.White.copy(alpha = backgroundAlpha)
)
2. Palette API 활용하기
배경 이미지의 색상을 가져와서 더 자연스러운 색상 전환을 만들 수 있어요.
val palette = Palette.from(bitmap).generate()
val dominantColor = palette.getDominantColor(Color.White)
그렇다면 이미지 영역을 벗어났을때는 어떻게 해야할까요? 이건 추후에 구현하겠습니다..
참고사항
- Jetpack Compose 버전은 1.0.0 이상
- 최소 SDK 버전은 Android 5.0 (API level 21) 이상
'Android > Compose' 카테고리의 다른 글
[Android Compose] Effect Handlers 딥다이브 (2) | 2025.06.20 |
---|---|
[Android Compose] 상태 읽기 지연(Defer State Reads)으로 리컴포지션 최적화하기 (0) | 2025.06.18 |
[Android Compose] Figma의 타원형 Radial Gradient를 구현해보자 (0) | 2025.06.06 |
[Android Compose] Figma 그림자를 쉽게 만들어 보자 (1) | 2025.05.14 |
[Android Compose] ImePadding 이중 패딩 문제 해결 방법 (+키보드 영역 조정) (6) | 2025.02.05 |