114 lines
3.1 KiB
Markdown
114 lines
3.1 KiB
Markdown
# 待办事项四象限拖拽交互设计
|
||
|
||
## 概述
|
||
|
||
四象限待办页面支持待办项在象限内排序以及跨象限拖拽移动,同时保持与后端的数据同步。
|
||
|
||
## 交互设计
|
||
|
||
### 拖拽状态
|
||
|
||
| 状态 | 视觉反馈 |
|
||
|------|----------|
|
||
| 按住(未拖拽) | 卡片 scale 1.0,轻微阴影 |
|
||
| 拖拽开始 | 卡片 scale 1.03 + 阴影加深,原位置保留半透明占位框 |
|
||
| 拖拽中 | 卡片跟随手指(transform),目标象限边框高亮发光 |
|
||
| 释放-象限内排序 | 卡片平滑移动到新位置(200ms ease-out) |
|
||
| 释放-跨象限移动 | 卡片以 spring 动画弹入目标位置 |
|
||
| 操作完成 | 显示成功 Toast |
|
||
|
||
### 动画参数
|
||
|
||
- **micro-interaction**: 150-300ms
|
||
- **easing**: ease-out 进入,ease-in 退出
|
||
- **spring**: 用于跨象限移动,natural feel
|
||
- **scale feedback**: 0.95-1.05 on press
|
||
- **exit faster than enter**: 退出时长是进入的 60-70%
|
||
|
||
### 防误触
|
||
|
||
- 拖拽启动延迟:100-150ms 确认是长按而非点击
|
||
- 仅在按住并移动超过阈值后启动拖拽
|
||
|
||
## 数据流
|
||
|
||
### 状态管理
|
||
|
||
```
|
||
_QuadrantScreenState
|
||
├── List<TodoResponse> _todos
|
||
├── DragState _dragState (null / dragging)
|
||
└── int? _dragTargetQuadrant (1, 2, 3)
|
||
```
|
||
|
||
### API 交互
|
||
|
||
1. **象限内排序**:调用 `PUT /todos/{id}` 更新 `priority` 和 `sort_order`
|
||
2. **跨象限移动**:调用 `PUT /todos/{id}` 更新 `priority`
|
||
|
||
### 乐观更新
|
||
|
||
- 用户释放后立即更新本地 UI
|
||
- 后端请求失败时回滚 + 显示错误 Toast
|
||
|
||
## 组件结构
|
||
|
||
```
|
||
TodoQuadrantsScreen
|
||
├── _QuadrantDragContainer (LongPressDraggable + DragTarget)
|
||
│ ├── _QuadrantCard (象限容器)
|
||
│ └── _TodoDragItem (可拖拽待办项)
|
||
└── _DragFeedbackWidget (拖拽中的视觉反馈)
|
||
```
|
||
|
||
## 状态定义
|
||
|
||
| 状态 | 描述 |
|
||
|------|------|
|
||
| `idle` | 正常显示 |
|
||
| `dragging` | 正在拖拽某项 |
|
||
| `dragOverQuadrant` | 拖拽到某象限上方 |
|
||
| `reordering` | 正在执行排序动画 |
|
||
|
||
## 优先级定义
|
||
|
||
| 象限 | Priority Value |
|
||
|------|---------------|
|
||
| 重要紧急 | 1 |
|
||
| 紧急不重要 | 3 |
|
||
| 重要不紧急 | 2 |
|
||
|
||
## 视觉规范
|
||
|
||
### 卡片样式
|
||
|
||
- **正常**: `color: AppColors.todoCardBg`, `borderRadius: 14px`
|
||
- **拖拽中**: `opacity: 0.5` 在原位置显示占位
|
||
- **跟随手指**: `scale: 1.03`, `shadow: elevated`
|
||
|
||
### 象限边框高亮
|
||
|
||
- **正常**: `border: 1px solid {quadrantBorderColor}`
|
||
- **dragOver**: `border: 2px solid AppColors.blue400`, `boxShadow: 0 0 12px AppColors.blue200`
|
||
|
||
### 插入指示器
|
||
|
||
- 高度 2px,圆角 1px
|
||
- 颜色:`AppColors.blue500`
|
||
- 位置:两个待办项之间
|
||
|
||
## 错误处理
|
||
|
||
| 场景 | 处理方式 |
|
||
|------|----------|
|
||
| 后端请求失败 | 回滚本地状态,显示错误 Toast |
|
||
| 网络断开 | 显示网络错误提示 |
|
||
| 并发冲突 | 以最新数据为准,提示用户刷新 |
|
||
|
||
## 实现要点
|
||
|
||
1. 使用 Flutter `LongPressDraggable` + `DragTarget` 实现拖拽
|
||
2. 使用 `AnimatedContainer` / `AnimatedPositioned` 实现平滑动画
|
||
3. 乐观更新:先更新 UI,后请求后端
|
||
4. 拖拽反馈使用 `Transform` 而非改变位置,避免 CLS
|