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

基于Jetpack Compose实现Android谷歌日历日视图拖拽拉伸功能遇阻求助

Android谷歌日历日视图拖拽与拉伸实现方案

数据模型设计(核心避坑点)

不要通过槽位索引绑定事件,改用独立事件实体存储起止槽位:

data class CalendarEvent(
    val id: String, // 唯一标识,避免操作混淆
    var startSlot: Int, // 起始槽位索引(0=00:00,1=00:30...47=23:30)
    var endSlot: Int, // 结束槽位索引
    val title: String,
    val color: Int
)

用MutableList<CalendarEvent>维护所有事件,每个事件独立管理自身的时间范围,彻底避免槽位索引导致的事件合并问题。

整体拖拽逻辑(上下移动时间段)

  1. 触摸事件触发时,通过坐标找到当前操作的CalendarEvent,记录其初始startSlot和endSlot。
  2. 根据触摸移动的距离计算偏移的槽位数deltaSlots(比如每移动一个槽位高度,deltaSlots±1)。
  3. 更新事件的起止槽位:
    val newStart = event.startSlot + deltaSlots
    val newEnd = event.endSlot + deltaSlots
    // 边界校验:不能超出当天的时间范围
    if (newStart >= 0 && newEnd <= 47) {
        event.startSlot = newStart
        event.endSlot = newEnd
    }
    
  4. 刷新视图(用DiffUtil优化RecyclerView刷新,避免全量刷新)。

拉伸时长逻辑(解决合并问题的关键)

  1. 区分拉伸区域:触摸点在事件顶部1/4区域时拉伸起始时间,底部1/4区域时拉伸结束时间。
  2. 拉伸时仅修改对应槽位,保持另一槽位不变:
    • 拉伸结束时间(延长/缩短时长):
      val newEndSlot = calculateNewSlotFromTouchY(touchY)
      // 确保时长至少为1个槽位(30分钟)
      if (newEndSlot > event.startSlot && newEndSlot <= 47) {
          event.endSlot = newEndSlot
      }
      
    • 拉伸起始时间:
      val newStartSlot = calculateNewSlotFromTouchY(touchY)
      if (newStartSlot < event.endSlot && newStartSlot >= 0) {
          event.startSlot = newStartSlot
      }
      
  3. 核心注意:拉伸过程中只修改当前操作事件的属性,绝不修改其他事件的起止槽位,从根源避免两个事件被错误合并。

视图渲染逻辑

绘制事件时,根据事件的startSlot和endSlot计算位置和高度:

  • 每个槽位的高度固定(比如80dp),事件顶部坐标 = startSlot * slotHeight
  • 事件高度 = (endSlot - startSlot) * slotHeight

触摸事件处理的细节

  • ACTION_DOWN时,遍历事件列表找到触摸点命中的事件,保存其id作为当前操作目标,后续ACTION_MOVE仅针对该事件。
  • 处理触摸时加入防抖逻辑,避免微小移动触发不必要的槽位更新。

内容的提问来源于stack exchange,提问作者Jahangir Jadi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 08:12:41