在Flutter中绘制应用程序.被重新绘制卡住



这是我的应用程序的主要.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;
}
}

这个实现显示了一个我正在尝试构建的应用程序,以便在画布上绘制。

然而,当我触摸屏幕时,我无法将其绘制出来。相反,只有当我改变墨水池的颜色时,它才会重新绘制,但当它正在书写或在手势检测器中接收到事件时,它不会重新绘制。

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

在此之前,我要说,请不要把我在回答中所说的任何话都视为针对个人的——我知道StackOverflow一直在努力实现be nice。但我也要说:你需要来清理你的代码。这不是人身攻击,只是某个人说的一些话,他想帮助你提高程序员的水平。

你肯定应该把逻辑分解成更小的块——一般规则是,如果你的构建函数在垂直方向上占据了超过一个页面,那么它可能做得太多了。这对程序员来说都是正确的,但对应用程序的性能来说也是正确的,因为flutter使用了一种受大型构建函数影响的基于状态的机制进行重建。

另外几个提示是:

  • 尽可能不要使用GlobalKey。AppBar应该封装在它自己的小部件中,或者至少使用Builder构建,这样它就可以接收到包含scaffold的上下文。然后可以使用Scaffold.of(....)而不是全局密钥。

  • 如果你要使用像static var change = [1, 2, 0]这样的静态var,你可能最好使用enum——尤其是没有文档注释,这对其他查看你代码的人来说毫无意义(即me=d)。使用枚举将使代码更具可读性。此外,请考虑用不同的方式命名事物——cchanged对任何人来说都没有意义,除了你自己(如果你离开几周,甚至可能对你也没有意义)。

  • 油漆工和小部件中的颜色列表是一个等待发生的编码错误。Building Paint对象很便宜,只要你不在循环中执行它——只需在构建函数中实例化一个新对象,而不是保留一个列表,然后直接传递颜色!或者至少,制作一个名为Palette或其他什么的enum,并用它来做决定,而不是列表——至少这样,如果你使用开关,飞镖分析仪将有助于确保你满足每一个可能的选项。

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

  • 使用setState时,实际在setState函数中设置状态现在如果你不。。。但它在语义上并不清楚你在做什么,如果flutter开发人员改变setState的工作方式,它在未来可能很容易被破坏。

  • 因为你在小吃条中使用Center,它们正在扩展以占据整个屏幕。我很确定这不是你想要做的,所以添加heightFactor = 1.0,这样它们就不会垂直扩展。我会让你来决定你是否想继续使用这一视觉机制来改变你正在改变的颜色——我个人认为这不太好,因为它掩盖了你实际用来选择颜色的区域。。。

我猜这只是一个为了好玩而快速组装的应用程序,你正在学习颤振和/或编码。但从专业的开发人员那里获取;偶尔的招聘经理-多花几分钟时间确保你的代码可读且易于理解从长远来看会节省你的时间,而且即使在一次性项目中也能写干净的代码,这将帮助你养成良好的习惯,帮助你成为一名更好的程序员。此外,当我检查一位可能的员工时,如果我在他们的公共github存储库中看到非常混乱的代码,那就是不给他们面试的又一个原因。我知道并不是你在github上做的每件事都是为公众准备的,但代码清洁是一项很容易开发的技能,不需要那么长时间,这说明了你是一名程序员!

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

现在来谈谈你的实际问题——这一切都归结为这样一句话:

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

在dart(以及许多其他编程语言,但不是全部)中,将列表与!=进行比较或者==只做我所说的"肤浅的比较";也就是说,它只检查该列表是否为同一列表。因为每次检测到手势时都会向相同的列表添加一个新点,但没有重新创建列表,所以proceeding语句总是返回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;
}
}

我添加了修订参数,并进行了其他一些小的修复。您的代码仍然需要大量的重构,但这至少应该使它能够工作。请记住我的其他意见。

还有——看看这个问题&回答,因为它可以做类似的事情,如果你最终想做的话,它可以帮助保存生成的图形!