文章目录
- 一、目标
- 二、新建计数器项目
- 2.1 创建项目,设置项目名及配置SDK
- 2.2 设置包名
- 2.3 运行项目
- 2.4 计数器
- 三、项目结构分析
- 3.1 导包
- 3.2 应用入口
- 3.3 应用结构
- 3.4 首页
- 3.5 _MyHomePageState详解
- 3.6 流程小结
- 四、解惑build为何放在State中
一、目标
通过一个计数器示例,对Flutter
应用程序结构有个基本了解,对其概念和技术有一定的认识,也方便后续对Flutter
其他概念和技术的学习。
二、新建计数器项目
此过程比较简单,通过Android Studio
创建新的Flutter
工程即可。
2.1 创建项目,设置项目名及配置SDK
2.2 设置包名
在此,仅勾选了Kotlin support
,如果在iOS上需要使用Swift
的话,在此勾选即可。
2.3 运行项目
至此,工程创建完毕。在Terminal
窗口,运行flutter run
即可将应用安装到当前连接的手机上。
2.4 计数器
机智的你,也许发现,此时点击程序右下角的"➕",页面中的数字会一直增加,简单吧,不需要手写一行代码,一个计数器应用就完成了。
三、项目结构分析
Flutter
是采用Dart
语言来编写的,该示例项目中,主要Dart
代码在lib/main.dart
中,接下来就对该文件中的代码进行分析。
3.1 导包
import 'package:flutter/material.dart';
该行代码主要导入Material UI
组件库。Material
是一种标准的移动端和web
端的视觉设计语言,Flutter
默认提供了一套丰富的Material
风格的UI
组件。
3.2 应用入口
void main() => runApp(MyApp());
Flutter
应用中,main
函数是整个应用程序的入口。main
函数中调用了runApp
方法,其功能是启动Flutter
应用,接收一个widget
参数,在该示例中,它是MyApp
类的一个实例,该参数代表Flutter
应用。
main函数使用了=>
符号,这是Dart
中单行函数或方法的简写。
3.3 应用结构
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
-
MyApp
类代表Flutter
应用,它继承了StatelessWidget
类,也意味着应用本身也是一个widget
- 在
Flutter
中,大多数东西都是widget
,包括对其(alignment
)、填充(padding
)和布局(layout
) -
Flutter
在构建页面时,会调用组建的build
方法,widget
的主要工作是提供一个build()
方法来描述如何构建UI
界面,通常是通过组合、拼装其他基础widget
-
MaterialApp
是Material
库提供的Flutter App
框架,通过它可以设置应用的名称、主题、首页以及路由列表等,MaterialApp
也是一个widget
-
home
为Flutter
应用的首页,它也是一个widget
3.4 首页
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
...
}
MyHomePage
是应用的首页,它继承自StatefulWidget
类,表示他是一个有状态的widget(Stateful widght)
,目前,可以简单认为Stateful Widget
和Stateless Widget
有两点不同:
a. Stateful widget
可以拥有状态,这些状态在widget生命周期中时可以变的,而Stateless widget
是不可变的
b. Stateful widget
至少由两个类组成:
- 一个
StatefulWidget
类 - 一个
State
类,StatefulWidget
类本身是不可变的,但State类中持有的状态在widget
生命周期中可能会发生变化
_MyHomePageState
类时MyHomePage
类对应的状态类。看到这里,也许机智的你已经发现,和MyApp
类不同,MyHomePage
类中没有build
方法,取而代之的是,build
方法被挪到了_MyHomePageState
方法中,至于为什么这么做,先留个疑问,在分析完完整代码后再来解答。
3.5 _MyHomePageState详解
接下来,一起看看_MyHomePageState
中都包含哪些东西。
- 状态
int _counter = 0;
_counter
为保存屏幕右下角"➕"号按钮点击次数的状态。- 设置状态的自增函数
void _incrementCounter() {
setState(() {
_counter++;
});
}
- 当按钮点击时,会调用此函数,该函数的作用是先自增
_counter
,然后调用setState
方法。setState
方法的作用是通知Flutter
框架,有状态发生了变化,Flutter
框架收到通知后,会执行build
方法来根据新的状态重新构建界面,Flutter
对此方法做了优化,使重新执行变得很快,所以你可以重新构建任何需要更新的东西,而无须分别去修改各个widget
。 - 构建
UI
界面
构建UI
界面的逻辑在build
方法中,当MyHomePage
第一次创建时,_MyHomePageState
类会被创建,当初始化完成后,Flutter
框架会调用Widget
的build
方法来构建widget
树,最终将widget
树渲染到设备屏幕上,所以,来看一下_MyhomePageState
的build
方法都干了什么事。
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
);
}
-
Scaffold
是Material
库中提供的页面脚手架,提供了默认的导航栏、标题和包含主屏幕widget
树的body
属性,widget
树可以很复杂 -
body
的widget
树中包含了一个Center widget
,Center
可以将其子widget
树对齐到屏幕中心,Center
子widget
是一个Column widget
,Column
的作用是将其所有字widget
沿屏幕垂直方向依次排列,此例中Column
包含两个Text
子widget
,第一个Text widget
显示固定为文本You have pushed the button this many times:
,第二个Text widget
显示_counter
状态的数值
-floatingActionButton
是页面右下角的带"➕"的炫富按钮,它的onPressed
属性接受一个毁掉参数,代表它被点击后的处理事件,本例中将_incrementCounter
作为其处理函数
3.6 流程小结
现在,我们将整个流程串起来:当右下角的floatingActionButton
按钮被点击后,会调用_incrementCounter
,在_incrementCounter
中,首先会自增_counter
计数器(状态),然后setState
会通知Flutter
框架状态发生变化, 接着,Flutter
会调用build
方法以新的状态重新构建UI
,最终显示在设备屏幕上。
四、解惑build为何放在State中
现在,我们可以回答之前的问题,为什么build
方法在State
,而不是StatefulWidget
中?
这个主要是为了灵活性,如果将build
方法放在StatefulWidget
中则会有两个问题:
- 状态访问不便
试想一下,如果我们的Stateful widget
有很多状态,而每次状态改变都要调用build
方法,由于状态是保存在State
中的,如果将build
方法放在StatefulWidget
中,那么构建时读取状态会很不方便。如果真的将build
方法放在StatefulWidget
中的话,由于构建用户界面过程需要依赖State
,所以build
方法必须加一个State
参数,大概是下面这样:
Widget build(BuildContext context, State state){
//state.counter
...
}
这样的话,就只能将State
的所有状态声明为公开的状态,这样才能在State
类外部访问状态,但将状态设置为公开后,状态将不再具有私密性,这样依赖,对状态的修改将会变得不可控。但将build
方法放入State
中的话,构建过程可以直接访问状态,会很方便。
- 继承
StatefulWidget
不便
例如,Flutter
中有一个动画widget
的基类AnimatedWidget
,它继承自StatefulWidget
。AnimatedWidget
中引入了一个抽象方法build(BuildContext context)
,继承自AnimatedWidget的动画
widget都要实现这个
build方法。 现在试想一下,如果
StatefulWidget类中已经有了一个
build方法,正如上面所述,此时build方法需要接受一个state对象,这意味着
AnimatedWidget必须先将自己的
State对象(记为
_animatedWidgetState)提供给其自雷,因为子类需要在其
build方法中调用父类的
build`方法,代码可能如下:
class MyAnimationWidget extends AnimatedWidget{
@override
Widget build(BuildContext context, State state){
//由于子类要用到AnimatedWidget的状态对象_animatedWidgetState,
//所以AnimatedWidget必须通过某种方式将其状态对象_animatedWidgetState
//暴露给其子类
super.build(context, _animatedWidgetState)
}
}
这样显然是不合理的,因为:
-
AnimatedWidget
的状态对象是AnimatedWidget
内部实现细节,不应该暴露给外部 - 如果要将父类状态暴露给子类,那么必须有一种传递机制,二做这一套传递机制是毫无意义的,因为父子类之间状态的传递和子类本身逻辑是无关的。
综上所述,对于StatefulWidget
,将build
方法放在State
中,可以给开发带来很大的灵活性,更加规范。