Skip to content

Instantly share code, notes, and snippets.

@JorgeCastilloPrz
Last active August 29, 2021 23:43
Show Gist options
  • Save JorgeCastilloPrz/1813e322acc2e153a8e7869cd969a032 to your computer and use it in GitHub Desktop.
Save JorgeCastilloPrz/1813e322acc2e153a8e7869cd969a032 to your computer and use it in GitHub Desktop.
Composable to show rating stars. Noninteractive.
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
/**
* Composable to render 5 stars followed by the total number of votes.
*/
@Composable
fun Rating(rating: Float, votes: Long, modifier: Modifier = Modifier) {
val ratingOverFive = rating / 2 // it comes over 10 from the API
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
RatingStars(ratingOverFive)
Text(
modifier = Modifier.padding(start = 4.dp),
text = "($votes)",
color = warnings,
style = MaterialTheme.typography.subtitle2.copy(fontWeight = FontWeight.Black)
)
}
}
@Composable
private fun RatingStars(ratingOverFive: Float) {
val maxWidth = with(LocalDensity.current) { 100.dp.toPx() }.toInt()
val maxHeight = with(LocalDensity.current) { 20.dp.toPx() }.toInt()
Layout(
content = {
(1..5).forEach { position ->
Image(
modifier = Modifier.preferredSize(20.dp),
painter = starPainterFor(position, ratingOverFive),
contentDescription = null,
colorFilter = ColorFilter.tint(warnings)
)
}
}
) { measurables, constraints ->
// Measure children with given constraints
// Measure each children to get a placeable back
val placeables = measurables.map { measurable ->
// Measure each children
val starConstraints = constraints.copy(maxWidth = constraints.maxWidth / 5)
measurable.measure(starConstraints)
}
// Set the size of the layout as big as it can
layout(maxWidth, maxHeight) {
var xPos = 0
// Place the stars
placeables.forEach { placeable ->
placeable.placeRelative(x = xPos, y = 0)
xPos += placeable.width
}
}
}
}
@Composable
private fun starPainterFor(position: Int, ratingOverFive: Float): Painter =
when {
ratingOverFive >= position -> {
painterResource(id = R.drawable.ic_star_full)
}
ratingOverFive >= position - 0.5f -> {
painterResource(id = R.drawable.ic_star_half)
}
else -> {
painterResource(id = R.drawable.ic_star_empty)
}
}
@Preview
@Composable
fun RatingPreview() {
Rating(4.5f, 1257)
}
@ggrell
Copy link

ggrell commented Feb 21, 2021

🤔 I haven't played around much with Compose yet, but it seems overly complex so far compared to SwiftUI. Here's the equivalent, roughly following the example code above:

import SwiftUI

struct Rating: View {
    var rating: Float
    var votes: Int

    var body: some View {
        HStack {
            RatingStars(rating: rating)
            Text("(\(votes))")
                .font(.subheadline)
                .bold()
        }
    }
}

struct RatingStars: View {
    var rating: Float

    var body: some View {
        HStack {
            ForEach(1...5, id: \.self) { position in
                if rating >= Float(position) {
                    Image(systemName: "star.fill")
                } else if rating >= Float(position) - 0.5 {
                    Image(systemName: "star.leadinghalf.fill")
                } else {
                    Image(systemName: "star")
                }
            }
        }
    }
}

struct Rating_Previews: PreviewProvider {
    static var previews: some View {
        Rating(rating: 4.5, votes: 1257)
            .padding()
            .previewLayout(.sizeThatFits)
    }
}

@JorgeCastilloPrz
Copy link
Author

That is because you’re comparing different approaches. In Compose I could also have used a Row instead of writing my own custom Layout which is a bit more low level. I think it would look almost equal than your example 🙂

No specific reason for not doing so tho

@ggrell
Copy link

ggrell commented Feb 21, 2021

That's good to hear; I didn't have the larger context of the gist. Thanks for the explanation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment