CustomPaint с анимацией: избегайте воссоздания объекта

Предположим, у меня есть объект

class LogoDrawer extends CustomPainter {

   int x;
   
   LogoDrawer(this.x){
      print('New logoDrawer object created');
      _initStuff();
   }

   void Paint(Canvas canvas, Size size)
   {
      _paintStuff();
   }
}

И значение x изменяется внешне через анимацию, и метод _initStuff может быть дорогостоящим, но не зависит от значения x.

Если бы я тогда оживил это методом _onClick

class _SomeState extends State<SomeWidget>  with TickerProviderStateMixin {

    x = 0;

    Widget build(BuildContext context){
        return GestureDetector(
                child: CustomPaint(
                  willChange: true,
                  painter: LogoDrawer(x)
                ),
                onTap: _onClick,
              );
    }

    void _onClick() {
        _controller = AnimationController(
        duration: Duration(milliseconds: 1000),
        vsync: this,
        );

        animation = Tween(begin: 0.4, end: 0.0).animate(CurvedAnimation(
            parent: _controller, curve: Curves.ease, reverseCurve: Curves.easeOut))
        ..addListener(() {
            setState(() {
                x = x+1;
            });
        });
        _controller.forward().orCancel;
    }
}

Технически это работает, но консоль продолжает печатать

Создан новый объект logoDrawer

довольно часто (не при каждой перерисовке). А это означает, что инициализации также очень часто повторяются, что сильно сказывается на ресурсах. На самом деле я бы хотел создать объект один раз, изменить значение x с помощью анимации и выполнять только перерисовку, а не создавать новые объекты. Я имел в виду создать пользовательский художник один раз и обновить его внутреннее значение, но это, похоже, не вызывает перерисовки.

class _SomeState extends State<SomeWidget>  with TickerProviderStateMixin {

    x = 0;
    var myPainter = LogoDrawer(x);

    Widget build(BuildContext context){
        return GestureDetector(
                child: CustomPaint(
                  willChange: true,
                  painter: myPainter
                ),
                onTap: _onClick,
              );
    }

    void _onClick() {
        _controller = AnimationController(
        duration: Duration(milliseconds: animDuration),
        vsync: this,
        );

        animation = Tween(begin: 0.4, end: 0.0).animate(CurvedAnimation(
            parent: _controller, curve: Curves.ease, reverseCurve: Curves.easeOut))
        ..addListener(() {
            setState(() {
                x = x+1;
                // OR 
                painter.x = x+1;
            });
        });
        _controller.forward().orCancel;
    }
}

Обратите внимание, что это общее описание моей проблемы и что реальный код, с которым я работаю, намного сложнее этого.


person Wouter Vandenputte    schedule 07.09.2020    source источник
comment
проверьте repaint параметр конструктора CustomPaint   -  person pskink    schedule 07.09.2020
comment
в качестве альтернативы смешайте свой CustomPainter с ChangeNotifier - дополнительную информацию см. в официальной документации   -  person pskink    schedule 09.09.2020


Ответы (2)


Возможно, можно использовать метод shouldRepaint для запуска перерисовки, только если какая-то переменная изменилась, как в этом примере: Аналоговые часы Flutter

person ouzari    schedule 11.09.2020
comment
Это все равно вызовет создание нового объекта вместо перерисовки. Я бы предпочел сделать что-то вроде myPainter.setDateTime(now), а затем сделать что-то вроде myPainter.repaint(), но последнего, к сожалению, не существует - person Wouter Vandenputte; 12.09.2020
comment
@WouterVandenputte, значит ли это, что параметр repaint из CustomPainter не работает? - person pskink; 12.09.2020
comment
ну точно не знаю. Это довольно сложное приложение. есть State, который вызывает onClick, а затем создает анимацию со случайной продолжительностью, так что вращается определенная часть художника, но не весь художник. Но мне нужно было бы дать эту анимацию перед созданием моего FooPainter. Как только эта анимация будет завершена, вращение должно продолжиться с того места, где оно есть, поэтому сброс на новый объект с начальным вращением в ноль радиан не выполняется. - person Wouter Vandenputte; 12.09.2020
comment
новый onClick затем создает новую анимацию, но как передать эту новую анимацию существующей FooPainter? Я могу только создать новый экземпляр, но не могу его обновлять repaint аргумент - person Wouter Vandenputte; 12.09.2020

Что вы могли бы сделать, так это создать класс, содержащий все данные, которые вы инициализируете, и инициализировать их один раз в вашем состоянии и передать их вашему художнику. Таким образом, каждый раз у вас будет новый экземпляр рисовальщика, но не нужно его инициализировать, поскольку эти данные передаются.

В качестве альтернативы вы можете поместить эти данные в static члены и получить статический метод инициализации вашего пользовательского класса рисовальщика. Например, в одном из моих проектов у меня есть два художника, между которыми вы можете переключаться: один просто заполняет разными цветами, а другой использует текстуры. Поскольку все текстуры должны быть загружены, это делается не при каждом переключении, а только один раз на экране-заставке.

person nvoigt    schedule 07.09.2020
comment
Да, я тоже об этом думал, но в художнике есть некоторая логика, которую мне пришлось бы реорганизовать. Я просто надеялся, что есть простое решение проблемы перерисовки через setState - person Wouter Vandenputte; 07.09.2020
comment
@nvoigt уже есть встроенное решение в CustomPainter: параметр repaint, используемый в CustomPainter конструкторе - person pskink; 10.09.2020
comment
@pskink Звучит хорошо, почему бы не ответить на этот вопрос? - person nvoigt; 10.09.2020
comment
@pskink Мне кажется, я не нашел этот repaint параметр в конструкторе. api.flutter.dev/flutter/widgets/CustomPaint-class.html - person Wouter Vandenputte; 11.09.2020
comment
@WouterVandenputte см. api.flutter.dev/flutter/rendering/CustomPainter/ - он читает: Художник будет перерисовывать каждый раз, когда repaint уведомляет своих слушателей. - person pskink; 11.09.2020
comment
хорошо согласно документу ‹Расширить этот класс и предоставить аргумент перерисовки конструктору CustomPainter, где этот объект уведомляет своих слушателей, когда пора перерисовать.› но это означает, что CustomPainter должен будет уведомить слушателей, что он может ' т? Мне действительно может помочь пример, потому что документация не дает никакой помощи в этой области. Я знаю, что CustomPainter наследуется от Listenable, но он предоставляет только методы для добавления слушателей, а не для вызова их - person Wouter Vandenputte; 11.09.2020
comment
@WouterVandenputte все, что вам нужно, это позвонить super(repaint: ...), например: class FooPainter extends CustomPainter { final Animation<Color> animation; FooPainter(this.animation) : super(repaint: animation); @override void paint(Canvas canvas, Size size) { canvas.drawRect(Offset.zero & size, Paint()..color = animation.value); } ... - person pskink; 11.09.2020
comment
@pskink, спасибо, похоже, возникла проблема с пакетом, так как есть 2 экземпляра CustomPainter. Один в widgets и один в rendering. Вот почему мне не удалось вызвать этот конструктор - person Wouter Vandenputte; 12.09.2020
comment
@WouterVandenputte в widgets у вас есть CustomPaint, а в rendering у вас есть CustomPainter - person pskink; 12.09.2020