
Hi, Developer in this jetpack composes I create an example. Jetpack composes a custom rating bar in android. Jetpack Compose is a great new declarative UI tool for Android that allows UI creation using Kotlin as a replacement for the cumbersome XML layouts.
This article will provide a straightforward demonstration of Jetpack Compose inside the context of a project. It also explains how to build a review rating bar in android.
Jetpack compose custom rating bar Example.
So let’s start to build follow below easy step. First, create a jetpack compose project and build it. To make a brand new app, you need to open Android Studio, select the File and then Create New A New Project In the wizard choose the empty Compose Activity. After that select Finish and a brand fresh Jetpack Compose project will be created.
After that add dependency in your Gradle build project.
Jetpack compose custom rating bar Souce code.
Here you can find source code and steps for making customer jetpack compose rating bar in android.
Gradle build Dependencies
dependencies { implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' implementation 'androidx.activity:activity-compose:1.4.0' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" }
After adding Dependencies syn your project and after Successfully syn and follow below step
Step 1 Create a Kotlin Class Name CustomerRatingBar.
now make a new Kotlin call for customization rating bar UI
import android.view.MotionEvent import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.layout.* import androidx.compose.runtime.* import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.consumeAllChanges import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.SemanticsPropertyKey import androidx.compose.ui.semantics.SemanticsPropertyReceiver import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.* import com.codeplayon.ratingbar.ratingbar.RatingBarUtils.stepSized sealed class StepSize { object ONE : StepSize() } sealed class RatingBarStyle { object Normal : RatingBarStyle() object HighLighted : RatingBarStyle() } val StarRatingKey = SemanticsPropertyKey<Float>("StarRating") var SemanticsPropertyReceiver.starRating by StarRatingKey @OptIn(ExperimentalComposeUiApi::class) @Composable fun CustomRatingBar( value: Float, modifier: Modifier = Modifier, config: RatingBarConfig = RatingBarConfig(), onValueChange: (Float) -> Unit, onRatingChanged: (Float) -> Unit ) { var rowSize by remember { mutableStateOf(Size.Zero) } var lastDraggedValue by remember { mutableStateOf(0f) } val direction = LocalLayoutDirection.current Row(modifier = modifier .onSizeChanged { rowSize = it.toSize() } .pointerInput( Unit ) { detectHorizontalDragGestures( onDragEnd = { if (config.isIndicator || config.hideInactiveStars) return@detectHorizontalDragGestures onRatingChanged(lastDraggedValue) }, onDragCancel = {}, onDragStart = {}, onHorizontalDrag = { change, _ -> if (config.isIndicator || config.hideInactiveStars) return@detectHorizontalDragGestures change.consumeAllChanges() val x1 = change.position.x.coerceIn(0f, rowSize.width) val calculatedStars = RatingBarUtils.calculateStars( x1, rowSize.width, config.numStars, config.padding.value.toInt() ) var newValue = calculatedStars .stepSized(config.stepSize) .coerceIn(0f, config.numStars.toFloat()) if (direction == LayoutDirection.Rtl) newValue = config.numStars - newValue onValueChange(newValue) lastDraggedValue = newValue } ) } .pointerInteropFilter { if (config.isIndicator || config.hideInactiveStars) return@pointerInteropFilter false when (it.action) { MotionEvent.ACTION_DOWN -> { //handling when click events val calculatedStars = RatingBarUtils.calculateStars( it.x, rowSize.width, config.numStars, config.padding.value.toInt() ) var newValue = calculatedStars .stepSized(config.stepSize) .coerceIn(0f, config.numStars.toFloat()) if (direction == LayoutDirection.Rtl) newValue = config.numStars - newValue onValueChange(newValue) onRatingChanged(newValue) } } true }) { ComposeStars(value, config) } } @Composable fun ComposeStars( value: Float, config: RatingBarConfig ) { val ratingPerStar = 1f var remainingRating = value Row(modifier = Modifier .semantics { starRating = value }) { for (i in 1..config.numStars) { val starRating = when { remainingRating == 0f -> { 0f } remainingRating >= ratingPerStar -> { remainingRating -= ratingPerStar 1f } else -> { val fraction = remainingRating / ratingPerStar remainingRating = 0f fraction } } if (config.hideInactiveStars && starRating == 0.0f) break RatingStar( fraction = starRating, config = config, modifier = Modifier .padding( start = if (i > 1) config.padding else 0.dp, end = if (i < config.numStars) config.padding else 0.dp ) .size(size = config.size) .testTag("RatingStar") ) } } }
Step 2 Make a New Kotlin call FractionalRectangleShape
import androidx.annotation.FloatRange import androidx.compose.runtime.Stable import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection @Stable class FractionalRectangleShape( @FloatRange(from = 0.0, to = 1.0) private val startFraction: Float, @FloatRange(from = 0.0, to = 1.0) private val endFraction: Float ) : Shape { override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline { return Outline.Rectangle( Rect( left = (startFraction * size.width).coerceAtMost(size.width - 1f), top = 0f, right = (endFraction * size.width).coerceAtLeast(1f), bottom = size.height ) ) } }
Step 3 Make a New Kotlin Class PathExtension.
import androidx.annotation.FloatRange import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Path import kotlin.math.PI import kotlin.math.cos import kotlin.math.sin fun Path.addStar( size: Size, spikes: Int = 5, @FloatRange(from = 0.0, to = 0.5) outerRadiusFraction: Float = 0.5f, @FloatRange(from = 0.0, to = 0.5) innerRadiusFraction: Float = 0.2f ): Path { val outRadius = size.minDimension * outerRadiusFraction val innerRadius = size.minDimension * innerRadiusFraction val centerX = size.width / 2 val centerY = size.height / 2 var totalAngle = PI / 2 val degreesPerSection = (2 * PI) / spikes moveTo(centerX, 0f) var x: Double var y: Double for (i in 1..spikes) { totalAngle += degreesPerSection / 2 x = centerX + cos(totalAngle) * innerRadius y = centerY - sin(totalAngle) * innerRadius lineTo(x.toFloat(), y.toFloat()) totalAngle += degreesPerSection / 2 x = centerX + cos(totalAngle) * outRadius y = centerY - sin(totalAngle) * outRadius lineTo(x.toFloat(), y.toFloat()) } close() return this }
Step: 4 Make RatingBarConfig Kotlin class
import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp class RatingBarConfig { var size: Dp = 50.dp private set var padding: Dp = 2.dp private set var style: RatingBarStyle = RatingBarStyle.Normal private set var numStars: Int = 5 private set var isIndicator: Boolean = false private set var activeColor: Color = Color.Green private set var inactiveColor: Color = Color.Green.copy(alpha = 0.5f) private set var stepSize: StepSize = StepSize.ONE private set var hideInactiveStars: Boolean = false private set fun style(value: RatingBarStyle): RatingBarConfig = apply { style = value } }
Step 5: Create a RatingBarUtils Kotlin class
import kotlin.math.roundToInt object RatingBarUtils { fun calculateStars( draggedWidth: Float, width: Float, numStars: Int, padding: Int ): Float { var overAllComposeWidth = width val spacerWidth = numStars * (2 * padding) overAllComposeWidth -= spacerWidth return if (draggedWidth != 0f) ((draggedWidth / overAllComposeWidth) * numStars) else 0f } fun Float.stepSized(stepSize: StepSize): Float { return if (stepSize is StepSize.ONE) this.roundToInt().toFloat() else { var value = this.toInt().toFloat() if (this < value.plus(0.5)) { if (this == 0f) return 0f value = value.plus(0.5).toFloat() value } else { this.roundToInt().toFloat() } } } }
Step 6 Create a Kotlin file With the Name RatingStar
Create a RatingStar Kotlin class and make a UI for Start and its stage like empty, fill, half fill clear start UI for customization on start UI.
import androidx.annotation.FloatRange import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.Fill import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.LayoutDirection private const val strokeWidth = 1f @Composable fun RatingStar( @FloatRange(from = 0.0, to = 1.0) fraction: Float, config: RatingBarConfig, modifier: Modifier = Modifier, ) { val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl Box(modifier = modifier) { FilledStar(fraction, config.activeColor, isRtl) EmptyStar(fraction, config, isRtl) } } @Composable private fun FilledStar(fraction: Float, activeColor: Color, isRtl: Boolean) = Canvas( modifier = Modifier .fillMaxSize() .clip( if (isRtl) rtlFilledStarFractionalShape(fraction = fraction) else FractionalRectangleShape(0f, fraction) ) ) { val path = Path().addStar(size) drawPath(path, color = activeColor, style = Fill) // Filled Star drawPath(path, color = activeColor, style = Stroke(width = strokeWidth)) // Border } @Composable private fun EmptyStar( fraction: Float, config: RatingBarConfig, isRtl: Boolean ) = Canvas( modifier = Modifier .fillMaxSize() .clip( if (isRtl) rtlEmptyStarFractionalShape(fraction = fraction) else FractionalRectangleShape(fraction, 1f) ) ) { val path = Path().addStar(size) if (config.style is RatingBarStyle.Normal) drawPath(path, color = config.inactiveColor, style = Fill) else drawPath(path, color = Color.Gray, style = Stroke(width = strokeWidth)) } fun rtlEmptyStarFractionalShape(fraction: Float): FractionalRectangleShape { return if (fraction == 1f || fraction == 0f) FractionalRectangleShape(fraction, 1f) else FractionalRectangleShape(0f, 1f - fraction) } fun rtlFilledStarFractionalShape(fraction: Float): FractionalRectangleShape { return if (fraction == 0f || fraction == 1f) FractionalRectangleShape(0f, fraction) else FractionalRectangleShape(1f - fraction, 1f) }
Step 7 Final Open Your MainActivity Class.
Final step let’s conclude the above step and make a start rating bar in jetpack compose. See below the full source code of the main class. And Final UI on the Show screen to users with the tab bar title and rating bar on the screen.
import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.codeplayon.ratingbar.MainActivity.Companion.initialRating import com.codeplayon.ratingbar.ratingbar.CustomRatingBar import com.codeplayon.ratingbar.ratingbar.RatingBarConfig import com.codeplayon.ratingbar.ratingbar.RatingBarStyle import com.codeplayon.ratingbar.ui.theme.RatingBarTheme class MainActivity : ComponentActivity() { companion object { var initialRating = 1.5f } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { RatingBarTheme { Surface(color = MaterialTheme.colors.background) { Scaffold( topBar = { TopAppBar( title = { Text( text = "Jetpack Compose Rating Bar", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Left ) } ) } ) { RatingBarView() } } } } } } @Composable fun RatingBarView() { var rating: Float by rememberSaveable { mutableStateOf(initialRating) } Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text( text = "Current Rating Bar Value: $rating", fontWeight = FontWeight.Bold, fontSize = 20.sp ) Spacer(modifier = Modifier.height(30.dp)) CustomRatingBar( value = rating, onValueChange = { rating = it }, onRatingChanged = { Log.d("Rating Value", "RatingBarView: $it") }, config = RatingBarConfig() .style(RatingBarStyle.HighLighted) ) } }
Final Run your project and see the output on the screen.
Read More Tutorial
- How to Convert Speech To Text in Jetpack Compose
- bottom sheet android jetpack compose
- Jetpack Compose Ticket ZigZag View
- Bluetooth 5 and its uses in IoT explained
- How to Generate PDF Files using Jetpack Compose
- Recognize Text using ML Kit Android Jetpack Compose
- Bluetooth Thermal Receipt Printer Android Integration Printer
- Jetpack Compose Tutorial
- Android Tutorial
- Flutter Tutorial