You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jetpack Compose中实现带时段高亮的24小时时间区间组件

实现思路

你之前尝试的Slider/RangeSlider本身是为连续滑动区间设计的,不支持跨零点的循环区间渲染;LazyVerticalGrid作为列表布局,很难实现连续区间的无缝拼接、当前时间线分钟级精确定位的需求,直接用Canvas自定义绘制是成本最低、可控性最高的实现方案。

步骤1:基础画布搭建

用Canvas composable占满指定宽高,将画布宽度等分为24份,每份对应1小时区间,先绘制浅灰色的小时分割线,可自行在底部补充0~23的刻度文字。

步骤2:绿色选中区间渲染

先对timeArray做连续区间合并,避免逐小时绘制出现缝隙:示例给出的数组可合并为19~24点、0~4点两个连续区间,按小时值 * 画布总宽度 /24计算每个区间的起止x坐标,直接绘制填充绿色矩形即可实现无缝拼接,自动适配跨零点的区间场景。

步骤3:当前时间红色标记线绘制

将当前时间换算为小时浮点值:比如20点30分换算为20.5f,按上述同样的坐标计算规则得到对应x轴位置,在该位置绘制1~2dp宽的红色竖线,高度和整个时间轴高度一致即可实现分钟级的精确定位。

可选交互扩展

如果需要支持点击/滑动修改选中区间,可给Canvas添加pointerInput修饰符,监听触摸位置换算为对应小时值,修改timeArray后触发重绘即可。


核心实现代码示例

@Composable
fun TimeRangeBar(
    selectedHours: Set<Int>,
    currentTime: Float, // 格式:小时值 + 分钟数/60f,例:20.5f 代表20点30分
    modifier: Modifier = Modifier,
    selectedColor: Color = Color.Green,
    timeLineColor: Color = Color.Red
) {
    Canvas(modifier = modifier.fillMaxWidth().height(48.dp)) {
        val hourWidth = size.width / 24
        // 合并连续选中区间
        val sortedHours = selectedHours.sorted()
        val ranges = mutableListOf<IntRange>()
        if (sortedHours.isNotEmpty()) {
            var start = sortedHours.first()
            var last = start
            sortedHours.drop(1).forEach { hour ->
                if (hour == last + 1) last = hour
                else {
                    ranges.add(start..last)
                    start = hour
                    last = hour
                }
            }
            ranges.add(start..last)
            // 处理跨零点合并
            if (ranges.first().first == 0 && ranges.last().last == 23) {
                val merged = ranges.last().first .. ranges.first().last
                ranges.removeFirst()
                ranges.removeLast()
                ranges.add(merged)
            }
        }
        // 绘制选中绿色区间
        ranges.forEach { range ->
            if (range.first <= range.last) {
                drawRect(
                    color = selectedColor,
                    topLeft = Offset(range.first * hourWidth, 0f),
                    size = Size((range.last + 1 - range.first) * hourWidth, size.height)
                )
            } else {
                // 跨零点区间绘制前后两段
                drawRect(
                    color = selectedColor,
                    topLeft = Offset(range.first * hourWidth, 0f),
                    size = Size((24 - range.first) * hourWidth, size.height)
                )
                drawRect(
                    color = selectedColor,
                    topLeft = Offset(0f, 0f),
                    size = Size((range.last + 1) * hourWidth, size.height)
                )
            }
        }
        // 绘制小时分割线
        for (i in 1 until 24) {
            drawLine(
                color = Color.LightGray,
                start = Offset(i * hourWidth, 0f),
                end = Offset(i * hourWidth, size.height),
                strokeWidth = 1.dp.toPx()
            )
        }
        // 绘制当前时间红色标记线
        drawLine(
            color = timeLineColor,
            start = Offset(currentTime * hourWidth, 0f),
            end = Offset(currentTime * hourWidth, size.height),
            strokeWidth = 2.dp.toPx()
        )
    }
}

// 调用示例
val timeArray = setOf(19,20,21,22,23,0,1,2,3)
// 获取当前时间浮点值示例
val currentTime = Calendar.getInstance().run {
    get(Calendar.HOUR_OF_DAY) + get(Calendar.MINUTE) / 60f
}
TimeRangeBar(
    selectedHours = timeArray,
    currentTime = currentTime,
    modifier = Modifier.padding(16.dp)
)

内容的提问来源于stack exchange,提问作者Jéluchu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 11:09:04