Когда вернуть True из CustomPainter.shouldRepaint() во Flutter?

Во Flutter, при создании CustomPainter, при каких обстоятельствах вы НЕ должны возвращать false внутри shouldRepaint()?

Из того, что я прочитал, и из всех примеров, которые я видел, он всегда возвращает false.

А в документации описание метода:

должен то же самое, потому что последнее реализовано в терминах первого). [...]

У меня голова болит!

2 ответа

Я широко использовал CustomPainter, и вот мой ответ.

Во-первых, вот полный документ. Возможно, вы прочитали только начальные предложения, а не весь документ. https://api.flutter.dev/flutter/rendering/CustomPainter/shouldRepaint.html

Почему и как знающий человек решит вернуть false/true?

Вот правило: If the new instance represents different information than the old instance, then the method should return true, otherwise it should return false.

Пример:

      class MyPainter extends CustomPainter {
  MyPainter() : super();

  @override
  void paint(Canvas canvas, Size size) => canvas.drawRect(Offset.zero & size, Paint());

  // false since all instances of MyPainter contain same information
  @override
  bool shouldRepaint(MyPainter oldDelegate) => false;
}

class MyPainter extends CustomPainter {
  final Color color;
  final double width;

  MyPainter(this.color, this.width) : super();

  @override
  void paint(Canvas canvas, Size size) => canvas.drawRect(
      Offset.zero & size,
      Paint()
        ..color = color
        ..strokeWidth = width);

  @override
  bool shouldRepaint(MyPainter oldDelegate) => oldDelegate.color != this.color || oldDelegate.width != this.width;
}

Я думал, что нам не нужно беспокоиться о том, когда виджет «должен перерисовываться», потому что Flutter должен был решать, где и когда перерисовывать дерево виджетов, основываясь на своих собственных сложных решениях по оптимизации.

Да и нет. Это shouldRepaint() в основном является оптимизацией скорости. Вы можете постоянно возвращать true, если вас не волнует производительность.

Я думал, что в методе paint () вы определяете «вот как это представление рисует себя (всегда и всякий раз, когда это необходимо)»

"так рисует себя этот взгляд" - да. "всегда и всякий раз, когда это необходимо" - частично нет. Если вы предоставите неверную информацию для shouldRepaint(), вы можете пропустить некоторые краски.

Все примеры, которые я нашел, просто возвращают false из этого метода... но я заметил различное поведение при использовании true и false.

Какая??? Я вижу, как люди возвращают true или возвращают сравнение (см. мой пример ниже). Но при возврате false могут возникнуть проблемы. Даже просто взглянув на комментарии к этой функции, вы увидите, что она должна вызывать проблемы с константой false. Но в любом случае, если ваш рисовальщик действительно не содержит никакой информации, которая может измениться, ничего страшного...

Если мы всегда возвращаем false, то как он перерисовывается? (И он перерисовывается, даже когда ложь)

        /// If the method returns false, then the [paint] call might be optimized
  /// away.
  ///
  /// It's possible that the [paint] method will get called even if
  /// [shouldRepaint] returns false (e.g. if an ancestor or descendant needed to
  /// be repainted). It's also possible that the [paint] method will get called
  /// without [shouldRepaint] being called at all (e.g. if the box changes
  /// size).

Обратите внимание на «могу» и абзац ниже. Flutter может выбрать, рисовать или не рисовать.

Если единственная возможная логика, доступная нам, — это сравнение «oldDelegate» с (чем-то?), то почему мы вообще должны переопределять метод?

Смотрите мой пример

Я не видел ни одного примера, демонстрирующего, почему или как вы вернете TRUE, и как будет выглядеть логика такого примера для принятия такого решения.

Смотрите мой пример


Кстати, было бы здорово, если бы вы получили некоторое представление о Flutter, например, о логике виджета/макета/отрисовки и т. д., тогда вы легко поймете проблему. Но в любом случае я ответил выше, используя слова, которые, надеюсь, легко понять даже без глубокого понимания Flutter.

Это означает всегда перерисовывать, если метод setState() вызывается из класса State. Виджеты в классе State будут обновлены, если setState() изменит их данные.

долженПерекрасить() ПРИМЕР:

проверьте внизу встроенные комментарии!

      @override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true; 
    // true means Always repaint if this CustomPainter is updated anywhere else
             // e.g. from the State class 
             // State class can be called as below 
}

Полный код...

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

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

class MyApp extends StatelessWidget {  
    const MyApp({super.key});
      // This widget is the root of your application.

    @override
    Widget build(BuildContext context) {
        return MaterialApp(
                  title: 'Flutter Custom Paint example ',
                  //theme: ThemeData(  //use theme if needed
                  //     brightness: Brightness.dark,
                  //     primaryColor: Colors.indigo,
                  //     accentColor: Colors.indigoAccent
                  //    ),      
                  home: _CustomPainterView()  // _is implemented in this file
                              //first return the StatefulWidget                      
                        
               );
    }
}


class _CustomPainterView extends StatefulWidget{ //must be a stateful widget
    @override
    State<StatefulWidget> createState() {
        return _CustomViewState();   //here return the State widget
    }
}


//State class below references the Stateful widget from which it was called...

class _CustomViewState extends State<_CustomPainterView>{
    Map<String, Object> painterData = {'screenMessage': 'not yet swiped', 'var_1': 0, 'var_2': 0.0  };
//all the needed parameters for the instance of  CustomPainter class must be defined here in the State Class. 
//they will be passed by setState() method to the  CustomPainter which will provides the actual canvas

@override
Widget build(BuildContext context) {
    return  Scaffold(             
          appBar: AppBar( title: Text("Custom Painter Example") ) ,
            body: Container(
                    width: MediaQuery.of(context).size.width,//get width from device
                    height: MediaQuery.of(context).size.width*2, //i use the the custom width from half of the height                                                                                      
                    // ignore: sort_child_properties_last
                    child:  GestureDetector( //to allow screen swipe or drag    
                                child: CustomPaint(//CustomPaint() is just a container for actual painter. 
                                                   //note the spelling 
                                        painter: _CustomPainterExample(painterData) 
                                                  //return CustomPainter() 
                                                  //supply constructor data
                                       ),  
                                onVerticalDragUpdate: (details) {
                                  int sensitivity = 1;// = 1 every pixel swiped will be detected by below code
                                  setState( (){                                          
                                      if  (details.delta.dy > sensitivity) {                                              
                                            debugPrint("swipe down");   //print to the debug console                                           
                                            painterData['screenMessage'] = "swipe down";
                                            //this change only the key-value that needs to be changed in the key-value pairs, then repaint.
                                            // painterData map will change but inside setState() will cause other effect                                                
                                            //setState  recalls CustomPainter consctructor every time
                                            //setState force a repaint in  CustomPainter
                                      } 
                                      if(details.delta.dy < -sensitivity){                                           
                                          debugPrint("swipe up");  
                                            painterData['screenMessage'] = "swipe up";                               
                                      }
                                                                         
                                    }
                                  );
                                },
                                onHorizontalDragUpdate: (details) {
                                  int sensitivity = 1;
                                  setState( (){                                           
                                       if  (details.delta.dx > sensitivity) {                                             
                                            debugPrint("swipe right");
                                              painterData['screenMessage'] = "swipe right";
                                      } 
                                      if(details.delta.dx < -sensitivity){
                                          
                                          debugPrint("swipe left");  
                                          painterData['screenMessage'] = "swipe left";                             
                                      }                                    
                                    }
                                  );
                                },
                                   
                            ),  
                            //color: Color.fromARGB(255, 50, 57, 126)
                    // ignore: prefer_const_constructors
                    color: Color.fromARGB(255, 1, 108, 141),
                    
                  ),
        );
    }
}

class _CustomPainterExample extends CustomPainter {
    Map<String, Object> painterData = new Map<String, Object>();

    _CustomPainterExample(Map<String, Object> pdata){
        painterData = pdata;
      }

    @override
    void paint(Canvas canvas, Size size) {
        var centerX = size.width/2;
        var centerY = size.height/2;
        var center = Offset(centerX,centerY);
        var radius = min(centerX,centerY);
        var fillBrush = Paint()
        // ignore: prefer_const_constructors
        ..color = Color.fromARGB(255, 202, 122, 29);
        canvas.drawCircle( center, radius, fillBrush );
        //can also draw using the data from constructor method
  
        var textPainter = TextPainter(   
                    text: TextSpan(
                           text:  painterData['screenMessage'].toString(),
                           style: TextStyle(color: Color.fromARGB(255, 245, 242, 242),fontSize: 30,),
                          ),
                    textDirection: TextDirection.ltr,                        
                  );
      textPainter.layout(minWidth: 0, maxWidth: size.width);//<<< needed method
      textPainter.paint(canvas, Offset(5.0, (90/100)*size.height));
  } //customPainter

      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) {
        return true;  //always repaint if setState() is called from the State  class. Look for setState() method in the class: _CustomViewState 
        //It will update canvas since the _CustomPainterExample is one of widgets in the _CustomViewState which is the State class. All widgets in the State class will be updated if SetState() changes their data.
      }

}
Другие вопросы по тегам