Flutter中平移/缩放InteractiveViewer时如何重绘GridPaper铺满屏幕?
问题描述
我想用Flutter制作类似draw.io的无限画布,用于放置节点和图形。目前在绘制网格背景时,使用了GridPaper和InteractiveViewer组件,但缩放或平移时网格不会扩展重绘。我想了解如何通过CustomPaint或TransformationController实现保持缩放级别下网格铺满屏幕的功能。
现有代码如下:
infinite_canvas.dart
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:aimed_infinite_canvas/src/presentation/widget/background.dart'; class InfiniteCanvas extends ConsumerStatefulWidget { const InfiniteCanvas({ super.key, }); @override ConsumerState<ConsumerStatefulWidget> createState() => _InfiniteCanvasState(); } class _InfiniteCanvasState extends ConsumerState<InfiniteCanvas> { ValueNotifier<double> _valueNotifier = ValueNotifier<double>(0); @override Widget build(BuildContext context) { return Listener( child: LayoutBuilder( builder: (context, constraints) { return InteractiveViewer( boundaryMargin: const EdgeInsets.all(double.infinity), onInteractionStart: (details) {}, onInteractionUpdate: (details) {}, onInteractionEnd: (details) {}, clipBehavior: Clip.none, child: CustomPaint( painter: BackgroundPainter(valueNotifier: _valueNotifier), child: const SizedBox.expand( child: Stack( children: [ Background(), ], ), ), ), ); }, ), ); } } class BackgroundPainter extends CustomPainter { ValueNotifier<double> valueNotifier; BackgroundPainter({required this.valueNotifier}) : super(repaint: valueNotifier) {} @override void paint(Canvas canvas, Size size) { final paint = Paint()..style = PaintingStyle.fill; canvas.drawPaint(paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }
background.dart
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class Background extends ConsumerStatefulWidget { const Background({ super.key, }); @override ConsumerState<ConsumerStatefulWidget> createState() => _BackgroundState(); } class _BackgroundState extends ConsumerState<Background> { @override Widget build(BuildContext context) { return GridPaper( divisions: 2, subdivisions: 2, child: Container( width: double.infinity, height: double.infinity, ), ); } }
解决方案
核心思路是放弃GridPaper,改用CustomPaint结合TransformationController绘制网格。通过跟踪交互状态动态计算网格线位置,确保缩放/平移时网格始终铺满屏幕且无限延伸。
修改后完整代码
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class InfiniteCanvas extends ConsumerStatefulWidget { const InfiniteCanvas({super.key}); @override ConsumerState<InfiniteCanvas> createState() => _InfiniteCanvasState(); } class _InfiniteCanvasState extends ConsumerState<InfiniteCanvas> { // 跟踪InteractiveViewer的缩放和平移状态 final TransformationController _transformController = TransformationController(); @override void dispose() { _transformController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { return InteractiveViewer( transformationController: _transformController, boundaryMargin: const EdgeInsets.all(double.infinity), minScale: 0.1, maxScale: 5.0, clipBehavior: Clip.none, child: CustomPaint( painter: GridBackgroundPainter(transformController: _transformController), child: const SizedBox.expand( child: Stack( // 此处可添加节点、图形等画布内容 ), ), ), ); }, ); } } class GridBackgroundPainter extends CustomPainter { final TransformationController transformController; // 网格自定义参数 final double majorGridSpacing = 80; final int subdivisions = 4; final double minorLineWidth = 0.5; final double majorLineWidth = 1.0; final Color minorLineColor = Colors.grey.shade300; final Color majorLineColor = Colors.grey.shade400; GridBackgroundPainter({required this.transformController}) : super(repaint: transformController); @override void paint(Canvas canvas, Size size) { // 获取当前缩放比例和平移偏移 final scale = transformController.value.getMaxScaleOnAxis(); final offset = transformController.value.getTranslation(); // 计算缩放后的网格间距 final scaledMajorSpacing = majorGridSpacing / scale; final scaledMinorSpacing = scaledMajorSpacing / subdivisions; // 计算画布中心偏移,确保网格从屏幕外开始绘制 final centerX = size.width / 2 + offset.dx / scale; final centerY = size.height / 2 + offset.dy / scale; // 计算覆盖屏幕内外的网格线数量 final horizontalLinesCount = (size.height / scaledMinorSpacing).ceil() + 2; final verticalLinesCount = (size.width / scaledMinorSpacing).ceil() + 2; // 绘制横向网格线 for (int i = -horizontalLinesCount; i < horizontalLinesCount; i++) { final y = centerY + i * scaledMinorSpacing; final isMajorLine = (i % subdivisions) == 0; canvas.drawLine( Offset(-size.width * 2, y), Offset(size.width * 2, y), Paint() ..color = isMajorLine ? majorLineColor : minorLineColor ..strokeWidth = isMajorLine ? majorLineWidth / scale : minorLineWidth / scale, ); } // 绘制纵向网格线 for (int i = -verticalLinesCount; i < verticalLinesCount; i++) { final x = centerX + i * scaledMinorSpacing; final isMajorLine = (i % subdivisions) == 0; canvas.drawLine( Offset(x, -size.height * 2), Offset(x, size.height * 2), Paint() ..color = isMajorLine ? majorLineColor : minorLineColor ..strokeWidth = isMajorLine ? majorLineWidth / scale : minorLineWidth / scale, ); } } @override bool shouldRepaint(covariant GridBackgroundPainter oldDelegate) { return oldDelegate.transformController != transformController; } }
关键细节说明
- TransformationController:实时获取缩放比例和平移偏移,用于动态计算网格线位置。
- 自适应网格:根据缩放比例调整网格间距和线宽,保证缩放时网格视觉大小一致。
- 无限延伸:通过计算超出屏幕范围的网格线数量,确保平移时网格始终覆盖可见区域。
- 性能优化:通过
repaint: transformController仅在交互状态变化时重绘网格,避免不必要的性能消耗。
内容的提问来源于stack exchange,提问作者user23295538
相关产品推荐
相关产品推荐

