Jetpack Compose自定义日程布局中实现垂直拖拽时段并修改拖拽表面颜色的问题
Jetpack Compose自定义日程布局中实现垂直拖拽时段并修改拖拽表面颜色的问题
嘿,我看了你遇到的问题——给自定义日程布局加了拖拽后屏幕冻住,没法滚动,还不知道怎么改拖拽时的颜色对吧?咱们一步步来解决:
首先,你当前的核心问题:
你把draggable直接加在了整个DynamicSchedule的Modifier上,这会导致整个布局的所有触摸事件都被拖拽逻辑拦截,自然没法滚动或者处理其他交互了。而且你要拖拽的是单个时段,不是整个日程表,所以得把拖拽逻辑移到每个预约项上才行。
解决方案分步走:
1. 把拖拽逻辑绑定到单个预约项
你需要把draggable从外层的Layout移到每个预约项的Box里,这样只有触摸到具体的时段卡片时才会触发拖拽,不影响其他区域的交互。
2. 实现拖拽时的颜色变化
我们可以用InteractionSource来监听拖拽状态,然后根据是否正在拖拽来修改卡片的背景色。
3. 处理拖拽位移,更新预约时间
拖拽时的delta是像素值,需要把它转换成对应的小时偏移,然后更新ViewModel里的预约数据,让布局自动重组更新位置。
修改后的代码示例:
首先,调整DynamicSchedule里的内容部分:
@Composable fun DynamicSchedule( viewModel: CalenderViewModel, modifier: Modifier = Modifier, appointmentContent: @Composable (appointment: Appointment, isDragging: Boolean) -> Unit = { appointment, isDragging -> ScheduleCard( appointment = appointment, backgroundColor = if (isDragging) Color.LightBlue // 拖拽时的颜色 else MaterialTheme.colors.surface // 默认颜色 ) }, minDate: LocalDate, maxDate: LocalDate, dayWidth: Dp, hourHeight: Dp ) { val numDays = ChronoUnit.DAYS.between(minDate, maxDate).toInt() + 1 val dividerColor = if (MaterialTheme.colors.isLight) Color.LightGray else Color.DarkGray // 移除外层的draggable,移到单个项上 Layout( content = { viewModel.state.value.appointmentList.sortedBy { it.startDate } .forEach { appointment -> val interactionSource = remember { MutableInteractionSource() } val isDragging = interactionSource.collectIsDraggingAsState().value Box( modifier = Modifier .appointmentData(appointment) .draggable( orientation = Orientation.Vertical, interactionSource = interactionSource, state = rememberDraggableState { delta -> // 把像素delta转换成小时偏移 val hourOffset = delta / hourHeight.toPx() // 计算新的开始和结束时间 val newStartTime = appointment.startDate.time.plusHours(hourOffset.toLong()) val newEndTime = appointment.endDate.time.plusHours(hourOffset.toLong()) // 这里可以加边界判断,比如不能拖到0点前或24点后 if (newStartTime >= LocalTime.MIN && newEndTime <= LocalTime.MAX) { viewModel.updateAppointmentTime(appointment.id, newStartTime, newEndTime) } } ) ) { appointmentContent(appointment, isDragging) } } }, modifier = modifier .drawBehind { // 保持原来的划线逻辑不变 repeat(23) { drawLine( dividerColor, start = Offset(0f, (it + 1) * hourHeight.toPx()), end = Offset(size.width, (it + 1) * hourHeight.toPx()), strokeWidth = 1.dp.toPx() ) } repeat(numDays - 1) { drawLine( dividerColor, start = Offset((it + 1) * dayWidth.toPx(), 0f), end = Offset((it + 1) * dayWidth.toPx(), size.height), strokeWidth = 1.dp.toPx() ) } } .pointerInput(Unit) { // 保持原来的点击逻辑不变 detectTapGestures { val x = it.x.toDp() val y = it.y.toDp() val time = y.value.toInt() / hourHeight.value val date = (x.value.toInt() / dayWidth.value) println("X: $x, Y: $y") println("Day: $date, Time: $time") } }, ) { measurables, constraints -> // 保持原来的布局测量逻辑不变 val height = hourHeight.roundToPx() * 24 val width = dayWidth.roundToPx() * numDays val placeablesWithAppointment = measurables.map { measurable -> val appointment = measurable.parentData as Appointment val appointmentDurationInMinutes = ChronoUnit.MINUTES.between( appointment.startDate.time.toJavaLocalTime(), appointment.endDate.time.toJavaLocalTime() ) val appointmentHeight = ((appointmentDurationInMinutes / 60f) * hourHeight.toPx()).roundToInt() val placeable = measurable.measure( constraints.copy( minWidth = dayWidth.roundToPx(), maxWidth = dayWidth.roundToPx(), minHeight = appointmentHeight, maxHeight = appointmentHeight ) ) Pair(placeable, appointment) } layout(width, height) { placeablesWithAppointment.forEach { (placeable, appointment) -> val appointmentOffsetMinutes = ChronoUnit.MINUTES.between( LocalTime.MIN, appointment.startDate.time.toJavaLocalTime() ) val appointmentY = ((appointmentOffsetMinutes / 60f) * hourHeight.toPx()).roundToInt() val appointmentOffsetDays = ChronoUnit.DAYS.between( minDate, appointment.startDate.date.toJavaLocalDate() ).toInt() val appointmentX = appointmentOffsetDays * dayWidth.roundToPx() placeable.place(appointmentX, appointmentY) } } } }
然后,你需要在CalenderViewModel里添加更新预约时间的方法,比如:
fun updateAppointmentTime(appointmentId: String, newStartTime: LocalTime, newEndTime: LocalTime) { val updatedList = state.value.appointmentList.map { appointment -> if (appointment.id == appointmentId) { appointment.copy( startDate = appointment.startDate.copy(time = newStartTime), endDate = appointment.endDate.copy(time = newEndTime) ) } else { appointment } } state.value = state.value.copy(appointmentList = updatedList) }
关键点说明:
- 拖拽范围缩小到单个项:现在只有点击/触摸到预约卡片时才会触发拖拽,整个日程表的其他区域(比如空白处)可以正常响应点击和滚动(如果你的外层有
scrollable或verticalScroll的话)。 - 拖拽状态监听:用
interactionSource.collectIsDraggingAsState()来获取当前是否正在拖拽,动态修改卡片颜色。 - 位移转换与边界控制:把像素偏移转换成小时偏移,并且限制时间在0-24点之间,避免出现不合理的时段。
这样修改后,你的屏幕不会再冻结,而且拖拽时段时会自动改变颜色,同时拖拽结束后预约的位置也会更新。
备注:内容来源于stack exchange,提问作者EliodeBeirut
相关产品推荐
相关产品推荐

