对于每一帧都重新绘制整个画布,甚至是重新绘制上一帧的尝试通常不会更有效率。
从你发布的代码来看,有一些需要改进的地方,但试图保留画布的某些部分不应该是其中之一。
你正在遇到的真正性能问题是,每50毫秒从Timer.periodic
事件中重复更改ValueNotifier
。更好的处理每帧重绘的方法是,使用带有vsync
的AnimatedBuilder
,这样CustomPainter
的paint
方法将在每一帧被调用。如果您熟悉Web浏览器世界中的Window.requestAnimationFrame
,那么这与其类似。这里的vsync
代表“垂直同步”,如果您了解计算机图形工作原理的话。基本上,在具有60 Hz屏幕的设备上,您的paint
方法将每秒调用60次,并且在具有120 Hz屏幕的设备上,它将每秒绘制120次。这是实现流畅动画的正确可扩展方式,可适用于各种设备。
在考虑保留画布的某些部分之前,还有其他需要优化的地方。例如,只是简要查看您的代码,您有这行:
_currentY = _rng.nextInt(size.height.toInt()).toDouble();
在这里,我假设你想要一个介于0
和size.height
之间的随机小数,如果是这样,你可以简单地写_rng.nextDouble() * size.height
,而不是把一个double强制转换成int再转回来,并且(可能无意中)在这个过程中四舍五入。但是像这样的性能提升是微不足道的。
想一想,如果一个3D视频游戏可以在手机上流畅运行,并且每个帧与前一个帧截然不同,那么你的动画应该可以流畅运行,而不必担心手动清除画布的部分。试图手动优化画布可能会导致性能损失。
因此,你真正应该关注的是,在你的项目中使用AnimatedBuilder
而不是Timer
来触发画布重绘,作为一个起点。
例如,这是一个我使用AnimatedBuilder和CustomPaint制作的小演示:
![demo snowman](https://istack.dev59.com/t4aJ6.gif)
完整源代码:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
List<SnowFlake> snowflakes = List.generate(100, (index) => SnowFlake());
AnimationController _controller;
@override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
)..repeat();
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.blue, Colors.lightBlue, Colors.white],
stops: [0, 0.7, 0.95],
),
),
child: AnimatedBuilder(
animation: _controller,
builder: (_, __) {
snowflakes.forEach((snow) => snow.fall());
return CustomPaint(
painter: MyPainter(snowflakes),
);
},
),
),
);
}
}
class MyPainter extends CustomPainter {
final List<SnowFlake> snowflakes;
MyPainter(this.snowflakes);
@override
void paint(Canvas canvas, Size size) {
final w = size.width;
final h = size.height;
final c = size.center(Offset.zero);
final whitePaint = Paint()..color = Colors.white;
canvas.drawCircle(c - Offset(0, -h * 0.165), w / 6, whitePaint);
canvas.drawOval(
Rect.fromCenter(
center: c - Offset(0, -h * 0.35),
width: w * 0.5,
height: w * 0.6,
),
whitePaint);
snowflakes.forEach((snow) =>
canvas.drawCircle(Offset(snow.x, snow.y), snow.radius, whitePaint));
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
class SnowFlake {
double x = Random().nextDouble() * 400;
double y = Random().nextDouble() * 800;
double radius = Random().nextDouble() * 2 + 2;
double velocity = Random().nextDouble() * 4 + 2;
SnowFlake();
fall() {
y += velocity;
if (y > 800) {
x = Random().nextDouble() * 400;
y = 10;
radius = Random().nextDouble() * 2 + 2;
velocity = Random().nextDouble() * 4 + 2;
}
}
}
在这里我正在生成100个雪花,每一帧都会重新绘制整个屏幕。你可以很容易地将雪花数量更改为1000或更高,并且它仍然可以非常流畅地运行。在这里我也没有充分利用设备屏幕大小,正如您所看到的,有一些硬编码值,如400或800。无论如何,希望这个演示能让您对Flutter的图形引擎有信心。 :)
这是另一个(较小的)示例,向您展示了在Flutter中使用Canvas和Animations所需的所有内容。这可能更容易跟进:
import 'package:flutter/material.dart';
void main() {
runApp(DemoWidget());
}
class DemoWidget extends StatefulWidget {
@override
_DemoWidgetState createState() => _DemoWidgetState();
}
class _DemoWidgetState extends State<DemoWidget>
with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
)..repeat(reverse: true);
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (_, __) => CustomPaint(
painter: MyPainter(_controller.value),
),
);
}
}
class MyPainter extends CustomPainter {
final double value;
MyPainter(this.value);
@override
void paint(Canvas canvas, Size size) {
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
value * size.shortestSide,
Paint()..color = Colors.blue,
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}