问题描述
最少的可重复代码:
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final List<Offset> _points = [];
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onpressed: () => setState(() {}),// This setState works
child: Icon(Icons.refresh),),body: GestureDetector(
onPanUpdate: (details) => setState(() => _points.add(details.localPosition)),// but this doesn't...
child: CustomPaint(
painter: MyCustomPainter(_points),size: Size.infinite,);
}
}
class MyCustomPainter extends CustomPainter {
final List<Offset> points;
MyCustomPainter(this.points);
@override
void paint(Canvas canvas,Size size) {
final paint = Paint()..color = Colors.red;
for (var i = 0; i < points.length; i++) {
if (i + 1 < points.length) {
final p1 = points[i];
final p2 = points[i + 1];
canvas.drawLine(p1,p2,paint);
}
}
}
@override
bool shouldRepaint(MyCustomPainter oldDelegate) => false;
}
尝试通过在屏幕上长时间拖动来绘制一些东西,您将看不到任何绘制出来的东西。现在,按下 FAB 将显示绘制的绘画,这可能是因为 FAB 调用 setState
但 onPanUpdate
也调用 setState
并且该调用不会在屏幕上绘制任何内容。为什么?
注意:我不是在寻找有关如何启用绘画的解决方案,一个简单的 return true
就可以完成这项工作。我需要知道的是为什么一个 setState
有效(在屏幕上绘制)但另一个失败。
解决方法
要了解为什么 onPanUpdate
中的 setState() 不起作用,您可能需要查看小部件绘制渲染器,即 CustomPaint
。
在完成该帧的渲染后,CustomPaint(也如文档所述)访问画家对象(在您的情况下为 MyCustomPainter
)。为了确认我们可以检查 CustomPainter 的来源。我们可以看到 markNeedsPaint()
仅在我们通过 setter 访问 painter
对象时被调用。为了更清楚,您可能需要查看 RenderCustomPaint
的来源,您一定会理解它:
void _didUpdatePainter(CustomPainter? newPainter,CustomPainter? oldPainter) {
// Check if we need to repaint.
if (newPainter == null) {
assert(oldPainter != null); // We should be called only for changes.
markNeedsPaint();
} else if (oldPainter == null ||
newPainter.runtimeType != oldPainter.runtimeType ||
newPainter.shouldRepaint(oldPainter)) { //THIS
markNeedsPaint();
}
.
.
.
}
虽然在每次 setState 调用时,您的点都在更新,但每次创建“MyCustomPainter”的新实例时,都会创建小部件树,但由于上述原因,画家尚未绘制。
这就是为什么调用 markNeedPaint()
(即绘制对象)的唯一方法是将 true
返回到 shouldRepaint
或者 oldDeleagate
为 null发生和 CustomPainter
的 Fist UI 构建,您可以验证这一点,在列表中提供一些默认点。
还说明了
[paint] 方法可能会被调用,即使 [shouldRepaint] 返回 false(例如,如果祖先或后代 需要重新粉刷)。 [paint] 方法也有可能 将在 [shouldRepaint] 根本没有被调用的情况下被调用(例如,如果 盒子会改变大小)。
所以 Fab 的 setState 在这里工作的唯一原因(接缝有效)是 Fab 正在以某种方式重建自定义画家的任何父级。您也可以尝试在“web build”或使用 dartpad 中调整 UI 大小,您会发现随着父级重建自身,点将变得可见,因此 setState 直接与 shouldRepaint 无关。即使将鼠标悬停在 fab(在 dartpad 中)按钮上也会导致 ui 重建,因此点将可见。