Когда вернуть 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.
}
}