본문 바로가기
안드로이드 코틀린

[Kotlin][Android] Jetpack Compose에서 Modifier 확장 함수로 Shimmer Effect 구현하기

by teamnova 2025. 7. 15.
728x90

안녕하세요.

오늘은 Jetpack Compose 에서 확장함수를 사용해서 Shimmer Effect 를 구현해보겠습니다.

 

1. Shimmer Effect란?

Shimmer Effect는 로딩 상태에서 콘텐츠의 자리를 차지하는 뷰에 빛이 흐르는 듯한 애니메이션을 주어, 사용자가 데이터를 기다릴 때 시각적으로 부드러운 경험을 제공합니다.

대표적으로 리스트, 카드, 이미지 등 다양한 컴포넌트의 플레이스홀더에 활용됩니다.

출처 : https://medium.com/geekculture/shimmer-effect-in-android-2b6840cc0097

 

 

2. Jetpack Compose와 Modifier

Jetpack Compose는 Android의 최신 UI 툴킷으로, 선언형 프로그래밍 방식을 채택하고 있습니다.
이 방식에서는 UI를 함수로 선언하고, 상태(state)에 따라 UI가 자동으로 갱신됩니다.

Modifier

Modifier는 Compose에서 UI 요소(Composable)에 스타일, 레이아웃, 동작, 효과 등 다양한 속성을 부여하는 데 사용되는 객체입니다.
쉽게 말해, Modifier는 컴포넌트의 외형과 동작을 꾸며주는 역할을 합니다.

주요 역할

  • 크기 지정
    • Modifier.size(100.dp)
  • 여백(Margin, Padding) 설정
    • Modifier.padding(16.dp)
  • 배경색/그림자/테두리 등 시각적 효과
    • Modifier.background(Color.Gray)
  • 클릭, 드래그 등 제스처 처리
    • Modifier.clickable { ... }
  • 정렬, 배치
    • Modifier.align(Alignment.Center)

특징

  • 체이닝(Chaining): 여러 Modifier를 '.' 으로 연결해 순차적으로 적용할 수 있습니다.
    • Modifier.padding(8.dp).background(Color.LightGray).size(120.dp)
  • 순서의 중요성: Modifier는 적용 순서에 따라 결과가 달라질 수 있습니다.
  • 재사용성: Modifier를 변수나 함수로 만들어 여러 컴포넌트에 재사용할 수 있습니다.

3. 확장 함수(Extension Function)

Kotlin의 확장 함수는 기존 클래스에 새로운 함수를 추가할 수 있는 기능입니다.

기존 클래스를 상속하거나 수정하지 않고도, 마치 그 클래스의 멤버 함수처럼 사용할 수 있습니다.

 

확장 함수 기본 구조

fun 기존클래스이름.함수이름(파라미터): 반환타입 {
    // 함수 본문
}

 

예를 들어, 아래처럼 String 클래스에 새로운 함수를 추가할 수 있습니다.

fun String.addHello(): String = "Hello, $this"

"World".addHello() // "Hello, World"

Compose에서의 확장 함수 활용

Compose에서는 Modifier에 확장 함수를 정의하여, 아래 예시와 같이 특정 효과나 동작을 한 줄로 손쉽게 재사용할 수 있습니다. 

fun Modifier.roundedBackground(color: Color): Modifier = this.then(
    background(color, shape = RoundedCornerShape(16.dp))
)

// 사용 예시
Modifier.roundedBackground(Color.Red)

 

 

장점

  • 재사용성: 자주 쓰는 효과를 함수로 만들어 여러 곳에서 재사용 가능
  • 가독성: Modifier 체인에 의미 있는 이름을 붙여 코드의 의도를 명확하게 전달
  • 유지보수성: 효과나 동작을 변경할 때 한 곳만 수정하면 됨

 

4. Shimmer Effect 구현 코드

이제 Modifier 확장 함수로 Shimmer Effect를 구현해보겠습니다.

4-1. Shimmer에 사용할 색상 준비

먼저, Shimmer 효과에 사용할 색상 리스트와 애니메이션을 준비하겠습니다. 

val shimmerColors = listOf(
    Color.LightGray.copy(alpha = 0.6f),
    Color.LightGray.copy(alpha = 0.2f),
    Color.LightGray.copy(alpha = 0.6f)
)

 

 

밝고 어두운 회색을 섞어 빛이 흐르는 듯한 느낌을 만듭니다.

4-2. rememberInfiniteTransition와 animateFloat

Shimmer 효과의 핵심은 빛이 계속 움직이는 애니메이션입니다. 이를 위해 Jetpack Compose의 애니메이션 API를 사용합니다.

 

rememberInfiniteTransition

  • 무한 반복되는 애니메이션 상태를 제공하는 Compose 함수입니다.
  • 일반적인 애니메이션은 일정 시간 후 종료되지만, 이 함수로 만든 애니메이션은 계속해서 반복됩니다.
  • Composable 내에서 호출하면, 해당 Composable이 recomposition될 때도 애니메이션 상태를 유지합니다.

animateFloat

  • Float 값이 시간에 따라 변하도록 애니메이션하는 함수입니다.
  • rememberInfiniteTransition과 함께 사용하면, 지정한 시작 값에서 끝 값까지 값을 변화시키고, 이를 반복할 수 있습니다.
// 무한히 반복되는 애니메이션의 상태를 관리
val transition = rememberInfiniteTransition(label = "")

// 0에서 1000까지 1초에 한 번씩 값을 변화 -> 빛이 이동하는 위치를 결정
val translateAnim = transition.animateFloat(
    initialValue = 0f,
    targetValue = 1000f,
    animationSpec = infiniteRepeatable( // 애니메이션 무한 반복
        animation = tween(durationMillis = 1000), // 1초 동안 애니메이션이 진행됨
        repeatMode = RepeatMode.Restart // 애니메이션이 끝까지 도달하면 처음부터 다시 시작
    ),
    label = "Shimmer loading animation"
)

 

4-3. Brush로 그라데이션 효과 만들기

또한 Shimmer 효과의 핵심은 Brush.linearGradient를 활용해 빛이 흐르는 듯한 그라데이션을 만드는 것입니다.

 

Brush는 Jetpack Compose에서 컴포넌트의 배경이나 테두리에 색상을 채우는 방법을 정의하는 객체입니다.

쉽게 말해, Brush는 단색이 아니라 여러 색상을 섞어서(그라데이션) 또는 패턴으로 칠할 수 있게 해줍니다.

  • 단색(Color) 배경은 Modifier.background(Color.Red)처럼 바로 지정
  • Brush를 사용하면 여러 색상이 자연스럽게 이어지는 그라데이션 효과나, 다양한 패턴 효과 적용 가능

Brush의 종류

  • SolidColor: 단일 색상
  • LinearGradient: 직선 방향으로 색상이 변화하는 그라데이션
  • RadialGradient: 원형 방향으로 색상이 변화하는 그라데이션
  • SweepGradient: 시계 방향으로 색상이 도는 그라데이션

이 중 Shimmer Effect에서는 LinearGradient를 주로 사용합니다.

 

LinearGradient와 동작 원리

  • start에서 end까지 지정한 색상 배열(colors)이 직선 방향으로 점진적으로 변하도록 만듭니다.
  • start end의 위치를 바꿔주면, 그라데이션의 방향과 이동 범위를 조절할 수 있습니다.
  • 애니메이션 값이 변하면, 그라데이션의 끝점이 이동하면서 빛이 흐르는 듯한 효과가 만들어집니다.
val brush = Brush.linearGradient(
    colors = shimmerColors, // 앞서 정한 색상 사용
    start = Offset.Zero, // 그라데이션 시작 좌표 (0,0)
    // 그라데이션 끝 좌표, 애니메이션 값에 따라 실시간으로 변함
    end = Offset(x = translateAnim.value, y = translateAnim.value) 
)

4-4. Modifier 확장 함수로 Shimmer Effect 만들기

이제 위에서 준비한 내용을 Modifier 확장 함수로 묶어줍니다.

fun Modifier.shimmerEffect(): Modifier = composed {
    // 위에서 만든 shimmerColors, transition, brush 코드가 이 안에 들어갑니다.
    val shimmerColors = listOf(
        Color.LightGray.copy(alpha = 0.6f),
        Color.LightGray.copy(alpha = 0.2f),
        Color.LightGray.copy(alpha = 0.6f)
    )

    val transition = rememberInfiniteTransition(label = "")
    val translateAnim = transition.animateFloat(
        initialValue = 0f,
        targetValue = 1000f,
        animationSpec = infiniteRepeatable(
            animation = tween(durationMillis = 1000),
            repeatMode = RepeatMode.Restart
        ),
        label = "Shimmer loading animation"
    )

    val brush = Brush.linearGradient(
        colors = shimmerColors,
        start = Offset.Zero,
        end = Offset(x = translateAnim.value, y = translateAnim.value)
    )

    this.background(brush)
}

 

 

실제 적용 예시

 

이제 실제로 Box에 Shimmer Effect를 적용해보겠습니다.

@Composable
fun ShimmerBox() {
    Box(
        modifier = Modifier
            .size(200.dp, 80.dp)
            .shimmerEffect()
    )
}

 

 

위 코드를 실행하면, 밝은 회색 빛이 사선으로 움직이는 애니메이션이 나타나는 것을 확인할 수 있습니다.