我是 flutter 的新手,我获取 StatefulWidget 状态的方式是向小部件添加状态属性,例如:
// ignore: must_be_immutable
class _CustomContainer extends StatefulWidget {
_CustomContainer({Key key}) : super(key: key);
@override
__CustomContainerState createState() {
state = __CustomContainerState();
return state;
}
__CustomContainerState state;
void changeColor() {
if (state == null) return;
// call state's function
this.state.changeColor();
}
}
class __CustomContainerState extends State<_CustomContainer> {
var bgColor = Colors.red;
@override
Widget build(BuildContext context) {
return Container(
width: 200,
height: 200,
color: bgColor,
);
}
void changeColor() {
setState(() {
bgColor = Colors.blue;
});
}
}
用法:
final redContainer = _CustomContainer();
final button = FlatButton(
onPressed: () {
// call widget function
redContainer.changeColor();
},
child: Text('change color'),
);
有效,但不知道有没有什么隐患?
答案 0 :(得分:2)
您会注意到以命令式方式操作 Flutter 小部件非常尴尬,就像在问题中一样。这是因为 Flutter 在构建屏幕时采用了声明式方法。
Flutter UI 的方法/哲学是声明式 UI 与命令式 UI。
上述问题中的示例倾向于命令式方法。
声明式方法:
下面我尝试将上面的命令式方法转换为声明式方法。
CustomContainer
是用 color
声明;状态已知/保持在CustomContainer
之外并在其构造中使用。
施工后,您不能强加对 CustomContainer
进行颜色更改。在命令式框架中,您将公开一个方法 changeColor(color)
并调用该方法,该框架将执行魔术以显示新颜色。
在 Flutter 中,要更改 CustomContainer
的颜色,您声明 CustomContainer
为新颜色。
import 'package:flutter/material.dart';
/// UI object holds no modifiable state.
/// It configures itself once based on a declared color.
/// If color needs to change, pass a new color for rebuild
class CustomContainer extends StatelessWidget {
final Color color;
CustomContainer(this.color);
@override
Widget build(BuildContext context) {
return Container(
color: color,
child: Text('this is a colored Container'),
);
}
}
/// A StatefulWidget / screen to hold state above your UI object
class DeclarativePage extends StatefulWidget {
@override
_DeclarativePageState createState() => _DeclarativePageState();
}
class _DeclarativePageState extends State<DeclarativePage> {
var blue = Colors.blueAccent.withOpacity(.3);
var red = Colors.redAccent.withOpacity(.3);
Color color;
// state (e.g. color) is held in a context above your UI object
@override
void initState() {
super.initState();
color = blue;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Declarative Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomContainer(color),
// color "state" ↑ is passed in to build/rebuild your UI object
RaisedButton(
child: Text('Swap Color'),
onPressed: () {
setState(() {
toggleColor();
});
}
)
],
),
),
);
}
void toggleColor() {
color = color == blue ? red : blue;
}
}
在 declarative vs imperative on Flutter.dev 上阅读更多内容。
在性能方面,当一个小部件(位于小部件树的下方)需要重建时,重建整个屏幕似乎很浪费。
如果可能(并且明智),最好将具有状态和需要重建的特定元素包装在 StatefulWidget 中,而不是将整个页面包装在 StatefulWidget 中并重建所有内容。 (很可能,这甚至不会成为问题,我将在下面进一步讨论。)
下面我修改了上面的示例,将 StatefulWidget 从整个 DeclarativePage 移动到 ChangeWrapper 小部件。
ChangeWrapper 将包装 CustomContainer(改变颜色)。
DeclarativePage 现在是一个 StatelessWidget,在切换 CustomContainer 的颜色时不会重建。
import 'package:flutter/material.dart';
class ChangeWrapper extends StatefulWidget {
@override
_ChangeWrapperState createState() => _ChangeWrapperState();
}
class _ChangeWrapperState extends State<ChangeWrapper> {
final blue = Colors.blueAccent.withOpacity(.3);
final red = Colors.redAccent.withOpacity(.3);
Color _color; // this is state that changes
@override
void initState() {
super.initState();
_color = blue;
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomContainer(_color),
RaisedButton(
child: Text('Swap Color'),
onPressed: () {
setState(() {
toggleColor();
});
}
)
],
);
}
void toggleColor() {
_color = _color == blue ? red : blue;
}
}
/// UI object holds no state, it configures itself once based on input (color).
/// If color needs to change, pass a new color for rebuild
class CustomContainer extends StatelessWidget {
final Color color;
CustomContainer(this.color);
@override
Widget build(BuildContext context) {
return Container(
color: color,
child: Text('this is a colored Container'),
);
}
}
class DeclarativePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('Declarative Page re/built');
return Scaffold(
appBar: AppBar(
title: Text('Declarative Page'),
),
body: Center(
child: ChangeWrapper(),
),
);
}
}
在运行此版本的代码时,通过按下按钮交换颜色时,仅重建 ChangeWrapper 小部件。您可以查看“声明性页面重新/构建”的控制台输出,该输出仅在第一次屏幕构建/查看时写入调试控制台一次。
如果 DeclarativePage 很大,有数百个小部件,以上述方式隔离小部件重建可能很重要或有用。在小屏幕上,例如顶部的第一个示例,甚至是带有几十个小部件的平均屏幕,节省的差异可能可以忽略不计。
Flutter 旨在以每秒 60 帧的速度运行。如果您的屏幕可以在 16 毫秒内构建/重建所有小部件(1000 毫秒/60 帧 = 每帧 16.67 毫秒),那么用户将不会看到任何问题。
当您使用动画时,这些动画设计为以每秒 60 帧(滴答)的速度运行。即动画中的小部件将在动画运行的每一秒内重建 60 次。
这是正常的 Flutter 操作,它是为它设计和构建的。因此,当您考虑是否可以优化您的小部件架构时,考虑其上下文或该组小部件将如何使用是很有用的。如果小部件组不在动画中,则在每个屏幕渲染或每个人点击按钮时构建一次...优化可能不是一个大问题。
动画中的一大组小部件......很可能是考虑优化和性能的好选择。
顺便说一句,this video series is a good overview of the Flutter architecture。我猜 Flutter 有很多隐藏的优化,例如当 Widget 没有实质性/实质性改变时重新使用 Element,以节省 CPU 周期实例化、渲染和定位 Element 对象。
答案 1 :(得分:1)
在要添加状态的地方添加 setState() 方法