DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Star Rating UI in Compose: Custom Rating Bar Implementation

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
                )
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
            )
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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]
                )
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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)