android Compose 图片星星评分组件

组件介绍

纯 Jetpack Compose 实现的图片版星星评分组件,无自定义绘制、无形状错误、分数精准,支持点击评分、滑动评分、半星显示、自定义样式、只读模式,兼容所有 Compose 正式版。

功能特性

  • ✅ 使用本地星星图片(实心/半星/空心,形状完美)
  • ✅ 精准点击评分 + 流畅滑动评分
  • ✅ 支持半星显示(可开关)
  • ✅ 自定义星星数量、大小、间距
  • ✅ 支持只读模式(仅展示不可交互)
  • ✅ 分数自动限制 0 ~ 最大星星数
  • ✅ 外部传入分数实时同步
  • ✅ 无 toPx() 报错,兼容所有 Compose 版本

前置准备

将 3 张星星图片放入 res/drawable 目录:

  1. ic_star_full:实心星星(选中状态)
  2. ic_star_half:半星(可选)
  3. ic_star_empty:空心星星(未选中状态)

无半星图片时,设置 enableHalfStar = false 即可。

完整代码

kotlin 复制代码
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.ceil
import kotlin.math.roundToInt

// 星星类型枚举
private enum class StarType {
    FULL, HALF, EMPTY
}

/**
 * 图片版精准星星评分组件
 * @param score 当前评分
 * @param stars 总星星数量
 * @param starSize 单个星星大小
 * @param space 星星间距
 * @param enableHalfStar 是否支持半星显示
 * @param onScoreChange 评分变更回调
 * @param enabled 是否可交互(false=只读展示)
 * @param fullStar 实心星星图片资源
 * @param halfStar 半星图片资源
 * @param emptyStar 空心星星图片资源
 */
@Composable
fun StarRatingBar(
    modifier: Modifier = Modifier,
    score: Float = 0f,
    stars: Int = 5,
    starSize: Dp = 28.dp,
    space: Dp = 6.dp,
    enableHalfStar: Boolean = true,
    onScoreChange: (Float) -> Unit = {},
    enabled: Boolean = true,
    fullStar: Int = R.drawable.ic_star_full,
    halfStar: Int = R.drawable.ic_star_half,
    emptyStar: Int = R.drawable.ic_star_empty
) {
    val density = LocalDensity.current
    val singleStarTotalWidth = with(density) { (starSize + space).toPx() }

    // 内部评分状态,同步外部传入值
    var currentScore by remember { mutableFloatStateOf(score.coerceIn(0f, stars.toFloat())) }

    // 监听外部分数变化,自动同步
    LaunchedEffect(score) {
        currentScore = score.coerceIn(0f, stars.toFloat())
    }

    // 精准计算评分
    val calculateScore: (Offset) -> Unit = { offset ->
        val rawScore = offset.x / singleStarTotalWidth
        val newScore = if (enableHalfStar) {
            (rawScore * 2).roundToInt() / 2f
        } else {
            ceil(rawScore)
        }.coerceIn(0f, stars.toFloat())

        currentScore = newScore
        onScoreChange(newScore)
    }

    // 评分交互布局
    Row(
        modifier = modifier
            .pointerInput(enabled) {
                if (enabled) detectTapGestures { calculateScore(it) }
            }
            .pointerInput(enabled) {
                if (enabled) detectDragGestures { change, _ ->
                    calculateScore(change.position)
                }
            },
        horizontalArrangement = Arrangement.spacedBy(space)
    ) {
        for (index in 1..stars) {
            val starType = when {
                currentScore >= index -> StarType.FULL
                currentScore >= index - 0.5f -> StarType.HALF
                else -> StarType.EMPTY
            }

            val icon = when (starType) {
                StarType.FULL -> fullStar
                StarType.HALF -> halfStar
                StarType.EMPTY -> emptyStar
            }

            Image(
                painter = painterResource(id = icon),
                contentDescription = null,
                modifier = Modifier.size(starSize)
            )
        }
    }
}

使用示例

kotlin 复制代码
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun StarRatingDemo() {
    // 评分状态
    var userScore by remember { mutableFloatStateOf(3.5f) }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(20.dp)
    ) {
        // 显示当前分数
        Text(
            text = "当前评分:$userScore 分",
            modifier = Modifier.padding(bottom = 12.dp)
        )

        // 标准评分组件(支持半星)
        StarRatingBar(
            score = userScore,
            onScoreChange = { userScore = it },
            enableHalfStar = true
        )

        // 自定义样式:大星星、红色、只读、无半星
        StarRatingBar(
            modifier = Modifier.padding(top = 20.dp),
            score = 4f,
            starSize = 32.dp,
            enableHalfStar = false,
            enabled = false // 只读模式
        )
    }
}

常用参数说明

参数名 作用 默认值
score 外部传入的当前评分 0f
stars 总星星数量 5
starSize 单个星星大小 28.dp
space 星星之间的间距 6.dp
enableHalfStar 是否开启半星显示 true
enabled 是否可点击/滑动评分 true
onScoreChange 评分变化回调 -
fullStar 实心星星图片 R.drawable.ic_star_full
emptyStar 空心星星图片 R.drawable.ic_star_empty
相关推荐
英俊潇洒美少年2 小时前
react 18 的fiber算法
前端·算法·react.js
清霜之辰3 小时前
车机跨屏交互实战:Android Automotive 跨屏显示与触摸传递实践
android·automotive·mirrordisplay
小鹿软件办公3 小时前
Firefox 149 正式推送:多任务处理升级,五大新功能详解
前端·firefox
evering3 小时前
遍历备份 Chrome | Edge 浏览器插件目录
前端·chrome·crx
WiChP3 小时前
【V0.1B4】从零开始的2D游戏引擎开发之路
前端·javascript·游戏引擎
意法半导体STM323 小时前
【官方原创】STM32CubeProgrammer与STM32 Bootloader连接全流程实战指南 LAT1631
开发语言·前端·javascript·stm32·单片机·嵌入式硬件
薛定谔的悦3 小时前
《储能系统中的故障定位》
java·服务器·前端
慧一居士3 小时前
Vue项目中使用的首选的依赖库如VueUse等,使用场景和使用示例介绍
前端·vue.js
luanma1509803 小时前
PHP vs Java:Web开发终极对决
android