接触过flutter的人,对StatefulWidget和StatelessWidget应该都很熟悉了. 大家都知道在flutter中几乎所有的对象都是一个Widget。与原生开发中“控件”不同的是,Flutter中的Widget的概念更广泛,它不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的 GestureDetector widget、用于APP主题数据传递的Theme等等,而原生开发中的控件通常只是指UI元素。而StatefulWidget和StatelessWidget就是继承自widget类.
- 一 . StatelessWidget和StatefulWidget的区别和概念
- StatelessWidget用来描述widget的类型为无状态的, 用于不需要维护状态的场景,它通常在build方法中通过嵌套其它Widget来构建UI,在构建过程中会递归的构建其嵌套的Widget.在整个生命周期中是不会发生改变的.
- StatefulWidget用来描述widget的类型为有状态的的,描述起来相对复杂 我们先来看一下StatefulWidget的定义
class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => new StatefulElement(this);
@protected
State createState();
}
在StatefulWidget类中添加了一个新的接口createState(), StatefulElement()方法执行过程中可能会多次调用createState()来创建状态(State)对象.createState()用于创建和Statefulwidget相关的状态,它在Stateful widget的生命周期中可能会被多次调用。例如,当一个Stateful widget同时插入到widget树的多个位置时,Flutter framework就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例.说到State类给大家说明一下,实现一个StatefulWidget类必须同时实现State类, 一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态.StatefulWidget类本身是不变的,但是State类在Widget生命周期中始终存在,如果想要页面发生改变,实际上就是调用State类中的setState()方法,在调用之后就会重新执行build()方法,达到刷新UI的目的.大家看一下State的生命周期,能更好的理解StatefulWidget是怎么实现刷新widget的
class _CounterWidgetState extends State<CounterWidget> {
int _counter;
@override
void initState() {
super.initState();
//初始化状态
_counter=widget.initValue;
print("initState");
}
@override
Widget build(BuildContext context) {
print("build");
return Scaffold(
body: Center(
child: FlatButton(
child: Text('$_counter'),
//点击后计数器自增
onPressed:()=>setState(()=> ++_counter,
),
),
),
);
}
@override
void didUpdateWidget(CounterWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget");
}
@override
void deactivate() {
super.deactivate();
print("deactive");
}
@override
void dispose() {
super.dispose();
print("dispose");
}
@override
void reassemble() {
super.reassemble();
print("reassemble");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
}
这就是一个State生命周期,其他的方法就不在这里说明了,大家有兴趣或者还是不太懂的话可以专门搜一下关于State的文章学习一下,能帮助你更好的理解这部分内容.
在这里多插一个知识点,大家可能注意到每个build方法里都有一个context参数, 它是BuildContext类的一个实例,表示当前widget在widget树中的上下文,每一个widget都会对应一个context对象,ontext是当前widget在widget树中位置中执行”相关操作“的一个句柄它提供了从当前widget开始向上遍历widget,树以及按照widget类型查找父级widget的方法.
class ContextRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Context测试"),
),
body: Container(
child: Builder(builder: (context) {
// 在Widget树中向上查找最近的父级`Scaffold` widget
Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
// 直接返回 AppBar的title, 此处实际上是Text("Context测试")
return (scaffold.appBar as AppBar).title;
}),
),
);
}
}
简单的概括就是说在子类中可以通过context参数获取到父级中的一些信息.大家大致有一个概念就可以,这里就不详细介绍了, 以后可能会专门写一篇来详细说明Context的一些用法,以及BuildContext的更多内容介绍.
总结: 因为我想尽可能的和大家在专业的角度去阐述他们的概念和区别,可能有些刚接触flutter的朋友对我说的东西没有太理解,或者是没耐心看我上面的长篇大论.那么大家可以只看这一段我的概括.这里我比较笼统的概括一下他们的区别以及使用场景.StatelessWidget就是描述一个无状态的widget,初始化之后就无法改变了,可以使用在一些静态页面上,比如显示一段文字或者是一张图片等.StatefulWidget就是描述一个有状态的widget,他可以对widget进行状态的改变,比如网络请求刷新一个列表,点击一个button改变text的内容等.
-
二 . 不要滥用StatefulWidget
有些初学者会有这样一个问题, 既然StatefulWidget功能这么强大,那就干脆都只用StatefulWidget好了,为什么还要使用鸡肋的StatelessWidget.这就涉及到本文的第二个知识点了,就是StatefulWidget并不是万金油的存在,它看似强大,但是如果滥用的话也会对内存有很大的消耗.
上面文章提到了想改变widget就要执行
setState()方法, 调用之后会出发State.build.但是这也会间接触发每个子控件的构造方法以及build方法如果你的根布局是一个StatefulWidget,那么每在根State中调用一次setState(),都将是一次整页所有Widget的rebuild,通俗的说就接近于你重新创建一遍控件,但是这么说只是让大家方便理解,大家不要就认为他是重新创建,因为他和重新创建还是有一些差别.下面给大家举个例子.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'1111111',
),
Text(
'222222',
),
Text(
'333333---' + _counter.toString(),
),
TestCirculating()
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class TestCirculating extends StatelessWidget {
@override
Widget build(BuildContext context) {
for (int i = 0; i < 200; i++) {
print("模拟耗时操作 :" + i.toString());
}
return Container(
child: Text("4444444"),
);
}
}
利用创建flutter工程系统自带的demo,写了一个小例子,然后为了大家直观的看到滥用又StatefulWidget的后果,模拟了一下耗时操作.下面是界面效果图,点击+号按钮, 第三行文字后面的0每点击一次都会+1
现在手机性能都很好,我模拟的一个for循环对于手机资源暂用可以说是微乎其微,只是让大家明白这个道理,真正项目有可能会遇到很大量的数据,如果整个页面都重新执行的话那对手机资源占用是很大的.为了更好的理解,大家可以把根widget想象成是一颗大树,里面的子控件想象成是树上的一条条树枝.你如果想去让其中一条树枝晃动,有两种方法,一种是晃动一颗大树,另一种是单独晃动你想晃动的树枝.我上面介绍的方法就是第一种晃动大树的方法,这种方法显然是不可取的,会对你体能造成很大的消耗.所以我们要做的就是单独晃动一条树枝,可以把控件单独抽出来,父级继承还是继承自StatelessWidget, 需要作出变化的控件单独继承StatefulWidget类,然后单独对一个控件进行刷新就可以了.