我刚开始使用StatefulWidget的状态,它的方法是在小部件中添加一个state属性,例如:
// 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'),
);它有效,但我想知道有什么隐藏的危险吗?
发布于 2021-03-02 05:07:47
您会注意到,以命令式的方式操作颤振部件是非常尴尬的,就像在问题中一样。这是因为Flutter采用了声明式的方法来构建屏幕。
声明性与祈使性
颤振UI的方法/哲学是声明性UI与命令式UI。
上面问题中的例子倾向于一种命令式的方法。
声明性方法:
下面,我尝试将上面的命令式方法转换为声明式方法。
CustomContainer是用color声明的;状态已知/保持在CustomContainer之外&用于其构造。
在构建之后,您不能在CustomContainer上强制进行颜色更改。在命令式框架中,您将公开一个方法,changeColor(color)并调用该方法,该框架将神奇地显示一个新的颜色。
在颤振中,要更改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;
}
}阅读有关Flutter.dev上的声明式和命令式的更多信息。
setState()重构与性能
就性能而言,在小部件树下的单个小部件需要重建时,重新构建整个屏幕似乎是浪费的。
如果可能(也是合理的),最好将具有状态的特定元素包装起来&需要在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具有数百个小部件,那么以上述方式重新构建小部件可能是非常重要或有用的。在小屏幕上,如第一个例子中的顶部,甚至是具有几十个小部件的平均屏幕上,节省的差别很可能可以忽略不计。
颤振设计为每秒60帧。如果屏幕能够在16毫秒内构建/重建所有小部件(1000毫秒/ 60帧=16.67ms/帧),用户将不会看到任何jankiness。
当你使用动画时,它们被设计成每秒运行60帧(滴答)。也就是说,动画中的小部件将被重建60次,每秒钟动画运行。
这是正常的颤振操作,它是为之设计和建造的。因此,当您考虑是否可以优化您的小部件体系结构时,考虑它的上下文或如何使用这组小部件是很有用的。如果小部件组不在动画中,则每个屏幕呈现或每个人工按钮点击生成一次.优化可能不是什么大问题。
动画中的一大组小部件。可能是考虑优化和性能的好人选。
顺便说一下,这个视频系列是颤振体系结构的一个很好的概述。.为了节省CPU周期,实例化、渲染和定位元素对象,我猜Flutter有很多隐藏的优化,比如当Widget没有实质性的/实质性的改变时重新使用元素。
发布于 2021-03-02 04:21:17
添加要添加状态的setState()方法
https://stackoverflow.com/questions/66432893
复制相似问题