Flutter:CustomPainter 绘制方法被调用多次,而不是只调用一次

Nor*_*man 8 graphics canvas dart flutter flutter-layout

我有一个简单的应用程序,它通过CustomPainter画布上的红色或绿色圆圈进行绘制,具体取决于按下的按钮AppBar

红圈
绿色圆圈

该类ColorCircle扩展CustomPainter并负责绘制彩色圆圈:

class ColorCircle extends CustomPainter {
  MaterialColor myColor;

  ColorCircle({@required this.myColor});
  
  @override
  void paint(Canvas canvas, Size size) {
    debugPrint('ColorCircle.paint, ${DateTime.now()}');
    final paint = Paint()..color = myColor;
    canvas.drawCircle(Offset(size.width / 2, size.height / 2), 100, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Run Code Online (Sandbox Code Playgroud)

不同颜色的绘制效果很好,但是当我单击(仅一次!)或将鼠标悬停在其中一个按钮上时,该paint方法会被调用多次:

调试信息


进一步的实现细节:我使用 aStatefulWidget来存储actualColor. 在 build 方法中actualColor传递给ColorCircle构造函数:

class _MyHomePageState extends State<MyHomePage> {
  MaterialColor actualColor = Colors.red;
    
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: <Widget>[
          OutlinedButton(
            onPressed: () => setState(() => actualColor = Colors.red),
            child: Text('RedCircle'),
          ),
          OutlinedButton(
            onPressed: () => setState(() => actualColor = Colors.green),
            child: Text('GreenCircle'),
          ),
        ],
      ),
      body: Center(
        child: CustomPaint(
          size: Size(300, 300),
          painter: ColorCircle(myColor: actualColor),
        ),
      ),
    );
  }
}  
Run Code Online (Sandbox Code Playgroud)

带有运行示例的完整源代码可以在这里找到:CustonPainter Demo


那么为什么要paint调用多次而不是只调用一次呢?(你如何实现它以便paint只被调用一次?)。

7ma*_*ada 17

你需要做的就是CustomPaint扭曲RepaintBoundary

   Center(
    child: RepaintBoundary(
      child: CustomPaint(
        size: Size(300, 300),
        painter: ColorCircle(myColor: actualColor),
      ),
    ),
Run Code Online (Sandbox Code Playgroud)

默认情况下,CustomPainter与同一屏幕上的所有其他小部件位于同一层,因此如果同一屏幕上的任何其他小部件重新绘制,它将调用它的绘制方法。为了解决这个问题,我们可以隔离CustomPainterwith RepaintBoundary,这样任何在 this 之外的重绘都RepaintBoundary不会影响它,或者我们可以通过扭曲其他可以重绘的小部件来修复它,RepaintBoundary这样它们在重绘时就不会影响任何其他小部件(包括CustomPainter小部件),但是最好只用 来扭曲CustomPainterRepaintBoundary而不是用 来扭曲多个小部件,RepaintBoundary因为它的成本很高,而且有时没有效果。

您可以通过DevTools.


Thi*_*rry 2

RepaintBoundary一个糟糕的解决方案可能是在悬停小部件周围添加一个:

class _MyHomePageState extends State<MyHomePage> {
  MaterialColor actualColor = Colors.red;

  @override
  Widget build(BuildContext context) {
    print('Rebuilding with $actualColor');
    return Scaffold(
      appBar: AppBar(
        title: Text('CustomPainter Demo'),
        actions: <Widget>[
          RepaintBoundary(
            child: OutlinedButton(
                style: ButtonStyle(
                    foregroundColor: MaterialStateProperty.all(Colors.black)),
                onPressed: () {
                  setState(() => actualColor = Colors.red);
                },
                child: Text('RedCircle')),
          ),
          RepaintBoundary(
            child: OutlinedButton(
                style: ButtonStyle(
                    foregroundColor: MaterialStateProperty.all(Colors.black)),
                onPressed: () {
                  setState(() => actualColor = Colors.green);
                },
                child: Text('GreenCircle')),
          ),
        ],
      ),
      body: Center(
        child: CustomPaint(
          size: Size(300, 300),
          painter: ColorCircle(myColor: actualColor),
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,正确定义(当前返回)shouldRepaint的方法:ColorCirclefalse

@override
bool shouldRepaint(CustomPainter oldDelegate) {
  return (oldDelegate as ColorCircle).myColor != myColor;
}
Run Code Online (Sandbox Code Playgroud)

这似乎是一个非常糟糕的解决方案。我很想知道更好、更可持续的答案。

完整的源代码和RepaintBoundary解决方法

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'CustomPainter Demo',
      home: MyHomePage(),
    );
  }
}

class ColorCirle extends CustomPainter {
  MaterialColor myColor;

  ColorCirle({@required this.myColor});
  @override
  void paint(Canvas canvas, Size size) {
    debugPrint('ColorCircle.paint, ${DateTime.now()}');
    final paint = Paint()..color = myColor;
    canvas.drawCircle(Offset(size.width / 2, size.height / 2), 100, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return (oldDelegate as ColorCirle).myColor != myColor;
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  MaterialColor actualColor = Colors.red;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('CustomPainter Demo'),
        actions: <Widget>[
          RepaintBoundary(
            child: OutlinedButton(
                style: ButtonStyle(
                    foregroundColor: MaterialStateProperty.all(Colors.black)),
                onPressed: () {
                  setState(() => actualColor = Colors.red);
                },
                child: Text('RedCircle')),
          ),
          RepaintBoundary(
            child: OutlinedButton(
                style: ButtonStyle(
                    foregroundColor: MaterialStateProperty.all(Colors.black)),
                onPressed: () {
                  setState(() => actualColor = Colors.green);
                },
                child: Text('GreenCircle')),
          ),
        ],
      ),
      body: Center(
        child: CustomPaint(
          size: Size(300, 300),
          painter: ColorCirle(myColor: actualColor),
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 抱歉,目前没有解决方案,因为问题仍在框架级别悬而未决:https://github.com/flutter/flutter/issues/49298 (2认同)