如何在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
相关产品推荐
相关产品推荐

