Search code examples
androidkotlinandroid-jetpack-compose

Jetpack Compose: How to create a rating bar?


I'm trying to implement a rating bar. I refer to https://gist.github.com/vitorprado/0ae4ad60c296aefafba4a157bb165e60 but I don't understand anything from this code. It works but when I use this code the stars don't have rounded corners. I want to implement something like the following :


Solution

  • I made very basic sample for this, it would give the basic idea for creating rating bar with sample border and filled png files.

    @Composable
    private fun RatingBar(
        modifier: Modifier = Modifier,
        rating: Float,
        spaceBetween: Dp = 0.dp
    ) {
    
        val image = ImageBitmap.imageResource(id = R.drawable.star)
        val imageFull = ImageBitmap.imageResource(id = R.drawable.star_full)
    
        val totalCount = 5
    
        val height = LocalDensity.current.run { image.height.toDp() }
        val width = LocalDensity.current.run { image.width.toDp() }
        val space = LocalDensity.current.run { spaceBetween.toPx() }
        val totalWidth = width * totalCount + spaceBetween * (totalCount - 1)
    
    
        Box(
            modifier
                .width(totalWidth)
                .height(height)
                .drawBehind {
                    drawRating(rating, image, imageFull, space)
                })
    }
    
    private fun DrawScope.drawRating(
        rating: Float,
        image: ImageBitmap,
        imageFull: ImageBitmap,
        space: Float
    ) {
    
        val totalCount = 5
    
        val imageWidth = image.width.toFloat()
        val imageHeight = size.height
    
        val reminder = rating - rating.toInt()
        val ratingInt = (rating - reminder).toInt()
    
        for (i in 0 until totalCount) {
    
            val start = imageWidth * i + space * i
    
            drawImage(
                image = image,
                topLeft = Offset(start, 0f)
            )
        }
    
        drawWithLayer {
            for (i in 0 until totalCount) {
                val start = imageWidth * i + space * i
                // Destination
                drawImage(
                    image = imageFull,
                    topLeft = Offset(start, 0f)
                )
            }
    
            val end = imageWidth * totalCount + space * (totalCount - 1)
            val start = rating * imageWidth + ratingInt * space
            val size = end - start
    
            // Source
            drawRect(
                Color.Transparent,
                topLeft = Offset(start, 0f),
                size = Size(size, height = imageHeight),
                blendMode = BlendMode.SrcIn
            )
        }
    }
    
    private fun DrawScope.drawWithLayer(block: DrawScope.() -> Unit) {
        with(drawContext.canvas.nativeCanvas) {
            val checkPoint = saveLayer(null, null)
            block()
            restoreToCount(checkPoint)
        }
    }
    

    Usage

    Column {
        RatingBar(rating = 3.7f, spaceBetween = 3.dp)
        RatingBar(rating = 2.5f, spaceBetween = 2.dp)
        RatingBar(rating = 4.5f, spaceBetween = 2.dp)
        RatingBar(rating = 1.3f, spaceBetween = 4.dp)
    }
    

    Result

    enter image description here

    Also created a library that uses gestures, other png files and vectors as rating items is available here.

    @Composable
    fun RatingBar(
      modifier: Modifier = Modifier,
      rating: Float,
      painterEmpty: Painter,
      painterFilled: Painter,
      tintEmpty: Color? = DefaultColor,
      tintFilled: Color? = null,
      itemSize: Dp = Dp.Unspecified,
      rateChangeMode: RateChangeMode = RateChangeMode.AnimatedChange(),
      gestureMode: GestureMode = GestureMode.DragAndTouch,
      shimmer: Shimmer? = null,
      itemCount: Int = 5,
      space: Dp = 0.dp,
      ratingInterval: RatingInterval = RatingInterval.Unconstrained,
      allowZeroRating: Boolean = true,
      onRatingChangeFinished: ((Float) -> Unit)? = null,
      onRatingChange: (Float) -> Unit
    )
    

    enter image description here