Skip to content

Instantly share code, notes, and snippets.

@notsatria
Created October 28, 2025 08:42
Show Gist options
  • Select an option

  • Save notsatria/ee44dd67724381574055e864d5923046 to your computer and use it in GitHub Desktop.

Select an option

Save notsatria/ee44dd67724381574055e864d5923046 to your computer and use it in GitHub Desktop.
Shimmer Effect Jetpack Compose
private data class UiState(
val isLoading: Boolean = false,
val data: List<String> = emptyList()
)
@Composable
fun ShimmerRoute(modifier: Modifier = Modifier) {
var state by remember { mutableStateOf(UiState()) }
LaunchedEffect(Unit) {
state = state.copy(isLoading = true)
delay(3000L)
state = state.copy(
isLoading = false,
data = List(20) { "User #$it" }
)
}
ShimmerScreen(modifier, state)
}
@Composable
private fun ShimmerScreen(modifier: Modifier = Modifier, state: UiState = UiState()) {
Scaffold(modifier.fillMaxSize()) { padding ->
LazyColumn(
Modifier
.fillMaxSize()
.padding(padding)
) {
if (state.isLoading) {
items(20) {
Row(
Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
Modifier
.size(48.dp)
.clip(CircleShape)
.shimmer()
)
Spacer(Modifier.width(8.dp))
Box(
Modifier
.fillMaxWidth()
.height(40.dp)
.padding(8.dp)
.shimmer()
)
}
Spacer(Modifier.height(8.dp))
}
} else {
items(state.data.size) { index ->
Row(Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)) {
AsyncImage(
model = ImageRequest
.Builder(LocalContext.current)
.data("https://i.pravatar.cc/150?img=${index + 1}")
.crossfade(true)
.build(),
contentDescription = null,
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
)
Spacer(Modifier.width(16.dp))
Text(
text = state.data[index],
modifier = Modifier
.padding(start = 8.dp)
.align(Alignment.CenterVertically)
)
}
Spacer(Modifier.height(8.dp))
}
}
}
}
}
@Preview
@Composable
fun ShimmerScreenPreview(modifier: Modifier = Modifier) {
ShimmerScreen()
}
@Composable
fun Modifier.shimmer(
baseColor: Color = Color(0xFFE0E0E0),
highlightColor: Color = Color(0xFFF5F5F5),
durationMillis: Int = 1000
): Modifier = composed {
val infiniteTransition = rememberInfiniteTransition(label = "shimmer")
val progress by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis, easing = LinearEasing),
repeatMode = RepeatMode.Restart
),
label = "shimmer progress"
)
drawWithCache {
val gradientWidth = size.width / 2
onDrawWithContent {
drawContent()
val startX = -gradientWidth + (size.width + gradientWidth * 2f) * progress
val endX = startX + gradientWidth
val brush = Brush.linearGradient(
colors = listOf(baseColor, highlightColor, baseColor),
start = Offset(x = startX, y = 0f),
end = Offset(x = endX, y = 0f)
)
drawRect(brush = brush, blendMode = BlendMode.SrcIn)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment