首先声明,Flutter本身的性能是没有问题的,关键在于如何使用。
我们编写的时候要避免这些错误,才能得到高效的Flutter应用。
1.不要把widgets拆倒方法里
当我们有一个巨大的布局页面,很多人通常的做法是分割成一个个多方法为widget。
下面的例子包含了header、center、footer widget
class MyHomePage extends StatelessWidget {
Widget _buildHeaderWidget() {
final size = 40.0;
return Padding(
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(
backgroundColor: Colors.grey[700],
child: FlutterLogo(
size: size,
),
radius: size,
),
);
}
Widget _buildMainWidget(BuildContext context) {
return Expanded(
child: Container(
color: Colors.grey[700],
child: Center(
child: Text(
'Hello Flutter',
style: Theme.of(context).textTheme.display1,
),
),
),
);
}
Widget _buildFooterWidget() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text('This is the footer '),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHeaderWidget(),
_buildMainWidget(context),
_buildFooterWidget(),
],
),
),
);
}
}
事实上是我们刷新局部的时候会刷新整个widget,这样毫无疑问浪费了CPU。
正确的做法是把这些做成stateless Widget,如下
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
HeaderWidget(),
MainWidget(),
FooterWidget(),
],
),
),
);
}
}
class HeaderWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final size = 40.0;
return Padding(
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(
backgroundColor: Colors.grey[700],
child: FlutterLogo(
size: size,
),
radius: size,
),
);
}
}
class MainWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
color: Colors.grey[700],
child: Center(
child: Text(
'Hello Flutter',
style: Theme.of(context).textTheme.display1,
),
),
),
);
}
}
class FooterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text('This is the footer '),
);
}
}
Stateful/Stateless widgets 有特殊的缓存机制(基于key, widget type and attributes),只在必要的情况下刷新。除了这个,也帮助我们封装和重构我们的widgets。(分而治之)
同时,给我们的widgets添加const关键字也是一个好主意,后面详述。
2.避免重复的rebuilding所有的wigets
典型的错误就是当我们需要rebuild我们的StatefulWidget的时候用到了setState。
下面的例子是一个widget包括一个方块做中央,里面有个fav的按钮,每点击一次颜色就会变好,还有一个背景图片。
同时我们也添加了一些print的代码来看看他是如何工作的。
class _MyHomePageState extends State<MyHomePage> {
Color _currentColor = Colors.grey;
Random _random = new Random();
void _onPressed() {
int randomNumber = _random.nextInt(30);
setState(() {
_currentColor = Colors.primaries[randomNumber % Colors.primaries.length];
});
}
@override
Widget build(BuildContext context) {
print('building `MyHomePage`');
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: _onPressed,
child: Icon(Icons.colorize),
),
body: Stack(
children: [
Positioned.fill(
child: BackgroundWidget(),
),
Center(
child: Container(
height: 150,
width: 150,
color: _currentColor,
),
),
],
),
);
}
}
class BackgroundWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('building `BackgroundWidget`');
return Image.network(
'https://cdn.pixabay.com/photo/2017/08/30/01/05/milky-way-2695569_960_720.jpg',
fit: BoxFit.cover,
);
}
}
你将会看到两条日志
flutter: building `MyHomePage`
flutter: building `BackgroundWidget`
每次点击按钮,我们都会刷新整个屏幕,脚手架scaffold,背景图widget。
重建整个widget不是一个好主意,我们只需要更新真正需要更新的部分。
大家都知可以用像是flutter_bloc, mobx, provider 等等来解决这个问题,但很少有人知道实际上不需要任何扩展包也可以。Flutter 框架本身已经提供了这些。
我们用ValueNotifier重构上面的代码。
class _MyHomePageState extends State<MyHomePage> {
final _colorNotifier = ValueNotifier<Color>(Colors.grey);
Random _random = new Random();
void _onPressed() {
int randomNumber = _random.nextInt(30);
_colorNotifier.value =
Colors.primaries[randomNumber % Colors.primaries.length];
}
@override
void dispose() {
_colorNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
print('building `MyHomePage`');
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: _onPressed,
child: Icon(Icons.colorize),
),
body: Stack(
children: [
Positioned.fill(
child: BackgroundWidget(),
),
Center(
child: ValueListenableBuilder(
valueListenable: _colorNotifier,
builder: (_, value, __) => Container(
height: 150,
width: 150,
color: value,
),
),
),
],
),
);
}
}
现在你不会再看到那些print出来的东西了。
当我没想要分离业务逻辑和视图时,我们也可以用notifier这个东西来做。
下面的例子使用了ChangeNotifier
//------ ChangeNotifier class ----//
class MyColorNotifier extends ChangeNotifier {
Color myColor = Colors.grey;
Random _random = new Random();
void changeColor() {
int randomNumber = _random.nextInt(30);
myColor = Colors.primaries[randomNumber % Colors.primaries.length];
notifyListeners();
}
}
//------ State class ----//
class _MyHomePageState extends State<MyHomePage> {
final _colorNotifier = MyColorNotifier();
void _onPressed() {
_colorNotifier.changeColor();
}
@override
void dispose() {
_colorNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
print('building `MyHomePage`');
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: _onPressed,
child: Icon(Icons.colorize),
),
body: Stack(
children: [
Positioned.fill(
child: BackgroundWidget(),
),
Center(
child: AnimatedBuilder(
animation: _colorNotifier,
builder: (_, __) => Container(
height: 150,
width: 150,
color: _colorNotifier.myColor,
),
),
),
],
),
);
}
}
这样就避免了最后两个widgets不必要的刷新。
3.使用尽可能的 const关键字
4.在长列表ListView里使用itemExtent属性
5.在动画AnimatedBuilder里避免重建rebuild
6.避免在动画里使用透明度
3-6下次有时间再做补充
原文:链接