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

Flutter自定义绘制大量TextSpan时缩放性能低下的优化咨询

优化CustomPainter绘制大量TextSpan的缩放性能

你遇到的这个问题太典型了——当在CustomPainter里批量绘制文本元素时,缩放触发的频繁重绘会因为重复创建、布局文本对象直接拖垮性能。结合你的代码,我整理了几个针对性的优化方案:

1. 缓存TextPainter实例,避免重复创建与布局

目前你在paint方法的循环里每次都新建TextSpan和TextPainter,还反复调用layout()——这是最大的性能杀手。我们可以提前创建并缓存这些对象,只在必要时更新:

class CustomPaintSpans extends CustomPainter {
  // 缓存每个数字对应的TextPainter
  final Map<int, TextPainter> _textPainterCache = {};

  CustomPaintSpans() {
    // 初始化时一次性完成所有TextPainter的创建与布局
    for (int i = 0; i < 100; ++i) {
      final textSpan = TextSpan(
        text: "$i",
        style: TextStyle(fontSize: 10, color: Colors.grey, fontWeight: FontWeight.w800)
      );
      final tp = TextPainter(
        text: textSpan,
        textAlign: TextAlign.center,
        textDirection: TextDirection.ltr
      );
      tp.layout();
      _textPainterCache[i] = tp;
    }
  }

  @override
  void paint(Canvas canvas, Size size) {
    double delta = 10.0;
    double pixelSize = 10.0;
    for (int j = 0; j < 100; ++j) {
      for (int i = 0; i < 100; ++i) {
        double startX = delta + i * (pixelSize + 1);
        double startY = delta + j * (pixelSize + 1);
        // 直接从缓存取已完成布局的TextPainter
        final tp = _textPainterCache[i]!;
        final offset = Offset(startX - pixelSize * 0.5, startY - pixelSize * 0.5);
        tp.paint(canvas, offset);
      }
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

这里我把textScaleFactor:10直接合并到fontSize:10里,减少了额外的缩放计算开销;同时把创建和layout操作移到构造函数里,paint阶段只做纯绘制操作,避免了重复劳动。

2. 根据缩放级别动态控制绘制范围与数量

当缩放比例很低时,很多TextSpan会重叠在一起,用户根本看不到细节。这时候我们可以:

  • 只绘制当前视口内的元素(需要获取GestureTransformable的缩放后视口信息)
  • 根据缩放比例跳过部分元素,比如缩放小于0.5时,只绘制每隔5个的TextSpan

示例思路(需结合缩放状态):

// 假设你能从GestureTransformable获取当前缩放比例scale
double scale = ...; 
int step = scale < 0.5 ? 5 : 1; // 缩放小时跳着绘制,减少数量

for (int j = 0; j < 100; j += step) {
  for (int i = 0; i < 100; i += step) {
    // 绘制逻辑...
  }
}

这种方式能大幅减少低缩放级别下的绘制量,性能提升非常明显。

3. 使用PictureRecorder预渲染静态内容

如果这些TextSpan是完全静态的(不会动态更新内容或样式),可以用PictureRecorder把所有文本绘制操作预录成Picture,之后每次重绘只需要绘制这个Picture即可:

class CustomPaintSpans extends CustomPainter {
  late final Picture _preRenderedPicture;

  CustomPaintSpans() {
    final recorder = PictureRecorder();
    final canvas = Canvas(recorder);
    double delta = 10.0;
    double pixelSize = 10.0;

    // 提前把所有内容绘制到Picture里
    for (int j = 0; j < 100; ++j) {
      for (int i = 0; i < 100; ++i) {
        double startX = delta + i * (pixelSize + 1);
        double startY = delta + j * (pixelSize + 1);
        final textSpan = TextSpan(
          text: "$i",
          style: TextStyle(fontSize: 10, color: Colors.grey, fontWeight: FontWeight.w800)
        );
        final tp = TextPainter(
          text: textSpan,
          textAlign: TextAlign.center,
          textDirection: TextDirection.ltr
        );
        tp.layout();
        final offset = Offset(startX - pixelSize * 0.5, startY - pixelSize * 0.5);
        tp.paint(canvas, offset);
      }
    }
    _preRenderedPicture = recorder.endRecording();
  }

  @override
  void paint(Canvas canvas, Size size) {
    // 直接绘制预渲染的Picture,缩放时Canvas会自动处理
    canvas.drawPicture(_preRenderedPicture);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

这种方式的性能提升是最大的,因为预渲染只做一次,后续重绘只是复用已生成的图形数据,完全避免了重复创建文本对象的开销。

4. 优化RepaintBoundary的使用

你已经用了RepaintBoundary,可以确保CustomPaint的重绘不会影响父组件。另外可以给CustomPaintSpans加上const构造(如果内容完全静态),或者确保shouldRepaint返回false,避免不必要的重绘触发。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 15:07:39