文章目录

  • 一、目标
  • 二、新建计数器项目
  • 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

android 计数台modbus rtu_flutter

2.2 设置包名

在此,仅勾选了Kotlin support,如果在iOS上需要使用Swift的话,在此勾选即可。

android 计数台modbus rtu_widget_02

2.3 运行项目

至此,工程创建完毕。在Terminal窗口,运行flutter run即可将应用安装到当前连接的手机上。

android 计数台modbus rtu_statefulWidget_03

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
  • MaterialAppMaterial库提供的Flutter App框架,通过它可以设置应用的名称、主题、首页以及路由列表等,MaterialApp也是一个widget
  • homeFlutter应用的首页,它也是一个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 WidgetStateless 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中都包含哪些东西。

  1. 状态
int _counter = 0;
  1. _counter为保存屏幕右下角"➕"号按钮点击次数的状态。
  2. 设置状态的自增函数
void _incrementCounter() {
	setState(() {
 		_counter++;
	});
}
  1. 当按钮点击时,会调用此函数,该函数的作用是先自增_counter,然后调用setState方法。setState方法的作用是通知Flutter框架,有状态发生了变化,Flutter框架收到通知后,会执行build方法来根据新的状态重新构建界面,Flutter对此方法做了优化,使重新执行变得很快,所以你可以重新构建任何需要更新的东西,而无须分别去修改各个widget
  2. 构建UI界面
    构建UI界面的逻辑在build方法中,当MyHomePage第一次创建时,_MyHomePageState类会被创建,当初始化完成后,Flutter框架会调用Widgetbuild方法来构建widget树,最终将widget树渲染到设备屏幕上,所以,来看一下_MyhomePageStatebuild方法都干了什么事。
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),
     ),
   );
 }
  • ScaffoldMaterial库中提供的页面脚手架,提供了默认的导航栏、标题和包含主屏幕widget树的body属性,widget树可以很复杂
  • bodywidget树中包含了一个Center widgetCenter可以将其子widget树对齐到屏幕中心,Centerwidget是一个Column widgetColumn的作用是将其所有字widget沿屏幕垂直方向依次排列,此例中Column包含两个Textwidget,第一个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,它继承自StatefulWidgetAnimatedWidget中引入了一个抽象方法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)
	    }
}

这样显然是不合理的,因为:

  1. AnimatedWidget的状态对象是AnimatedWidget内部实现细节,不应该暴露给外部
  2. 如果要将父类状态暴露给子类,那么必须有一种传递机制,二做这一套传递机制是毫无意义的,因为父子类之间状态的传递和子类本身逻辑是无关的。

综上所述,对于StatefulWidget,将build方法放在State中,可以给开发带来很大的灵活性,更加规范。