【问题标题】:Flutter Improve CustomPainter animation performanceFlutter 提升 CustomPainter 动画性能
【发布时间】:2020-06-28 15:40:48
【问题描述】:

我需要一个将移动的正弦和余弦函数绘制到画布中的加载小部件。我使用 CustomPaint 小部件和 CustomPainter 毫无问题地对其进行了编码,但是当我对其进行分析时,我发现它以大约 49 fps 的速度运行,而不是 60 fps。 UI 线程运行良好,每帧大约需要 6 毫秒,但光栅线程需要更长的时间。我尝试在画布上画更少的点(在 for 循环中使用 i=i+5 而不是 i++),但结果完全一样。

¿有人可以建议我如何提高性能吗?下面是小部件代码,以及 Raster 线程在每一帧中所做的 DevTools 屏幕截图,以防它有用。

import 'dart:math';

import 'package:flutter/material.dart';

class LoadingChart extends StatefulWidget{
  final Color color1;
  final Color color2;
  final double lineWidth;
  final bool line;
  final Size size;

  const LoadingChart({
    @required this.color1,
    @required this.color2,
    @required this.size,
    @required this.lineWidth,
    this.line = true,
    Key key
  }): super(key: key);

  @override
  State<StatefulWidget> createState() => _LoadingChartState();

}

class _LoadingChartState extends State<LoadingChart>
  with SingleTickerProviderStateMixin{
  AnimationController _controller;


  double randomHeight(Random random, double max){
    return random.nextDouble()*max;
  }

  @override
  void initState() {
    _controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
    _controller.addListener(() {setState(() {});});
    _controller.repeat();

    super.initState();
  }

  @override
  void dispose(){
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: widget.size.height,
      width: widget.size.width,
      child: CustomPaint(
        painter: PathPainter(
          color1: widget.color1,
          color2: widget.color2,
          value: _controller.value,
          line: widget.line,
        ),
      )
    );
  }

}

class PathPainter extends CustomPainter {
  final Color color1;
  final Color color2;
  final double lineWidth;
  final bool line;

  final double value;

  PathPainter({
    @required this.value,
    this.color1=Colors.red,
    this.color2=Colors.green,
    this.line = true,
    this.lineWidth=4.0,
  }): super();

  @override
  void paint(Canvas canvas, Size size) {
    final height = size.height;
    final width = size.width;

    Paint paint1 = Paint()
      ..color = color1
      ..style = PaintingStyle.stroke
      ..strokeWidth = lineWidth;

    Paint paint2 = Paint()
      ..color = color2
      ..style = PaintingStyle.stroke
      ..strokeWidth = lineWidth;

    Path path1 = Path();
    Path path2 = Path();

    /* If line is true, draw sin and cos functions, otherwise, just some points */
    for (double i = 0; i < width; i=i+5){
      double f = i*2*pi/width + 2*pi*value;
      double g = i*2*pi/width - 2*pi*value;
      if (i == 0){
        path1.moveTo(0, height/2 + height/6*sin(f));
        path2.moveTo(0, height/2 + height/6*cos(g));
        continue;
      }

      path1.lineTo(i, height/2 + height/6*sin(f));
      path2.lineTo(i, height/2 + height/6*cos(g));
    }

    /* Draw both lines */
    canvas.drawPath(path1, paint1);
    canvas.drawPath(path2, paint2);
  }

  @override
  bool shouldRepaint(PathPainter oldDelegate) {
   return oldDelegate.value != value || oldDelegate.color1 != color1
     || oldDelegate.color2 != color2 || oldDelegate.line != line
     || oldDelegate.lineWidth != lineWidth;
  }
}

PS:我在配置文件模式下运行应用程序,所以这不应该是问题。我还想提一下,它是唯一在屏幕上重绘的小部件。 非常感谢!!

【问题讨论】:

    标签: flutter profiling flutter-animation custom-painting


    【解决方案1】:

    CustomPainter 可以接收可侦听的对象,因此也许您可以使用那里的动画控制器在每个滴答声中更新它

    class _LoadingChartState extends State<LoadingChart>
        with SingleTickerProviderStateMixin{
      AnimationController _controller;
    
    
      double randomHeight(Random random, double max){
        return random.nextDouble()*max;
      }
    
      @override
      void initState() {
        _controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
        //_controller.addListener(() {setState(() {});}); no need to setState
        _controller.repeat();
    
        super.initState();
      }
    
      @override
      void dispose(){
        _controller.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return SizedBox(
            height: widget.size.height,
            width: widget.size.width,
            child: CustomPaint(
              willChange: true, //this can help (Whether the raster cache should be told that this painting is likely)
              painter: PathPainter(
                color1: widget.color1,
                color2: widget.color2,
                line: widget.line,
                listenable: _controller //pass the controller as it is (An animationController extends a Listenable)
              ),
            )
        );
      }
    }
    

    在 PathPainter 中,您将可侦听对象提供给构造函数,并将其传递给 CustomPainter 构造函数,该构造函数接受名为 repaint 的可侦听对象

    class PathPainter extends CustomPainter {
      final Animation listenable;
      final Color color1;
      final Color color2;
      final double lineWidth;
      final bool line;
    
      PathPainter({
        this.listenable,
        this.color1=Colors.red,
        this.color2=Colors.green,
        this.line = true,
        this.lineWidth=4.0,
      }): super(repaint: listenable); //don't forget calling the CustomPainter constructor with super
    
      @override
      void paint(Canvas canvas, Size size) {
        double value = listenable.value; // get its value here
        final height = size.height;
        final width = size.width;
    
        Paint paint1 = Paint()
          ..color = color1
          ..style = PaintingStyle.stroke
          ..strokeWidth = lineWidth;
    
        Paint paint2 = Paint()
          ..color = color2
          ..style = PaintingStyle.stroke
          ..strokeWidth = lineWidth;
    
        Path path1 = Path();
        Path path2 = Path();
    
        /* If line is true, draw sin and cos functions, otherwise, just some points */
        for (double i = 0; i < width; i=i+5){
          double f = i*2*pi/width + 2*pi*value;
          double g = i*2*pi/width - 2*pi*value;
          if (i == 0){
            path1.moveTo(0, height/2 + height/6*sin(f));
            path2.moveTo(0, height/2 + height/6*cos(g));
            continue;
          }
    
          path1.lineTo(i, height/2 + height/6*sin(f));
          path2.lineTo(i, height/2 + height/6*cos(g));
        }
    
        /* Draw both lines */
        canvas.drawPath(path1, paint1);
        canvas.drawPath(path2, paint2);
      }
    
      @override
      bool shouldRepaint(PathPainter oldDelegate) {
        //delete the oldDelegate.value, it doesn't exists anymore
        return oldDelegate.color1 != color1
            || oldDelegate.color2 != color2 || oldDelegate.line != line
            || oldDelegate.lineWidth != lineWidth;
      }
    }
    

    我处于调试模式,所以我希望您在配置文件模式下获得更好的性能

    【讨论】:

    • 好吧,这样我得到了稍微好一点的结果,但我觉得奇怪的是,与其他更复杂的动画相比,像这样的相对简单的动画在光栅线程上花费了如此多的时间,因为小部件在 AnimatedSwitcher 上切换。为什么会这样?还是谢谢!
    • 我认为问题在于您使用的是 AnimationController 但不是可以侦听或更新该值的小部件,而是您使用 setState 每个滴答声较慢,请尝试不要将 setState 与 Animationcontroller 一起使用并使用包装 CustomPaint 的 AnimatedBuilder 来优化事物
    • 我不认为是这个问题。如果你看一下 AnimatedBuilder 代码,它只是 AnimatedWidget 的一个包装器,而 AnimatedWidget 只是在每个控制器更新时调用setState,所以它基本上和我在这里做的一样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多