官方文档
Stateful & stateless
在 Flutter 中, Widget
分为两种,一种是有状态的称为 StatefulWidget
,一种是无状态的称为 StatelessWidget
。例如 Checkbox
复选框是 StatefulWidget
(具有可变状态的小部件),而 Text
部件就是 StatelessWidget
(不需要可变状态的小部件)。
我们自定义 StatefulWidget
的时候,会重写 createState()
方法来创建一个 State
对象,在 State
中可以通过 setState((){...});
方法来改变当前 Widget
的状态,而 StatelessWidget
则不可以。
创建一个 StatefulWidget
实例的方法如下:
class FavoriteWidget extends StatefulWidget {
// 创建 State 对象
@override
State createState() {
return _FavoriteWidgetState();
}
}
// 用来管理 FavoriteWidget 的状态
class _FavoriteWidgetState extends State<FavoriteWidget> {
bool _isFavorited = true;
void _toggleFavorite() {
// 改变状态
setState(() {
_isFavorited = !_isFavorited
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
// 这里可以根据 _isFavorited 去改变改变部分 widget 的状态
...
);
}
}
复制代码
在 Dart 中,成员变量或者类名称以 下划线 开头表示该成员或者类为 private 的。 官方文档
管理 Widget 的状态
Flutter提供了一下几种方式管理 widget 的状态
widget
状态由自己管理- 由父
widget
管理widget
的状态 - 混合管理
widget
的状态 (自己和父部件各管理一部分)
widget 状态由自己管理
这个比较简单,直接在 widget
内部管理自己的状态
// 有可变状态的 widget
class TapboxA extends StatefulWidget {
TapboxA({Key key}) : super(key: key) {
print('TapboxA init');
}
// 创建 State 用来管理当前 widget 的状态
@override
_TapboxAState createState() => _TapboxAState();
}
// State
class _TapboxAState extends State<TapboxA> {
_TapboxAState() {
print('boxA state init');
}
bool _active = false;
void _handleTap() {
print('boxA state handleTap is call');
// 设置状态
setState(() {
_active = !_active;
});
}
Widget build(BuildContext context) {
print('boxA state build is call');
return GestureDetector(
onTap: _handleTap, // 点击事件
child: Container(
child: Center(
// 根据 _active 来设置不同的 Text 值
child: Text(
_active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: _active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
//------------------------- MyApp ----------------------------------
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: Center(
child: TapboxA(),
),
),
);
}
}
复制代码
效果如下:
初始化顺序:
flutter: TapboxA init
flutter: boxA state init
flutter: boxA state build is call
复制代码
点击事件被触发时:
flutter: boxA state handleTap is call
flutter: boxA state build is call
复制代码
由父 widget 管理
案例中的 TapboxB
是一个 StatelessWidget
, ParentWidget
是一个 StatefulWidget
。
我们需要使用 ParentWidget
来改变 TapboxB
的状态。
//------------------------- parent widget ----------------------------------
class ParentWidget extends StatefulWidget {
ParentWidget() {
print('ParentWidget init');
}
@override
State createState() {
return _ParentWidgetState();
}
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
_ParentWidgetState() {
print('ParentWidgetState init');
}
void _handleTapboxChanged(bool newValue) {
print('parent _handleTapboxChanged is call : $newValue');
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
print('ParentWidgetState build is call');
return MaterialApp(
title: 'ParentWidget',
theme: ThemeData(
primaryColor: Colors.redAccent
),
home: Scaffold(
appBar: AppBar(
title: Text('ParentWidget'),
),
body: Center(
child: TapboxB(onChanged: _handleTapboxChanged, active: _active,),
),
),
);
}
}
//------------------------- child widget ----------------------------------
class TapboxB extends StatelessWidget {
TapboxB({Key key, this.active: false, @required this.onChanged})
: super(key: key) {
print('Tap boxB init : ${this.active}');
}
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
print('child _handleTap : $active');
onChanged(!active);
}
@override
Widget build(BuildContext context) {
print('Tap boxB build method');
return GestureDetector(
onTap: _handleTap,
child: Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600]
),
child: Center(
child: Text(active ? 'Android' : 'Flutter',
style: TextStyle(fontSize: 32.0, color: Colors.white),),
),
),
);
}
}
复制代码
可以看到,TapboxB
的构造方法中有一个 onChanged
参数,和 active
参数;onChanged
参数类型为 ValueChanged
他是一个接受一个参数的回调方法,方法源码如下:
/// Signature for callbacks that report that an underlying value has changed.
///
/// See also [ValueSetter].
typedef void ValueChanged(T value);
复制代码
在 TapboxB
中,当触发点击事件的时候就是执行这个回调方法,也就是上述代码中 _handleTap()
方法中所执行的语句。而 active
用来切换当前 TapboxB
的状态。
注意:onChanged 和 active 都是由父部件传递过来的
效果如下:
然后我们来看看输出:
初始化时输出如下:
Performing hot restart...
flutter: ParentWidget init
Restarted app in 1,976ms.
flutter: ParentWidgetState init
flutter: ParentWidgetState build is call
flutter: Tap boxB init : false
flutter: Tap boxB build method
复制代码
执行顺序如下:
我们再看看子部件(TapboxB)触发点击事件时的输出
flutter: child _handleTap : false
flutter: parent _handleTapboxChanged is call : true
flutter: ParentWidgetState build is call
flutter: Tap boxB init : true
flutter: Tap boxB build method
复制代码
执行顺序如下:
说明:当我们触发子部件上的点击事件时候,这个时候会执行 _handleTap()
方法,_handleTap()
方法里面会执行 onChanged(...)
,接着就会执行父部件里面的回调方法 _handleTapboxChanged(...)
,注意 _handleTapboxChanged(...)
方法里面执行了 setState(() {...})
,在这个方法里面切换了状态,然后会重新调用 build
方法重新渲染子部件。
setState(() {...})
会使 widget 重绘,类似于 Android 中调用 View 的invalidate()
方法
混合管理 widget 的状态
混合管理就是某些状态由自己管理,某些状态由父部件来管理。
下面的例子就是一个混合管理状态的例子,部件 TabboxC
在被点击时有三个状态变换,背景色,文字和边框。
示例中,背景色和文字的状态交由父部件来管理(和上一个示例类似),而边框状态由自己管理。
既然父部件和子部件都能管理状态,那么它们都是要继承StatefulWidget
类。
// ------------parent widget-----------
class ParentWidget2 extends StatefulWidget {
ParentWidget2() {
print('Parent init');
}
@override
State createState() {
return _ParentWidgetState2();
}
}
class _ParentWidgetState2 extends State<ParentWidget2> {
_ParentWidgetState2() {
print('_Parent State init');
}
bool _active = false;
void _handleTapboxChanged(bool newValue) {
print('_Parent _handleTapboxChanged method is called');
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
print('_Parent State build is called');
return TabboxC(onChanged: _handleTapboxChanged, active: _active,);
}
}
// ------------child widget-----------
class TabboxC extends StatefulWidget {
// 构造方法
TabboxC({
Key key,
this.active: false,
@required this.onChanged
}) : super(key: key) {
print('TabboxC init');
}
final bool active;
final ValueChanged<bool> onChanged;
@override
State createState() {
return _TapboxCState();
}
}
class _TapboxCState extends State<TabboxC> {
bool _highlight = false;
_TapboxCState() {
print('_TapboxC State init');
}
void _handleTapDown(TapDownDetails details) {
print('_TapboxC tap down');
setState(() {
_highlight = true;
});
}
void _handleTapUp(TapUpDetails details) {
print('_TapboxC tap up');
setState(() {
_highlight = false;
});
}
void _handleTapCancel() {
print('_TapboxC tap cancel');
setState(() {
_highlight = false;
});
}
void _handleTap() {
print('_TapboxC tap clicked');
widget.onChanged(!widget.active);
}
@override
Widget build(BuildContext context) {
print('_TapboxCState build is called');
return MaterialApp(
title: 'mix',
theme: ThemeData(
primaryColor: Colors.redAccent
),
home: Scaffold(
appBar: AppBar(
title: Text('mix'),
),
body: Center(
child: GestureDetector(
// down
onTapDown: _handleTapDown,
// up
onTapUp: _handleTapUp,
// cancel
onTapCancel: _handleTapCancel,
// click
onTap: _handleTap,
child: Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
// Box 颜色 父控件 控制(通过回调方法)
color: widget.active ? Colors.lightGreen[700] : Colors
.grey[600],
// 边框颜色 自己控制
border: _highlight ? Border.all(
color: Colors.teal[700], width: 10.0) : null
),
child: Center(
child: Text(widget.active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 32.0, color: Colors.white),),
),
),
),
)
),
);
}
}
复制代码
效果如下:
初始化时候的顺序和上面类似,我们来看看点击事件被触发时候的执行顺序:
flutter: _TapboxC tap down
flutter: _TapboxCState build is call
flutter: _TapboxC tap up
flutter: _TapboxC tap clicked
flutter: _Parent _handleTapboxChanged method is call
flutter: _Parent State build is call
flutter: TabboxC init
flutter: _TapboxCState build is call
复制代码
执行流程如下:
大家可能会发现,子部件在 Down 事件中调用了
setState(...)
方法,然后执行了一次 build 操作;而在 Up 事件中同样也调用了setState(...)
方法,但是为什么没有执行 build 操作,而是直接执行了 click 操作。这里面可能和 Android 里面类似,在 View 的 onTouchEvent 方法里面,onClick 方法也是在 ACTION_UP 里面执行的。
如有错误,还请指出,谢谢!