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
相关产品推荐
相关产品推荐

