如何确保我的 CustomPaint 小部件绘画存储在光栅缓存中? [英] How to ensure my CustomPaint widget painting is stored in the raster cache?

查看:25
本文介绍了如何确保我的 CustomPaint 小部件绘画存储在光栅缓存中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个应用程序,它在用户触摸屏幕时显示一个黑点,如下所示:

用户可以在他/她在屏幕上拖动手指时移动黑点.

背景是一个昂贵的绘画操作,所以我在一个堆栈中创建了两个单独的小部件,希望背景小部件绘画将存储在 Flutter 光栅缓存中.但它没有被存储——每次黑点移动时,Flutter 都会调用我昂贵的绘制方法.

我做错了什么?

这是我的代码:

import 'package:flutter/material.dart';导入飞镖:数学";无效主(){运行应用程序(新的我的应用程序());}class MyApp 扩展 StatelessWidget {@覆盖小部件构建(BuildContext 上下文){返回新的 MaterialApp(主页:新的我的主页(),);}}class MyHomePage 扩展了 StatefulWidget {@覆盖状态 createState() =>新的 MyHomePageState();}类 MyHomePageState 扩展了 State{GlobalKey _paintKey = new GlobalKey();偏移_offset;@覆盖小部件构建(BuildContext 上下文){返回新的脚手架(正文:新堆栈(适合:StackFit.expand,孩子们:<小部件>[新的自定义油漆(画家:新的 ExpensivePainter(),isComplex: 真,willChange: 假,),新听众(onPointerDown: _updateOffset,onPointerMove: _updateOffset,孩子:新的自定义油漆(键:_paintKey,画家:新的MyCustomPainter(_offset),孩子:新的约束框(约束:新 BoxConstraints.expand(),),),)],),);}_updateOffset(指针事件事件){RenderBox referenceBox = _paintKey.currentContext.findRenderObject();Offset offset = referenceBox.globalToLocal(event.position);设置状态((){_offset = 偏移量;});}}类 ExpensivePainter 扩展 CustomPainter {@覆盖无效油漆(帆布画布,尺寸大小){print("做昂贵的油漆工作");随机兰特 = 新随机(12345);列表<颜色>颜色 = [颜色.红色,颜色.蓝色,颜色.黄色,颜色.绿色,颜色.白色,];for (int i = 0; i <5000; i++) {canvas.drawCircle(新偏移量(rand.nextDouble() * size.width, rand.nextDouble() * size.height),10 + rand.nextDouble() * 20,新油漆()..color = 颜色[rand.nextInt(colors.length)].withOpacity(0.2));}}@覆盖bool shouldRepaint(ExpensivePainter other) =>错误的;}类 MyCustomPainter 扩展了 CustomPainter {最终偏移_offset;MyCustomPainter(this._offset);@覆盖无效油漆(帆布画布,尺寸大小){如果(_offset == null)返回;canvas.drawCircle(_offset, 10.0, new Paint()..color = Colors.black);}@覆盖bool shouldRepaint(MyCustomPainter other) =>其他._offset != _offset;}

解决方案

这是 Flutter 的一个特殊性.我们不是在 React 中,组件"在这里.仅当它们的状态/道具发生变化时才重新绘制.

在 Flutter 中,每次小部件必须重新绘制整棵树时也会如此.

通常,这不是问题,而且速度相当快.但是在某些情况下(例如您的情况),您不希望那样.这是一个相当无证但重要的小部件出现的地方!重绘边界

这里有一篇关于 Flutter 渲染管道如何工作的精彩演讲:

I have an app that displays a black dot at the point where the user touches the screen like this:

The black dot can be moved by the user as he/she drags his finger on the screen.

The background is an expensive paint operation, so I have created two separate widgets in a stack, hoping that the background widget painting will be stored in the Flutter raster cache. But it's not stored - Flutter calls my expensive paint method every time the black dot moves.

What am I doing wrong?

Here's my code:

import 'package:flutter/material.dart';
import 'dart:math';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State createState() => new MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  GlobalKey _paintKey = new GlobalKey();
  Offset _offset;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Stack(
        fit: StackFit.expand,
        children: <Widget>[
          new CustomPaint(
            painter: new ExpensivePainter(),
            isComplex: true,
            willChange: false,
          ),
          new Listener(
            onPointerDown: _updateOffset,
            onPointerMove: _updateOffset,
            child: new CustomPaint(
              key: _paintKey,
              painter: new MyCustomPainter(_offset),
              child: new ConstrainedBox(
                constraints: new BoxConstraints.expand(),
              ),
            ),
          )
        ],
      ),
    );
  }

  _updateOffset(PointerEvent event) {
    RenderBox referenceBox = _paintKey.currentContext.findRenderObject();
    Offset offset = referenceBox.globalToLocal(event.position);
    setState(() {
      _offset = offset;
    });
  }
}

class ExpensivePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    print("Doing expensive paint job");
    Random rand = new Random(12345);
    List<Color> colors = [
      Colors.red,
      Colors.blue,
      Colors.yellow,
      Colors.green,
      Colors.white,
    ];
    for (int i = 0; i < 5000; i++) {
      canvas.drawCircle(
          new Offset(
              rand.nextDouble() * size.width, rand.nextDouble() * size.height),
          10 + rand.nextDouble() * 20,
          new Paint()
            ..color = colors[rand.nextInt(colors.length)].withOpacity(0.2));
    }
  }

  @override
  bool shouldRepaint(ExpensivePainter other) => false;
}

class MyCustomPainter extends CustomPainter {
  final Offset _offset;

  MyCustomPainter(this._offset);

  @override
  void paint(Canvas canvas, Size size) {
    if (_offset == null) return;
    canvas.drawCircle(_offset, 10.0, new Paint()..color = Colors.black);
  }

  @override
  bool shouldRepaint(MyCustomPainter other) => other._offset != _offset;
}

解决方案

It's a specificity of Flutter. We are not in React, where "Components" are repainted only when their state/props change.

In Flutter, every time a widget has to repaint the whole tree will too.

Usually, this is not a problem and fairly fast. But in some cases (such as yours), you don't want that. And this is where a fairly undocumented but important widget appears! RepaintBoundary

There's an excellent talk about how Flutter's rendering pipeline works, here: https://www.youtube.com/watch?v=UUfXWzp0-DU

But in short, consider RepaintBoundary as what tells Flutter to split the painting operation into different parts.

Anyway, the solution ? Wrap your Expensive widget in a RepaintBoundary. And suddenly you get 60 FPS.

      new RepaintBoundary(
        child: new CustomPaint(
          painter: new ExpensivePainter(),
          isComplex: true,
          willChange: false,
        ),
      ),

这篇关于如何确保我的 CustomPaint 小部件绘画存储在光栅缓存中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆