Star Rating UI in Compose: Custom Rating Bar Implementation
Building an interactive star rating component is a fundamental UI pattern. Material 3 provides the building blocks to create flexible, accessible rating systems that work across all screen sizes.
Basic Star Rating Component
Create a reusable star rating component using IconButton and Icon:
@Composable
fun StarRating(
rating: Float,
onRatingChanged: (Float) -> Unit,
modifier: Modifier = Modifier
) {
Row(modifier = modifier) {
repeat(5) { index ->
val isFilled = rating > index
IconButton(
onClick = { onRatingChanged(index + 1f) }
) {
Icon(
imageVector = if (isFilled) Icons.Filled.Star else Icons.Outlined.Star,
contentDescription = "Rating ${index + 1}",
tint = MaterialTheme.colorScheme.primary
)
}
}
}
}
Half-Star Support with detectTapGestures
For more granular ratings, implement half-star detection:
@Composable
fun DetailedStarRating(
rating: Float,
onRatingChanged: (Float) -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.pointerInput(Unit) {
detectTapGestures { offset ->
val starWidth = size.width / 5f
val starIndex = offset.x / starWidth
val isHalf = (offset.x % starWidth) < (starWidth / 2f)
val newRating = if (isHalf) {
starIndex.toInt() + 0.5f
} else {
starIndex.toInt() + 1f
}
onRatingChanged(newRating)
}
}
) {
repeat(5) { index ->
val fillPercentage = when {
rating > index + 1 -> 1f
rating > index + 0.5f -> 0.5f
rating > index -> 0f
else -> 0f
}
StarIcon(
fillPercentage = fillPercentage,
modifier = Modifier.weight(1f)
)
}
}
}
@Composable
fun StarIcon(
fillPercentage: Float,
modifier: Modifier = Modifier
) {
if (fillPercentage >= 1f) {
Icon(
Icons.Filled.Star,
contentDescription = null,
modifier = modifier,
tint = MaterialTheme.colorScheme.primary
)
} else if (fillPercentage > 0f) {
Icon(
Icons.Filled.StarHalf,
contentDescription = null,
modifier = modifier,
tint = MaterialTheme.colorScheme.primary
)
} else {
Icon(
Icons.Outlined.Star,
contentDescription = null,
modifier = modifier,
tint = Color.Gray
)
}
}
Read-Only Display with StarHalf
Display existing ratings without interaction:
@Composable
fun RatingDisplay(rating: Float) {
Row {
repeat(5) { index ->
when {
rating > index + 1 -> {
Icon(Icons.Filled.Star, contentDescription = null)
}
rating > index -> {
Icon(Icons.Filled.StarHalf, contentDescription = null)
}
else -> {
Icon(Icons.Outlined.Star, contentDescription = null)
}
}
}
}
}
Review Card Integration
Combine rating with review metadata:
@Composable
fun ReviewCard(
author: String,
rating: Float,
reviewText: String,
onRatingClick: (Float) -> Unit = {}
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = author,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
StarRating(
rating = rating,
onRatingChanged = onRatingClick,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = reviewText,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
Custom Tint Colors
Personalize the rating appearance:
@Composable
fun GradientStarRating(
rating: Float,
onRatingChanged: (Float) -> Unit
) {
val colors = listOf(
Color(0xFFFF6B6B), // Red
Color(0xFFFFA726), // Orange
Color(0xFFFFD93D), // Yellow
Color(0xFF6BCB77), // Green
Color(0xFF4D96FF) // Blue
)
Row {
repeat(5) { index ->
IconButton(onClick = { onRatingChanged(index + 1f) }) {
Icon(
imageVector = if (rating > index) Icons.Filled.Star else Icons.Outlined.Star,
contentDescription = null,
tint = colors[index]
)
}
}
}
}
Pro Tips
- Accessibility: Use descriptive content descriptions for screen readers
- Performance: Memoize rating values to avoid unnecessary recompositions
- UX: Provide visual feedback during tap—consider highlighting stars on touch
- State management: Keep rating state in a parent composable for better control
8 Android App Templates → https://myougatheax.gumroad.com
Top comments (0)