在 Flutter 中使用设置的 FPS 绘制大量简单、填充颜色的矩形的最高效方法是啥?
Posted
技术标签:
【中文标题】在 Flutter 中使用设置的 FPS 绘制大量简单、填充颜色的矩形的最高效方法是啥?【英文标题】:What is the most performant way to draw a large number of simple, colour-filled rectangles with a set FPS in Flutter?在 Flutter 中使用设置的 FPS 绘制大量简单、填充颜色的矩形的最高效方法是什么? 【发布时间】:2021-03-26 19:05:17 【问题描述】:我想用大量随机的、颜色填充的矩形来填充一个方形的 CustomPaint 小部件。动画需要以设定的 FPS 速率更新。我有一个工作演示,但即使只有 100x100 随机矩阵,我的手机上的最大 FPS 似乎也在 30 左右。代码需要跨平台(安卓和ios)。
我是 Flutter 的新手,我想知道是否有更高效的方式来生成这个动画。逻辑一点也不复杂,但我想大量的随机值生成和成千上万的小矩形是及时生成的挑战。在原生 android 上,我使用了 openGL 和 GLSurfaceView,我希望 Flutter 有同样的性能......
这是我的代码的链接:https://gist.github.com/ize8/f734b9d62d78c74667a845f211e06fb7
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main()
runApp(MyApp());
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'CustomPaint Animation'),
);
class MyHomePage extends StatefulWidget
MyHomePage(Key key, this.title) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin
bool _isRunning = true; //is the animation running?
var _fps = new List.filled(20, 0.0); //list for the last 20 FPS
int _pos = 0; //position for logging FPS
final _rnd = Random();
final _rowSize = 100;//how many rectangles in a row
final _freq = -1; //the maximum FPS, -1 if no limit
List _matrix;
Ticker _ticker;
Duration lastTick = Duration(milliseconds: 0);
@override
void initState()
super.initState();
_matrix = _generateMatrix();//generate random set
_ticker = createTicker((Duration elapsed)
var diff = elapsed.inMicroseconds - lastTick.inMicroseconds;//time elapsed since last tick
var currentFps = 1000000 / diff;//calculate current FPS
if (currentFps <= _freq || _freq == -1)
setState(()
_fps[_pos] = currentFps;//save FPS into list
lastTick = elapsed;
_matrix = _generateMatrix(); //generate new random set
);
if (_pos < _fps.length - 1)
_pos += 1;
else
_pos = 0;
);
_ticker.start();//start ticker
@override
void dispose()
_ticker.dispose();
super.dispose();
//generate new random set of color codes 0-3
List _generateMatrix()
return new List.generate(_rowSize * _rowSize, (int i) => _rnd.nextInt(4),
growable: false);
void _toggleDynamic(bool isRunning)
setState(()
_isRunning = isRunning;
);
if (isRunning)
_ticker.start();
else
_ticker.stop();
//calculate average FPS from list
double _getAvgFps()
return _fps.reduce((value, element) => value + element) / _fps.length;
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: OrientationBuilder(builder: (context, orientation)
return Center(
child: Container(
color: Colors.grey[850],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Run!",
style: TextStyle(
color: Colors.blue[300],
fontWeight: FontWeight.bold,
fontSize: 20.0),
),
Switch(
value: _isRunning,
onChanged: (bool val) => _toggleDynamic(val)
),
],
),
Divider(),
Container(
width: 400,
height: 400,
decoration: BoxDecoration(color: Colors.white, boxShadow: [
BoxShadow(
color: Colors.black,
blurRadius: 4.0,
spreadRadius: 2.0,
)
]),
child: CustomPaint(
painter: MyPainter(_matrix, 400/_rowSize, _rowSize),//fill out the 400x400 container
isComplex: true,
willChange: true,
),
),
Divider(),
Text(
"$_getAvgFps().toStringAsFixed(2) FPS",
style: TextStyle(color: Colors.blue[300]),
),
],
),
),
);
),
);
class MyPainter extends CustomPainter
List _matrix;
double _pixSize;
int _rowSize;
final redPaint = Paint()..color = Colors.red;
final greenPaint = Paint()..color = Colors.green[900];
final bluePaint = Paint()..color = Colors.blueGrey;
final purplePaint = Paint()..color = Colors.purple;
Paint getPaint(int code)
switch (code)
case 0:
return bluePaint;
break;
case 1:
return purplePaint;
break;
case 2:
return greenPaint;
break;
case 3:
return redPaint;
break;
default:
return bluePaint;
MyPainter(List matrix, double pixSize, int rowSize)
this._matrix = matrix;
this._pixSize = pixSize;
this._rowSize = rowSize;
@override
void paint(Canvas canvas, Size size)
for (var i = 0; i < _matrix.length; i++)
var col = (i / _rowSize).floor();
var row = i % _rowSize;
canvas.drawRect(
Rect.fromLTWH(col * _pixSize, row * _pixSize, _pixSize, _pixSize),
getPaint(_matrix[i]));
@override
bool shouldRepaint(MyPainter oldDelegate)
return false;
也许将位图生成为原始字节码并将其显示在画布上而不是绘制所有这些矩形会更快?
P.S.[1]:我发现
decodeImageFromPixels()
看起来很有希望,虽然同时也是一个 PITA...
【问题讨论】:
【参考方案1】:decodeImageFromPixels()
然后将回调中的 Image 绘制到 CustomPaint 上提供了令人满意的性能提升。虽然还没有达到Android+GLSurfaceView的水平,但还是可以接受的:)
【讨论】:
以上是关于在 Flutter 中使用设置的 FPS 绘制大量简单、填充颜色的矩形的最高效方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
60 FPS 是啥意思在 Flutter 中如何一个屏幕每秒有 60 帧