前言
简单粗暴来说,一个widget,如果在运行的过程中需要变化,就是有状态的。
Fluter的widget,安卓状态来说,可以分为:有状态(StatefulWidget)和无状态(StatelessWidget)
无状态(StatelessWidget)
状态组件指的就是其内部的状态是来自其父组件
并使用final类型的变量来存储
,当组件被build
的时候它们就使用这些不可变的数据
来构建自己的UI。
无状态的组件,前面我们已经使用无数次了,重点是看StatefulWidget,有状态的组件。
有状态(StatefulWidget)
有状态的wdiget,其持有状态可能在Widget生命周期中发生变化。
Checkbox、Radio、Slider、InkWell、Form和TextField是有状态小部件的示例,它们是StatefulWidget的子类。
对于有可变状态控件的管理,官方文档是写了有3种模式:
控件自己管理状态
、交给父控件管理状态
以及混合管理 (自己和父部件各管理一部分)
。
实现一个StatefulWidget至少需要两个类:
一个StatefulWidget
类
一个State
类。
- StatefulWidget类本身是不变的,但是State类在Widget生命周期中始终存在,当在State内部改变任何子控件需要的变量时,都需要使用
setState
。
一句话来说,就是一个有状态的组件,需要两个类,分别是StatefulWidget和State,而状态组件的内容,需要用到setState方法。
一、 有状态(StatefulWidget)
实现StatefulWidget需要的两个类
实现一个StatefulWidget至少需要两个类:
一个StatefulWidget
类
一个State
类。
如何改变StatefulWidget的内容
- StatefulWidget类本身是不变的,但是State类在Widget生命周期中始终存在,当在State内部改变任何子控件需要的变量时,都需要使用
setState
。
例子
例子1 控件自己管理状态
一个计数器,点击自身,统计点击次数。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
// final wordPair = new WordPair.random();
return MaterialApp(
title: 'Flutter 测试标题',
theme: new ThemeData(
primaryColor: Colors.red,
),
home: new Scaffold(
appBar: AppBar(
title: Text("测试呀"),
),
body: new Counter(),
));
}
}
class Counter extends StatefulWidget {
// 经典写法,继承自StatefulWidget的Widget返回一个自己的State私有类
_CounterState createState() => new _CounterState();
}
class _CounterState extends State<Counter> {
// State类里面定义私有变量,我们的状态变化,会通过setState方法具体操作
int _count = 0;
void _increment() {
setState(() {
++_count;
});
}
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(color: Colors.grey[100]),
child: new Center(
child: new RaisedButton(
// 按下时的事件
onPressed: _increment,
// 内容不写死,数据动态显示,一切都是因为_increment里面的setState方法
child: new Text('click count : ${_count}'))));
}
}
.
.
重要都在备注里面了,如果非要说说逻辑,大概就是:
- 1、继承自StatefulWidget的Widget返回一个自己的State私有类
- 2、State类里面定义私有变量,我们的状态变化,会通过setState方法具体操作
- 3、通过一定的事件或者条件,触发setState改变数据
- 4、数据展示的逻辑,不写死,根据变量的调整动态调整
.
.
例子2 父控件管理子控件状态
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
// final wordPair = new WordPair.random();
return MaterialApp(
title: 'Flutter 测试标题',
theme: new ThemeData(
primaryColor: Colors.red,
),
home: new Scaffold(
appBar: AppBar(
title: Text("测试呀"),
),
body: new ParentWidget(),
));
}
}
//------------------------- parent widget ----------------------------------
class ParentWidget extends StatefulWidget {
ParentWidget() {
print('ParentWidget init');
}
@override
State<StatefulWidget> createState() {
return _ParentWidgetState();
}
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
_ParentWidgetState() {
print('ParentWidgetState init');
}
void _handleSonChanged(bool newValue) {
print('parent _handleSonChanged is call : $newValue');
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
print('ParentWidgetState build is call');
return new Center(
child: SonWidget(onChanged: _handleSonChanged, isShowAndroid: _active,),
);
}
}
//------------------------- child widget ----------------------------------
class SonWidget extends StatelessWidget {
SonWidget({
Key key, this.isShowAndroid: false,
@required this.onChanged
}): super(key: key) {
print('Son SonWidget init : ${this.isShowAndroid}');
}
final bool isShowAndroid;
final ValueChanged<bool> onChanged;
void _handleSon() {
print('child _handleSon : $isShowAndroid');
onChanged(!isShowAndroid);
}
@override
Widget build(BuildContext context) {
print('Son SonWidget build method');
return GestureDetector(
onTap: _handleSon,
child: Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: isShowAndroid ? Colors.lightGreen[700] : Colors.grey[600]
),
child: Center(
child: Text(isShowAndroid ? 'Android' : 'Flutter',
style: TextStyle(fontSize: 32.0, color: Colors.white),),
),
),
);
}
}
核心:子控件的构造方法,暴露出事件参数,父控件创建子控件的时候,传入参数,进而实现父控件改变自控内容的目的。
所以核心代码:
初始化顺序
初始化输出日志:
flutter: ParentWidget init
flutter: ParentWidgetState build is call
flutter: Son SonWidget init : false
flutter: Son SonWidget build method
Reloaded 1 of 432 libraries in 2,751ms.
点击回调执行顺序
点击输出日志:
第一次点击
flutter: child _handleSon : false
flutter: parent _handleSonChanged is call : true
flutter: ParentWidgetState build is call
flutter: Son SonWidget init : true
flutter: Son SonWidget build method
第二次点击
flutter: child _handleSon : true
flutter: parent _handleSonChanged is call : false
flutter: ParentWidgetState build is call
flutter: Son SonWidget init : false
flutter: Son SonWidget build method
第三次点击
flutter: Son SonWidget init : false
flutter: Son SonWidget build method
flutter: child _handleSon : false
flutter: parent _handleSonChanged is call : true
flutter: ParentWidgetState build is call
flutter: Son SonWidget init : true
flutter: Son SonWidget build method
一切是那么重新,在父控件控制子控件的时,刷新ui会让子控件重新初始化和和build(绘制)。
.
.
.
.
例子3 混合管理
也就是,父控件管,子控件自己也管,一起管
混合管理就是某些状态由自己管理,某些状态由父部件来管理。
下面的例子就是一个混合管理状态的例子,部件 TabboxC 在被点击时有三个状态变换,背景色,文字和边框。
示例中,背景色和文字的状态交由父部件来管理(和上一个示例类似),而边框状态由自己管理。
既然父部件和子部件都能管理状态,那么它们都是要继承StatefulWidget类。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
// final wordPair = new WordPair.random();
return MaterialApp(
title: 'Flutter 测试标题',
theme: new ThemeData(
primaryColor: Colors.red,
),
home: new Scaffold(
appBar: AppBar(
title: Text("测试呀"),
),
body: new ParentWidget2(),
));
}
}
// ------------parent widget-----------
class ParentWidget2 extends StatefulWidget {
ParentWidget2() {
print('Parent init');
}
@override
State<StatefulWidget> 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<StatefulWidget> 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 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 里面执行的。
END