Jetpack Compose에서 TopBar 배경색 애니메이션 구현하기

한포도·2024년 12월 3일
0

AND-ANDROID

목록 보기
2/2
post-thumbnail

안녕하세요! 이번에 Jetpack Compose로 TopBar 구현을 하면서 스크롤에 따른 배경색 애니메이션을 적용해보았는데, 그 과정에서 알게 된 내용들을 공유해보려 합니다.

💡 구현 목표

  • 스크롤 전: 투명 배경의 TopBar
  • 보자마자 물음표 20개띄움

  • 스크롤 후: 흰색 배경으로 자연스럽게 전환
  • 배경색 변화에 따른 아이콘 색상 자동 대응

🛠 구현 방법

1. 스크롤 상태 감지하기

val scrollState = rememberLazyListState()
val isScrolledPastImage = scrollState.firstVisibleItemIndex > 0 ||
    scrollState.firstVisibleItemScrollOffset > 0

LazyColumn의 스크롤 상태를 체크해서 이미지 영역을 지나쳤는지 확인합니다.

2. animateColorAsState로 배경색 애니메이션 적용

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가 content 위에 겹쳐서 표시되어야 했는데 제대로 동작하지 않았습니다. Scaffold의 구조상 TopBar와 content가 별도의 slot으로 구분되어 있어 발생한 문제였습니다.

// TopBar는 상위에 위치하도록 zIndex 설정
modifier = Modifier.zIndex(1f)

// 콘텐츠는 하위에 위치하도록 설정
LazyColumn(
    modifier = Modifier.zIndex(0f)
)

2. 아이콘 색상 처리

배경색이 투명에서 흰색으로 변할 때 아이콘이 보이지 않는 구간이 생기는 것을 방지하기 위해, 배경색의 명도를 기준으로 아이콘 색상을 결정했습니다.

// luminance를 기준으로 아이콘 색상 결정
val iconTint = if (backgroundColor.luminance() < 0.5f) Color.White else Color.Black

🔍 animateColorAsState 상세 옵션

기본 파라미터

  • targetValue: 최종 색상 값
  • animationSpec: 애니메이션 동작 방식
  • label: 디버깅용 레이블
  • finishedListener: 애니메이션 완료 시 콜백

animationSpec 옵션들

  1. tween - 시간 기반 애니메이션
tween(
    durationMillis = 800,
    easing = FastOutSlowInEasing
)
  1. spring - 물리 기반 애니메이션
spring(
    dampingRatio = Spring.DampingRatioMediumBouncy,
    stiffness = Spring.StiffnessMedium
)
  1. 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) 이상
profile
응애 개발맨

0개의 댓글