【问题标题】:Draw App in Flutter.Stuck with repaint在 Flutter 中绘制应用程序。重新绘制时卡住了
【发布时间】:2018-07-04 12:52:31
【问题描述】:

这是我应用的 main.dart 文件:

import 'package:flutter/material.dart';

main() {
  runApp(
    new MaterialApp(
      title: 'Flutter Database Test',
      theme: ThemeData(
        textTheme: TextTheme(
          display1: TextStyle(fontSize: 24.0, color: Colors.white),
          display2: TextStyle(fontSize: 24.0, color: Colors.grey),
          display3: TextStyle(fontSize: 18.0, color: Colors.black),
        ),
      ),
      home: new MyHomePage(),
    ),
  );
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Color> color = [
    Colors.white,
    Colors.black,
    Colors.pink,
    Colors.blue,
    Colors.red,
    Colors.yellow,
    Colors.orange,
    Colors.green,
    Colors.cyan,
    Colors.purple,
    Colors.brown,
    Colors.indigo,
    Colors.teal,
    Colors.grey,
  ];

  Color _pencolor = Colors.white;
  Color _canvasclr = Colors.black;
  bool ispen = true;
  Color bc = Colors.black54;
  List<Offset> points = List<Offset>();
  GlobalKey<ScaffoldState> _skey = GlobalKey<ScaffoldState>();
  int cchanged = change[1];

  static var change = [1, 2, 0];

  @override
  Widget build(BuildContext context) {
    MyPainter _painter = MyPainter(color: cchanged, canvasp: points, changed: color.indexOf(_pencolor));
    _painter.addListener(() {
      print('hello');
    });
    return new Scaffold(
      resizeToAvoidBottomPadding: true,
      key: _skey,
      appBar: AppBar(
        backgroundColor: bc,
        elevation: 0.0,
        title: Text(
          'Draw',
          style: Theme.of(context).textTheme.display1,
        ),
        centerTitle: true,
        actions: <Widget>[
          IconButton(
              icon: Icon(Icons.content_paste),
              onPressed: () {
                _skey.currentState.showSnackBar(SnackBar(
                    backgroundColor: Colors.transparent,
                    content: Center(
                        child: Text(
                      'Choose Canvas Color',
                      textScaleFactor: 0.7,
                      style: Theme.of(context).textTheme.display3,
                    ))));
                ispen = false;
              }),
          IconButton(
              icon: Icon(Icons.edit),
              onPressed: () {
                _skey.currentState.showSnackBar(SnackBar(
                  content: Center(
                      child:
                          Text('Choose Pen Color', textScaleFactor: 0.7, style: Theme.of(context).textTheme.display3)),
                  backgroundColor: Colors.transparent,
                ));
                ispen = true;
              })
        ],
      ),
      drawer: Drawer(
        child: ListView(
          children: <Widget>[
            ListTile(
              title: Text('Create Canvas'),
              leading: Icon(Icons.add),
              onTap: () {
                //TODO
              },
            ),
            ListTile(
              title: Text('Connect to Canvas'),
              leading: Icon(Icons.compare_arrows),
              onTap: () {
                //TODO
              },
            )
          ],
        ),
      ),
      body: Container(
        color: bc,
        child: Column(
          children: <Widget>[
            Expanded(
              child: Padding(
                padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 10.0),
                child: ClipRRect(
                  child: Container(
                    color: _canvasclr,
                    child: GestureDetector(
                      onPanStart: (DragStartDetails d) {
                        points.add(Offset(d.globalPosition.dx, d.globalPosition.dy - 100));
                        cchanged = change[2];
                        print('${d.globalPosition},$points');
                        setState(() {});
                      },
                      onPanUpdate: (DragUpdateDetails d) {
                        points.add(Offset(d.globalPosition.dx, d.globalPosition.dy - 100));
                        print('${d.globalPosition}');
                        cchanged = change[0];
                        setState(() {});
                      },
                      child: CustomPaint(
                        isComplex: true,
                        willChange: false,
                        child: Container(),
                        painter: _painter,
                      ),
                    ),
                  ),
                  borderRadius: BorderRadius.circular(25.0),
                ),
              ),
            ),
            Container(
              height: 75.0,
              child: ListView.builder(
                scrollDirection: Axis.horizontal,
                shrinkWrap: true,
                itemCount: color.length,
                itemBuilder: (BuildContext context, int index) {
                  return InkWell(
                    splashColor: Colors.white,
                    onTap: () {
                      ispen ? _pencolor = color[index] : _canvasclr = color[index];
                      setState(() {});
                    },
                    child: Padding(
                      padding: const EdgeInsets.all(12.0),
                      child: CircleAvatar(
                        backgroundColor: color[index],
                      ),
                    ),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class MyPainter extends CustomPainter {
  final int color;
  final List<Offset> canvasp;
  Paint p = Paint();
  List<Paint> _paint = [
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.white,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.black,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.pink,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.blue,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.red,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.yellow,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.orange,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.green,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.cyan,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.purple,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.brown,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.indigo,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.teal,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.grey,
  ];

  final int changed;

  MyPainter({
    this.color,
    this.canvasp,
    this.changed,
  });

  @override
  void paint(Canvas canvas, Size size) {
    print('painting .......     $canvasp');
    for (int i = 0; i < canvasp.length; i++) {
      canvas.drawCircle(canvasp[i], 10.0, _paint[color]);
    }
  }

  @override
  bool shouldRepaint(MyPainter oldDelegate) {
    return oldDelegate.canvasp.length != canvasp.length;
  }
}

此实现显示了我正在尝试构建以在画布上绘制的应用。

但是,当我触摸屏幕时,我无法绘制它。相反,它仅在我更改墨水池中的颜色时才会重新绘制 - 而不是在书写时或在 GestureDetector 中接收到事件时。

我们将不胜感激以更改我现有代码的形式给出答案。

【问题讨论】:

  • 这次我已经为您完成了,但通常最好在发布之前格式化您的代码(VS Code 和 IntelliJ 有一个热键)。

标签: android flutter repaint flutter-layout


【解决方案1】:

我要先说请不要把我在这个答案中所说的任何话当做开场白——我知道 StackOverflow 一直在尝试实施的 be nice 努力。但我还要说:您需要考虑清理您的代码。这不是人身攻击,只是希望帮助您提高程序员水平的人的一些话。

您绝对应该将逻辑分解为更小的块 - 一般规则是,如果您的构建函数垂直占用超过一页,则它可能做得太多。这对于作为程序员的你来说都是如此,对于应用程序的性能也是如此,因为 Flutter 使用基于状态的重建机制,这种机制会受到大型构建函数的影响。

另外几个提示是:

  • 尽可能不要使用 GlobalKey。您的 AppBar 应该封装在它自己的小部件中,或者至少使用Builder 构建,以便它接收包含脚手架的上下文。然后你可以使用Scaffold.of(....) 而不是全局键。

  • 1234563 =D)。使用枚举将使您的代码更具可读性。另外,请考虑以不同的方式命名 - cchanged 对任何人都没有任何意义,除了你自己(如果你离开几个星期,甚至可能对你也没有意义)。
  • 您的画家和小部件中的颜色列表是一个等待发生的编码错误。只要您不在循环中进行,构建 Paint 对象就很便宜 - 只需在构建函数中实例化一个新对象,而不是保留一个列表,然后直接传递颜色!或者至少,制作一个名为 Palette 的enum 或其他东西,并用它来做出决定而不是列出一个列表——至少这样,如果你使用开关,飞镖分析器将有助于确保你满足每个可能的选项.

  • 这是第二段的重复,但重要的是重复...将您的类分解为更多有状态或无状态的小部件...或至少将您的构建功能分解为多个功能.这将使事情变得不那么复杂、更容易理解并且性能更高。例如,每次在画布上绘制一个点时,您可能会同时重绘标题栏、底部栏以及您在屏幕上看到的所有其他内容,因为它们都在一个小部件中!您的 statefulwidget 应该只构建有状态的可视化部分!

  • 使用setState时,实际上是在setState函数内设置状态。 现在,如果你不这样做,一切都会好起来的......但是你在做什么在语义上并不那么清楚,如果颤振开发者改变如何在未来很容易崩溃setState 有效。

  • 因为您在快餐栏中使用Center,所以它们正在扩展以占据整个屏幕。我很确定这不是你想要做的,所以添加heightFactor = 1.0 这样它们就不会垂直扩展。我将由您决定是否要继续使用这种视觉机制来更改您要更改的颜色 - 我个人认为这不是很好,因为它覆盖了您实际用于选择颜色的区域。 .

我猜这只是一个有趣的快速应用程序,并且您正在学习 Flutter 和/或编码。但请从专业的开发人员和临时招聘经理那里获取 - 多花几分钟时间确保您的代码可读且易于理解从长远来看会节省您的时间,并且编写即使在一次性项目中,干净的代码也会帮助您养成良好的习惯,从而帮助您成为更好的程序员。另外 - 当我检查可能的招聘时,如果我在他们的一个公共 github 存储库中看到非常混乱的代码,那就是不给他们面试的另一个理由。我知道并不是你在 github 上所做的一切都是为公众服务的,但代码整洁是一项易于开发的技能,不需要花费那么长时间,并且说明了你作为一名程序员的很多信息!

最后一件事 - 要求以一种特定方式回答您的问题有点粗鲁,因为我们在 stackoverflow 上免费这样做是因为我们想帮助社区。我已经软化了你问题的语气,但请记住这一点,因为礼貌 ==> 更好的答案!

现在谈谈你的实际问题 - 这一切都归结为以下陈述:

@override
bool shouldRepaint(MyPainter oldDelegate) {
  return oldDelegate.canvasp.length != canvasp.length;
}

在 dart(以及许多其他编程语言,但不是全部)中,将列表与 != 或 == 进行比较只会做我所说的“浅比较”;也就是说,它只检查列表是否是同一个列表。因为您每次检测到手势时都会向 same 列表添加一个新点,但不会重新创建列表,因此后续语句始终返回 false,因此您的画布从不重画。

解决此问题的最简单方法是每次添加内容时将列表复制到一个新列表中 - 这样列表将比较不同。但是,如果您要处理很多可能会出现在此处的元素,那么这并不是一个特别好的答案。

我建议改为使用计数器来跟踪每次更改列表时的情况。我在下面的一些代码中进行了更改 - 我称之为_revision。每次添加到列表时,还要增加 _revision 并将其传递到画布。在您的画布中,您只需检查修订版是否相同。我建议创建一个为您执行递增的方法,并确保正确调用 setState。

import 'package:flutter/material.dart';

main() {
  runApp(
    new MaterialApp(
      title: 'Flutter Database Test',
      theme: ThemeData(
        textTheme: TextTheme(
          display1: TextStyle(fontSize: 24.0, color: Colors.white),
          display2: TextStyle(fontSize: 24.0, color: Colors.grey),
          display3: TextStyle(fontSize: 18.0, color: Colors.black),
        ),
      ),
      home: new MyHomePage(),
    ),
  );
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Color> color = [
    Colors.white,
    Colors.black,
    Colors.pink,
    Colors.blue,
    Colors.red,
    Colors.yellow,
    Colors.orange,
    Colors.green,
    Colors.cyan,
    Colors.purple,
    Colors.brown,
    Colors.indigo,
    Colors.teal,
    Colors.grey,
  ];

  Color _pencolor = Colors.white;
  Color _canvasclr = Colors.black;
  bool ispen = true;
  Color bc = Colors.black54;
  List<Offset> points = List<Offset>();
  GlobalKey<ScaffoldState> _skey = GlobalKey<ScaffoldState>();
  int cchanged = change[1];
  int _revision = 0;

  static var change = [1, 2, 0];

  @override
  Widget build(BuildContext context) {
    MyPainter _painter =
        MyPainter(color: color.indexOf(_pencolor), canvasp: points, changed: cchanged, revision: _revision);
    _painter.addListener(() {
      print('hello');
    });
    return new Scaffold(
      resizeToAvoidBottomPadding: true,
      key: _skey,
      appBar: AppBar(
        backgroundColor: bc,
        elevation: 0.0,
        title: Text(
          'Draw',
          style: Theme.of(context).textTheme.display1,
        ),
        centerTitle: true,
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.content_paste),
            onPressed: !ispen
                ? null
                : () {
                    _skey.currentState.showSnackBar(
                      SnackBar(
                        backgroundColor: Colors.transparent,
                        content: Center(
                          heightFactor: 1.0,
                          child: Text(
                            'Choose Canvas Color',
                            textScaleFactor: 0.7,
                            style: Theme.of(context).textTheme.display3,
                          ),
                        ),
                      ),
                    );
                    setState(() {
                      ispen = false;
                    });
                  },
          ),
          IconButton(
            icon: Icon(Icons.edit),
            onPressed: ispen
                ? null
                : () {
                    _skey.currentState.showSnackBar(
                      SnackBar(
                        content: Center(
                            heightFactor: 1.0,
                            child: Text('Choose Pen Color',
                                textScaleFactor: 0.7, style: Theme.of(context).textTheme.display3)),
                        backgroundColor: Colors.transparent,
                      ),
                    );
                    setState(() {
                      ispen = true;
                    });
                  },
          )
        ],
      ),
      drawer: Drawer(
        child: ListView(
          children: <Widget>[
            ListTile(
              title: Text('Create Canvas'),
              leading: Icon(Icons.add),
              onTap: () {
                //TODO
              },
            ),
            ListTile(
              title: Text('Connect to Canvas'),
              leading: Icon(Icons.compare_arrows),
              onTap: () {
                //TODO
              },
            )
          ],
        ),
      ),
      body: Container(
        color: bc,
        child: Column(
          children: <Widget>[
            Expanded(
              child: Padding(
                padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 10.0),
                child: ClipRRect(
                  child: Container(
                    color: _canvasclr,
                    child: GestureDetector(
                      onPanStart: (DragStartDetails d) {
                        setState(() {
                          points.add(Offset(d.globalPosition.dx, d.globalPosition.dy - 100));
                          cchanged = change[2];
                          _revision++;
                        });
                        print('${d.globalPosition},$points');
                      },
                      onPanUpdate: (DragUpdateDetails d) {
                        print('${d.globalPosition}');
                        setState(() {
                          points.add(Offset(d.globalPosition.dx, d.globalPosition.dy - 100));
                          cchanged = change[0];
                          _revision++;
                        });
                      },
                      child: CustomPaint(
                        isComplex: true,
                        willChange: false,
                        child: Container(),
                        painter: _painter,
                      ),
                    ),
                  ),
                  borderRadius: BorderRadius.circular(25.0),
                ),
              ),
            ),
            Container(
              height: 75.0,
              child: ListView.builder(
                scrollDirection: Axis.horizontal,
                shrinkWrap: true,
                itemCount: color.length,
                itemBuilder: (BuildContext context, int index) {
                  return InkWell(
                    splashColor: Colors.white,
                    onTap: () {
                      setState(() {
                        ispen ? (_pencolor = color[index]) : (_canvasclr = color[index]);
                      });
                    },
                    child: Padding(
                      padding: const EdgeInsets.all(12.0),
                      child: CircleAvatar(
                        backgroundColor: color[index],
                      ),
                    ),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class MyPainter extends CustomPainter {
  final int color;
  final List<Offset> canvasp;
  final int revision;
  Paint p = Paint();
  List<Paint> _paint = [
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.white,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.black,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.pink,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.blue,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.red,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.yellow,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.orange,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.green,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.cyan,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.purple,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.brown,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.indigo,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.teal,
    Paint()
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2.0
      ..color = Colors.grey,
  ];

  final int changed;

  MyPainter({this.color, this.canvasp, this.changed, this.revision});

  @override
  void paint(Canvas canvas, Size size) {
    print('painting .......     $canvasp');
    for (int i = 0; i < canvasp.length; i++) {
      canvas.drawCircle(canvasp[i], 10.0, _paint[color]);
    }
  }

  @override
  bool shouldRepaint(MyPainter oldDelegate) {
    return oldDelegate.revision != revision;
  }
}

我添加了修订参数并进行了其他一些小修复。你的代码仍然需要大量的重构,但这至少应该让它工作。请记住我的其他 cmets =)。

另外 - 看看 this question & answer,因为它做了类似的事情,如果你最终想要这样做,它可以帮助保存生成的绘图!

【讨论】:

  • 为了强调这一点,每个MyPainter 都引用同一个列表,因此即使尝试比较长度也不起作用,因为它们同一个列表。我通过在构建时保存列表的长度以供以后比较来解决它,但是rmt的修订更优雅。
  • 非常感谢..因为我是这里的新手,所以我没有太多知识。我将实施您指出的所有内容以及所有这些实际努力。再次感谢您。
  • 没问题@SushanthKille,这就是我们来这里的目的!每个人在某些时候都是新手=D
猜你喜欢
  • 2012-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-27
  • 1970-01-01
  • 1970-01-01
  • 2011-05-22
  • 1970-01-01
相关资源
最近更新 更多