响应式的编程框架中都会有一个永恒的主题——“状态管理”,不管是React/Vue(二者都是支持响应式编程的Web开发框架)仍是Flutter,讨论的问题和解决的思想都是一致的。因此,若是你对React/Vue的状态管理有了解,能够跳过本节。言归正传,咱们想一个问题,Stateful Widget的状态应该被谁管理?是Widget自己?是父Widget?仍是都会?亦或是另外一个对象?答案是:取决于实际状况!编程
如下是管理状态的最多见的方式:redux
如下原则能够帮助你决定如何决定使用哪一种管理方式?bash
在Widget内部管理状态封装性会好一些,而在父Widget中管理会比较灵活。有些时候,若是不肯定到底该由谁来管理状态,那么首选由父Widget来管理(由于灵活会显得更重要一些)。框架
接下来,咱们将经过建立三个简单示例TapboxA、TapboxB和TapboxC来讲明管理状态的不一样方式。这些例子的功能是类似的——建立一个盒子,当点击它时,盒子背景会在绿色与灰色之间切换,状态_active
肯定颜色:绿色为true
,灰色为false
。less
下面的例子将使用GestureDetector
来识别点击事件,关于该GestureDetector
的详细内容咱们将在后续进行讲解。ide
_TapboxAState
类:函数
_active
:肯定盒子的当前颜色的布尔值;_handleTap()
函数,该函数在点击该盒子时更新_active
,并调用setState()
更新UI;// TapboxA 管理自身状态.
//------------------------- TapboxA ----------------------------------
class TapboxA extends StatefulWidget {
TapboxA({Key key}) : super(key: key);
@override
_TapboxAState createState() => new _TapboxAState();
}
class _TapboxAState extends State<TapboxA> {
bool _active = false;
void _handleTap() {
setState(() {
_active = !_active;
});
}
Widget build(BuildContext context) {
return new GestureDetector(
onTap: _handleTap,
child: new Container(
child: new Center(
child: new Text(
_active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: _active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
复制代码
对于父Widget来讲,管理状态并告诉其子Widget什么时候更新一般是比较好的方式。例如,IconButton是一个图片按钮,但它是一个无状态的Widget,由于咱们认为父Widget须要知道该按钮是否被点击从而采起相应的处理。动画
在如下示例中,TapboxB经过回调将其状态导出到其父项。因为TapboxB无论理任何状态,所以它的父类为StatefulWidget,TapboxB为StatelessWidget。ui
ParentWidgetState
类:this
_active
状态;_handleTapboxChanged()
,当盒子被点击时调用的方法;setState()
更新UI。TapboxB
类:
StatelessWidget
类,由于全部状态都由其父Widget处理;// ParentWidget 为 TapboxB 管理状态.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => new _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return new Container(
child: new TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
TapboxB({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context) {
return new GestureDetector(
onTap: _handleTap,
child: new Container(
child: new Center(
child: new Text(
active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
复制代码
对于一些Widget来讲,混合管理的方式很是有用,在这种状况下,Widget自身管理一些内部状态,而父Widget管理一些其它外部状态。
在下面TapboxC示例中,按下时,盒子的周围会出现一个深绿色的边框,抬起时,边框消失,点击生效,盒子的颜色改变。TapboxC将其_active
状态导出到其父Widget中,但在内部管理其_highlight
状态。这个例子有两个状态对象_ParentWidgetCState
和_TapboxCState
。
_ParentWidgetCState
对象:
_active
状态;_handleTapboxChanged()
,当盒子被点击时调用;_active
状态改变时调用setState()
方法更新UI。_TapboxCState
对象:
_highlight
状态;GestureDetector
监听全部Tap事件,当用户按下时,添加高亮(深绿色边框),当用户抬起(释放按下)时,会移除高亮;_highlight
状态,调用setState()
方法更新UI;//---------------------------- ParentWidget ----------------------------
class ParentWidgetC extends StatefulWidget {
@override
_ParentWidgetCState createState() => new _ParentWidgetCState();
}
class _ParentWidgetCState extends State<ParentWidgetC> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return new Container(
child: new TapboxC(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//----------------------------- TapboxC ------------------------------
class TapboxC extends StatefulWidget {
TapboxC({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
@override
_TapboxCState createState() => new _TapboxCState();
}
class _TapboxCState extends State<TapboxC> {
bool _highlight = false;
void _handleTapDown(TapDownDetails details) {
setState(() {
_highlight = true;
});
}
void _handleTapUp(TapUpDetails details) {
setState(() {
_highlight = false;
});
}
void _handleTapCancel() {
setState(() {
_highlight = false;
});
}
void _handleTap() {
widget.onChanged(!widget.active);
}
@override
Widget build(BuildContext context) {
// 在按下时添加绿色边框,当抬起时,取消高亮
return new GestureDetector(
onTapDown: _handleTapDown, // 处理按下事件
onTapUp: _handleTapUp, // 处理抬起事件
onTap: _handleTap,
onTapCancel: _handleTapCancel,
child: new Container(
child: new Center(
child: new Text(widget.active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0, color: Colors.white)),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
border: _highlight
? new Border.all(
color: Colors.teal[700],
width: 10.0,
)
: null,
),
),
);
}
}
复制代码
当应用中包括一些跨Widget(甚至跨路由)的状态须要同步时,上面介绍的方法就很难胜任了。好比,有一个设置页,里面能够设置应用语言,可是为了让设置实时生效,咱们指望在语言状态发生改变时,APP Widget可以从新构建(build
),可是APP Widget和设置页并不在一块儿,那怎么办呢?正确的作法是经过一个全局状态管理器来处理这种“相距较远”的Widget之间的通讯。
目前只要有两种方法:
initState()
方法中订阅语言状态改变的事件,当用户在设置页切换语言状态后,触发语言状态改变事件,而后APP Widget就会收到通知,接着从新构建(build
)一下便可。关于全局事件总线的实现,咱们后续进行。