리스트나 화면을 구성하는 동안의 Loading 상태에서 자주 사용되는 Shimmer View. 기존 XML 방식에서는 각종 라이브러리를 활용하여 많이 구현되었습니다.
하지만 Compose에서는 아직 직접 구현이 필요한 듯하여 다른 분들의 코드를 참고하여 구현을 진행했습니다.
먼저 위 이미지와 동일한 형태의 컴포저블을 구현해야합니다.
스켈레톤 효과를 입혀줄 틀을 만들어주는 것입니다.
@Composable
fun PlaceholderItem() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.height(72.dp)
) {
Box(
modifier = Modifier
.size(60.dp)
.shimmerEffect(8.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column(
modifier = Modifier
.weight(1f)
.padding(top = (2.5).dp)
.height(60.dp)
) {
Box(
modifier = Modifier
.height(16.dp)
.fillMaxWidth(0.7f)
.shimmerEffect(4.dp)
)
Spacer(modifier = Modifier.height(2.dp))
Box(
modifier = Modifier
.height(16.dp)
.fillMaxWidth(0.5f)
.shimmerEffect(4.dp)
)
Spacer(modifier = Modifier.height(2.dp))
Box(
modifier = Modifier
.height(16.dp)
.fillMaxWidth(0.3f)
.shimmerEffect(4.dp)
)
}
}
}
이제 컴포저블의 속성을 변경해 줄 Modifier를 기준으로 Shimmer Effect 확장 함수를 만들어줍니다.
composed {}는 Modifier 확장 함수를 구성 가능한 함수로 만들어 줍니다. 이는 상태를 기억하고 재구성할 수 있게 해줍니다.
fun Modifier.shimmerEffect(radius: Dp): Modifier = composed {
var size by remember { mutableStateOf(IntSize.Zero) }
val transition = rememberInfiniteTransition(label = "")
val startOffsetX by transition.animateFloat(
initialValue = -2 * size.width.toFloat(),
targetValue = 2 * size.width.toFloat(),
animationSpec = infiniteRepeatable(
animation = tween(1000)
),
label = ""
)
...
initialValue와 targetValue로 애니메이션 시작 종료 지점을 지정해 주고, 1초 간격으로 무한 반복 동작하도록 구성합니다.
size는 변화하는 컴포저블의 크기를 저장하기 위함입니다.
...
background(
brush = Brush.linearGradient(
colors = listOf(
Color(0xFFEFF0F1),
Color(0xFFE0E0E0),
Color(0xFFEFF0F1)
),
start = Offset(startOffsetX, 0f),
end = Offset(startOffsetX + size.width.toFloat(), size.height.toFloat())
),
shape = RoundedCornerShape(radius)
).onGloballyPositioned { size = it.size }
}
background는 컴포저블의 배경을 설정합니다. 여기서 애니메이션을 적용하고, 스켈레톤 그러데이션 처리 색상을 지정해 줍니다.
Brush.linearGradient로 선형 그러데이션을 지정합니다. 그리고 start end 값을 지정해 줌으로써 대각선 방향으로 애니메이션 처리가 되도록 설정해 줄 수 있습니다.
shape는 필요한 스켈레톤 뷰의 모양에 따라 다르게 구현이 가능합니다. 저 같은 경우에는 모서리가 둥글어야 해서 RoundedCornerShape 활용했습니다.
마지막으로 onGloballyPositioned를 통해 변화하는 컴포저블의 크기를 변수에 저장하여 애니메이션 처리를 진행할 수 있습니다.
활용 시에는 아래처럼 선언하면 되겠습니다.
Spacer(modifier = Modifier.height(2.dp))
Box(
modifier = Modifier
.height(16.dp)
.fillMaxWidth(0.5f)
.shimmerEffect(4.dp)
)