【问题标题】:Flutter keep background canvas drawing to skip repaint when only foreground changes当仅前景发生变化时,颤振保持背景画布绘图以跳过重绘
【发布时间】:2020-12-13 20:19:43
【问题描述】:

我正在开发一个项目,该项目有一个CustomPaint,它以绘制形状作为背景,当您点击屏幕时,它会在该特定位置绘制一个圆圈。我正在使用GestureDetector 获取点击数据并将其作为参数发送给_MyCustomPainter 类似于以下代码:

class MyApp extends StatefulWidget{
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  Offset _circlePosition = Offset(-1, -1);

  void setCirclePosition(Offset newPosition) {
    setState(() {
      this._circlePosition = newPosition;
    });
  }

  void clearCircle() {
    setState(() {
      this._circlePosition = Offset(-1, -1);
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.green,
          title: Text('My app'),
        ),
        body: Container(
          child: GestureDetector(
            onTapDown: (detail) {
              setCirclePosition(detail.localPosition);
            },
            onHorizontalDragStart: (detail) {
              setCirclePosition(detail.localPosition);
            },
            onHorizontalDragUpdate: (detail) {
              setCirclePosition(detail.localPosition);
            },
            onVerticalDragStart: (detail) {
              setCirclePosition(detail.localPosition);
            },
            onVerticalDragUpdate: (detail) {
              setCirclePosition(detail.localPosition);
            },
            onTapUp: (detail) {
              clearCircle();
            },
            onVerticalDragEnd: (detail) {
              clearCircle();
            },
            onHorizontalDragEnd: (detail) {
              clearCircle();
            },
            child: LimitedBox(
              maxHeight: 400,
              maxWidth: 300,
              child: CustomPaint(
                size: Size.infinite,
                painter: new _MyCustomPainter(
                  circlePosition: circlePosition,
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _MyCustomPainter extends CustomPainter {
  _MyCustomPainter({
    this.circlePosition,
  });

  final Offset circlePosition;

  void _drawBackground(Canvas canvas) {
    // draw the background
  }

  @override
  void paint(Canvas canvas, Size size) {
    _drawBackground(canvas);
    if (circlePosition.dx != -1 && circlePosition.dy != -1) {
      // draws a circle on the position
      var circlePaint = Paint()..color = Colors.green;
      canvas.drawCircle(circlePosition, 5, circlePaint);
    }
  }

  @override
  bool shouldRepaint(_MyCustomPainter old) {
    return circlePosition.dx != old.circlePosition.dx ||
    circlePosition.dy != old.circlePosition.dy;
  }
}

所以这里的问题是,每次用户长按在屏幕上移动手指时,不变的背景都会被一遍又一遍地重新绘制,并且绘制这个特定的背景涉及很多代码.有shouldRepaint 方法,但它发出重新绘制整个小部件的信号。我怎样才能使背景只绘制一次,而不是在重绘时我只使用我之前创建的背景?使用PictureRecorder 生成图像,而不是在未来重绘时使用该图像是最好的方法吗?我的CustomPainter 是否应该扩展ChangeNotifier 以获得更好的性能?目前的方法可行,但我想知道如何改进它。

【问题讨论】:

  • 扩展ChangeNotifier(或在CustomPainter构造函数中使用repaint参数)用于通知您的画家重新绘制自己,现在您正在为每次更改创建一个全新的_MyCustomPainter

标签: flutter dart canvas flutter-layout


【解决方案1】:

所以我用来实现这一点的方法是使用两个 CustomPainter,一个用于背景(我们称之为 BackgroundPainter),一个用于前景(如 ForegroundPainter,当 BackgroundPaiter.shouldRepaint 方法返回 true 时,您将其绘制在画布并将其保存到图像中,然后在 ForegroundPainter 上使用该图像。我必须为这个烛台图表小部件 here 执行此操作。代码的简短版本类似于下面的示例:

class MyWidget extends StatefulWidget {
  MyWidget({
    this.foregroundParam,
    this.backgroundParam,
  });

  final String foregroundParam;
  final String backgroundParam;

  @override
  MyWidget createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  Picture backgroundPicture;
  _BackgroundPainter backgroundPainter;
  Size parentSize;
  Size oldParentSize;
  
  @override
  Widget Build(BuildContext context) {
    // We need the context size, which is only available after
    // the build method finishes.
    WidgetsBinding.instance.addPostFrameCallback((_) {
      // this code only runs after build method is run
      if (parentSize != context.size) {
        setState(() {
          parentSize = context.size;
        });
      }
    });
    
    var newBackgroundPainter = _BackgroundPainter(
      backgroundParam: widget.backgroundParam,
    );
    
    // update backgroundPicture and backgroundPainter if parantSize was updated
    // (like after a screen rotation) or if backgroundPainter was updated
    // based on the backgroundParam
    if (parentSize != null &&
         (oldParentSize != null && oldParentSize != parentSize || 
         backgroundPainter == null || 
         newBackgroundPainter.shouldRepaint(backgroundPainter) ||
         backgroundPicture == null)
    ) {
      oldParentSize = parentSize;
      backgroundPainter = newBackgroundPainter;
      var recorder = PictureRecorder();

      var canvas = Canvas(recorder, Rect.fromLTWH(0, 0, parentSize.width, parentSize.height));
      backgroundPainter.paint(canvas, parentSize);

      setState(() {
        backgroundPicture = recorder.endRecording();
      });
    }
    // if there is no backgroundPicture, this must be the first run where
    // parentSize was not set yet, so return a loading screen instead, but
    // this is very quick and most likely will not even be noticed. Could return
    // an empty Container as well
    if (backgroundPicture == null) {
      return Container(
        width: double.infinity,
        height: double.infinity,
        child: Center(
          child: CircularProgressIndicator(),
        ),
      );
    }

    // if everything is set, return an instance of _ForegroundPainter passing
    // the backgroundPicture as parameter
    return CustomPaint(
      size: Size.infinite,
      painter: _ForegroundPainter(
        backgroundPicture: backgroundPicture,
        foregroundParam: widget.foregroundParam,
      ),
    );
  }
}

class _BackgroundPainter extends CustomPainter {
  _BackgroundPainter({
    this.backgroundParam,
  });
  final String backgroundParam;

  @override
  void paint(Canvas canvas, Size size) {
    // paint background here
  }

  @override
  bool shouldRepaint(_BackgroundPainter oldPainter) {
    return backgroundParam != oldPainter.backgroundParam;    
  }
}

class _ForegroundPainter extends CustomPainter {
  _ForegroundPainter({
    this.backgroundPicture,
    this.foregroundParam,
  });
  final Picture backgroundPicture;
  final String foregroundParam;

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawPicture(backgroundPicture);

    // paint foreground here
  }

  @override
  bool shouldRepaint(_ForegroundPainter oldPainter) {
    return foregroundParam != oldPainter.foregroundParam ||
    backgroundPicture != oldPainter.backgroundPicture;   
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-13
    • 1970-01-01
    • 2013-12-31
    • 2022-10-04
    • 2014-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多