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
相关推荐
安卓程序员_谢伟光26 分钟前
m3颜色定义
android·compose
踩着两条虫35 分钟前
VTJ.PRO的平台介绍与特性
前端·架构·ai编程
光影少年1 小时前
前端工程化升级
前端·javascript·react.js·前端框架
Hello--_--World1 小时前
节流 VS 防抖 相关知识点与面试题
前端·javascript
We་ct1 小时前
AI辅助开发术语体系深度剖析
开发语言·前端·人工智能·ai·ai编程
去伪存真1 小时前
Superpowers 从“调教提示词”转向“构建工程规范”
前端·agent
发现一只大呆瓜1 小时前
深度起底 Vite:从打包流程到插件钩子执行时序的全链路解析
前端·vite
jserTang1 小时前
Claude Code 源码深度解析 - 前言
前端·javascript·后端
麻辣璐璐1 小时前
EditText属性运用之适配RTL语言和LTR语言的输入习惯
android·xml·java·开发语言·安卓
hehelm1 小时前
vector模拟实现
前端·javascript·算法