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
相关推荐
hai_android21 分钟前
Kotlin 协程作用域源码剖析
android·kotlin
Yeyu1 小时前
Android 画中画(PiP)小窗播放:怎么做、以及它到底是怎么工作的
android
千里马学框架1 小时前
安卓系统性能优化高级实战开发专题--开机,app冷启动优化
android·智能手机·性能优化·framework·性能·开机优化·app冷启动优化
平头哥技术团队1 小时前
Day 16 | 用 ul、ol、li 一个标签,把散资料收成有序的技能清单页
开发语言·前端·html·html5
代码青铜z1 小时前
不会写代码做小程序,模板开发和AI 编程靠谱吗?创业前先看这 5 件事
前端
小傅哥1 小时前
Java + DDD,1:1 复刻 Deepseek Harness 项目
前端·后端·ai编程
律宏阔1 小时前
Android 手机通过 adblib 使用 ADB Wi-Fi 控制 Android 9 开发板
android
律宏阔2 小时前
Android App 里实现开机动画替换
android
积硅步致千里2 小时前
Word 里的 .wmf 其实是 EMF:一次 metafile 转 PNG 排障
前端·后端
mayaairi2 小时前
Vue2 组件通讯(二):ref、自定义事件与provide/inject实战
前端·javascript·vue.js