使用Riverpod实现撤销/重做按钮时遭遇void类型错误求助
问题根源
- 错误使用
StateProvider<void>:StateProvider用于存储可观察的有效状态值,但void类型不代表任何实际状态,这本身就是设计错误。即便返回null也无意义,因为void类型无法承载任何值。 - 无意义的按钮逻辑:undo/redo按钮的
onPressed中仅读取ref.read(undoProvider).state,这行代码既没有实现撤销/重做功能,还因读取void类型值触发编译错误。 - 核心功能缺失:当前
DrawingAreaNotifier未维护绘制历史记录,根本不具备撤销/重做的基础能力。
解决方案
1. 重构DrawingAreaNotifier,添加历史管理逻辑
在状态管理类中维护三个列表:当前绘制点列表、撤销历史栈、重做历史栈,同时实现undo()、redo()方法,确保每次操作都更新历史记录。
2. 移除无用的undoProvider和redoProvider
撤销/重做属于绘制状态管理的一部分,无需单独的StateProvider,直接通过drawingAreaProvider.notifier调用对应方法即可。
3. 修改按钮的onPressed逻辑
直接调用DrawingAreaNotifier中的undo()和redo()方法,实现实际功能。
修改后的完整代码
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; final drawingAreaProvider = StateNotifierProvider.autoDispose<DrawingAreaNotifier, List<Offset>>((ref) { return DrawingAreaNotifier(); }); final toggleGridProvider = StateProvider.autoDispose<bool>((ref) => false); class DrawingAreaNotifier extends StateNotifier<List<Offset>> { // 撤销历史栈:存储每次添加新点前的状态 final List<List<Offset>> _undoStack = []; // 重做历史栈:存储撤销操作后被移除的状态 final List<List<Offset>> _redoStack = []; DrawingAreaNotifier() : super([]); void addPoint(Offset point) { // 添加新点前,将当前状态存入撤销栈 _undoStack.add(List.from(state)); // 清空重做栈(新操作会使之前的重做状态失效) _redoStack.clear(); state = [...state, point]; } void clearPoints() { _undoStack.add(List.from(state)); _redoStack.clear(); state = []; } void undo() { if (_undoStack.isNotEmpty) { // 将当前状态存入重做栈 _redoStack.add(List.from(state)); // 恢复到上一个状态 state = _undoStack.removeLast(); } } void redo() { if (_redoStack.isNotEmpty) { // 将当前状态存入撤销栈 _undoStack.add(List.from(state)); // 恢复到之前撤销的状态 state = _redoStack.removeLast(); } } } class DrawingArea extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final points = ref.watch(drawingAreaProvider); return GestureDetector( onPanStart: (details) { final RenderBox renderBox = context.findRenderObject() as RenderBox; final localPosition = renderBox.globalToLocal(details.globalPosition); ref.read(drawingAreaProvider.notifier).addPoint(localPosition); }, onPanUpdate: (details) { final RenderBox renderBox = context.findRenderObject() as RenderBox; final localPosition = renderBox.globalToLocal(details.globalPosition); ref.read(drawingAreaProvider.notifier).addPoint(localPosition); }, onPanEnd: (_) { // Do nothing on pan end }, child: Stack( children: [ CustomPaint( painter: DrawingPainter(points), size: Size.infinite, ), Positioned( top: 20, left: 20, child: Row( children: [ IconButton( icon: const Icon(Icons.undo), onPressed: () { ref.read(drawingAreaProvider.notifier).undo(); }, ), IconButton( icon: const Icon(Icons.redo), onPressed: () { ref.read(drawingAreaProvider.notifier).redo(); }, ), IconButton( icon: const Icon(Icons.grid_on), onPressed: () { ref.read(toggleGridProvider).state = !ref.read(toggleGridProvider).state; }, ), ], ), ), ], ), ); } } class DrawingPainter extends CustomPainter { final List<Offset> points; DrawingPainter(this.points); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.black ..strokeWidth = 2 ..style = PaintingStyle.stroke; if (points.isEmpty) return; final path = Path(); path.moveTo(points.first.dx, points.first.dy); for (int i = 1; i < points.length; i++) { path.lineTo(points[i].dx, points[i].dy); } canvas.drawPath(path, paint); final crossPaint = Paint() ..color = Colors.red ..strokeWidth = 2; for (final point in points) { canvas.drawLine(Offset(point.dx - 5, point.dy), Offset(point.dx + 5, point.dy), crossPaint); canvas.drawLine(Offset(point.dx, point.dy - 5), Offset(point.dx, point.dy + 5), crossPaint); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } }
关键说明
- 历史栈管理:每次添加新点或清空画布时,会将当前状态存入
_undoStack,并清空_redoStack(新操作会使之前的重做状态失效)。 - 撤销逻辑:点击撤销按钮时,若
_undoStack不为空,将当前状态存入_redoStack,再恢复到_undoStack的最后一个状态。 - 重做逻辑:点击重做按钮时,若
_redoStack不为空,将当前状态存入_undoStack,再恢复到_redoStack的最后一个状态。
内容的提问来源于stack exchange,提问作者CodeBuddy
相关产品推荐
相关产品推荐

