Flutter 如何确保我的CustomPaint小部件绘制存储在光栅缓存中?

Flutter 如何确保我的CustomPaint小部件绘制存储在光栅缓存中?,flutter,Flutter,我有一个应用程序,在用户触摸屏幕的地方显示一个黑点,如下所示: 当用户在屏幕上拖动手指时,可以移动黑点 背景是一个昂贵的绘制操作,因此我在一个堆栈中创建了两个单独的小部件,希望背景小部件的绘制将存储在flatter光栅缓存中。但它不是存储的——每当黑点移动时,弗利特就会调用我昂贵的绘画方法 我做错了什么 这是我的密码: import 'package:flutter/material.dart'; import 'dart:math'; void main() { runApp(new

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

当用户在屏幕上拖动手指时,可以移动黑点

背景是一个昂贵的绘制操作,因此我在一个堆栈中创建了两个单独的小部件,希望背景小部件的绘制将存储在flatter光栅缓存中。但它不是存储的——每当黑点移动时,弗利特就会调用我昂贵的绘画方法

我做错了什么

这是我的密码:

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;
}
导入“包装:颤振/材料.省道”;
导入“dart:math”;
void main(){
runApp(新的MyApp());
}
类MyApp扩展了无状态小部件{
@凌驾
小部件构建(构建上下文){
返回新材料PP(
主页:新建MyHomePage(),
);
}
}
类MyHomePage扩展StatefulWidget{
@凌驾
State createState()=>新建MyHomePageState();
}
类MyHomePageState扩展了状态{
GlobalKey _paintKey=新的GlobalKey();
偏移量_偏移量;
@凌驾
小部件构建(构建上下文){
归还新脚手架(
正文:新堆栈(
fit:StackFit.expand,
儿童:[
新定制涂料(
油漆工:新的昂贵油漆工(),
是的,
willChange:错,
),
新听众(
onPointerDown:_updateOffset,
onPointerMove:\u updateOffset,
孩子:新油漆(
钥匙:_paintKey,
画家:新的MyCustomPainter(_offset),
子项:新的约束框(
约束:新建BoxConstraints.expand(),
),
),
)
],
),
);
}
_updateOffset(PointerEvent事件){
RenderBox referenceBox=_paintKey.currentContext.FindEnderObject();
偏移量=referenceBox.globalToLocal(事件位置);
设置状态(){
_偏移量=偏移量;
});
}
}
类ExpensivePainter扩展CustomPainter{
@凌驾
空心油漆(帆布,尺寸){
印刷(“做昂贵的油漆工作”);
随机兰德=新随机(12345);
列表颜色=[
颜色,红色,
颜色,蓝色,
颜色,黄色,
颜色,绿色,
颜色,白色,
];
对于(int i=0;i<5000;i++){
帆布画圈(
新偏移量(
rand.nextDouble()*size.width,rand.nextDouble()*size.height),
10+rand.nextDouble()*20,
新油漆
…颜色=颜色[rand.nextInt(colors.length)],不透明度(0.2));
}
}
@凌驾
bool应重新喷漆(其他费用)=>false;
}
类MyCustomPainter扩展了CustomPainter{
最终偏移量_偏移量;
MyCustomPainter(此偏移量);
@凌驾
空心油漆(帆布,尺寸){
如果(_offset==null)返回;
canvas.drawCircle(_offset,10.0,new Paint()…color=Colors.black);
}
@凌驾
bool应重新绘制(MyCustomPainter other)=>其他。偏移量!=\U偏移量;
}

这是颤振的一种特殊性。我们不在React中,在React中,“组件”只有在其状态/道具发生变化时才会重新绘制

在flatter中,每次小部件必须重新绘制时,整个树也将重新绘制

通常,这不是一个问题,而且速度相当快。但在某些情况下(比如你的),你不希望这样。这就是一个相当没有文档但很重要的小部件出现的地方<代码>重新绘制边界

这里有一篇关于颤振渲染管道工作原理的精彩演讲:

<> P>但是,请考虑<代码> RePrimeStult作为告诉颤动将绘画操作分割成不同的部分。

不管怎样,解决办法是什么? 将您的
昂贵
小部件包装在
重新绘制边界
中。然后突然你得到60帧每秒

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

顺便问一下,为什么要使用
Listener
进行拖放?有一个用于此目的的
Draggable
小部件。感谢您的提示-我不知道Draggable,但它并不适合我的情况:我正在为我的协作白板webapp编写一个flifter应用程序:为一个草图应用程序编写了一些几乎相同的东西。那很有效-谢谢!还感谢在关于重新绘制边界的部分添加书签的视频链接:)我也在使用RepaitBoundary,但我没有得到60 fps,你能给我发送你的代码吗。
RepaitBoundary
本身是否缓存东西,还是决定是否缓存的
isComplex
参数?RepaitBoundary缓存东西,但不是马上。如果节点使用相同的内容绘制>=3次,则会保留缓存