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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 16:02:06