用Android Studio和VS Code创建的Flutter应用模板是一个简单的计数器示例,本节先仔细讲解一下这个计数器Demo的源码,让读者对Flutter应用程序结构有个基本了解,在随后小节中,将会基于此示例,一步一步添加一些新的功能来介绍Flutter应用的其它概念与技术。对于接下来的示例,希望读者可以跟着笔者实际动手来写一下,这样不仅可以加深印象,而且也会对介绍的概念与技术有一个真切的体会。如果你还不是很熟悉Dart或者没有移动开发经验,不用担心,只要你熟悉面向对象和基本编程概念(如变量、循环和条件控制),则可以完成本示例。

通过Android Studio和VS Code根据前面“编辑器配置与使用”一章中介绍的创建Flutter工程的方法创建一个新的Flutter工程,命名为"first_flutter_app"。创建好后,就会得到一个计数器应用的Demo。

注意,默认Demo示例可能随着编辑器Flutter插件版本变化而变化,本例中会介绍计数器示例的全部代码,所以不会对本示例产生影响。

我们先运行此示例,效果图如下:

该计数器示例中,每点击一次右下角带“➕”号的悬浮按钮,屏幕中央的数字就会加1。

在这个示例中,主要Dart代码是在 lib/main.dart 文件中,下面我们看看该示例的源码:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(new MyApp());
  3. class MyApp extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return new MaterialApp(
  7. title: '【全栈编程】-onajax.com',
  8. theme: new ThemeData(
  9. primarySwatch: Colors.blue,
  10. ),
  11. home: new MyHomePage(title: '【全栈编程】-onajax.com'),
  12. );
  13. }
  14. }
  15. class MyHomePage extends StatefulWidget {
  16. MyHomePage({Key key, this.title}) : super(key: key);
  17. final String title;
  18. @override
  19. _MyHomePageState createState() => new _MyHomePageState();
  20. }
  21. class _MyHomePageState extends State<MyHomePage> {
  22. int _counter = 0;
  23. void _incrementCounter() {
  24. setState(() {
  25. _counter++;
  26. });
  27. }
  28. @override
  29. Widget build(BuildContext context) {
  30. return new Scaffold(
  31. appBar: new AppBar(
  32. title: new Text(widget.title),
  33. ),
  34. body: new Center(
  35. child: new Column(
  36. mainAxisAlignment: MainAxisAlignment.center,
  37. children: <Widget>[
  38. new Text(
  39. '【全栈编程】-onajax.com提示:你已经点击了--',
  40. ),
  41. new Text(
  42. '$_counter',
  43. style: Theme.of(context).textTheme.display1,
  44. ),
  45. ],
  46. ),
  47. ),
  48. floatingActionButton: new FloatingActionButton(
  49. onPressed: _incrementCounter,
  50. tooltip: '【全栈编程】-onajax.com',
  51. child: new Icon(Icons.add),
  52. ), // This trailing comma makes auto-formatting nicer for build methods.
  53. );
  54. }
  55. }

分析

1.导入包

  1. import 'package:flutter/material.dart';

此行代码作用是导入了Material UI组件库。Material是一种标准的移动端和web端的视觉设计语言, Flutter默认提供了一套丰富的Material风格的UI组件。

2.应用入口

  1. void main() => runApp(new MyApp());

与C/C++、Java类似,Flutter 应用中main函数为应用程序的入口,main函数中调用了,runApp 方法,它的功能是启动Flutter应用,它接受一个Widget参数,在本示例中它是MyApp类的一个实例,该参数代表Flutter应用。

main函数使用了(=>)符号,这是Dart中单行函数或方法的简写。

3.应用结构。

  1. class MyApp extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. return new MaterialApp(
  5. //应用名称
  6. title: 'Flutter Demo',
  7. theme: new ThemeData(
  8. //蓝色主题
  9. primarySwatch: Colors.blue,
  10. ),
  11. //应用首页路由
  12. home: new MyHomePage(title: 'Flutter Demo Home Page'),
  13. );
  14. }
  15. }

MyApp类代表Flutter应用,它继承了 StatelessWidget类,这也就意味着应用本身也是一个widget。

在Flutter中,大多数东西都是widget,包括对齐(alignment)、填充(padding)和布局(layout)。

Flutter在构建页面时,会调用组件的build方法,widget的主要工作是提供一个build()方法来描述如何构建UI界面(通常是通过组合、拼装其它基础widget)。

MaterialApp 是Material库中提供的Flutter APP框架,通过它可以设置应用的名称、主题、语言、首页及路由列表等。MaterialApp也是一个widget。

Scaffold 是Material库中提供的页面脚手架,它包含导航栏和Body以及FloatingActionButton(如果需要的话)。 本书后面示例中,路由默认都是通过Scaffold创建。

home 为Flutter应用的首页,它也是一个widget。

4.首页

  1. class MyHomePage extends StatefulWidget {
  2. MyHomePage({Key key, this.title}) : super(key: key);
  3. final String title;
  4. @override
  5. _MyHomePageState createState() => new _MyHomePageState();
  6. }
  7. class _MyHomePageState extends State<MyHomePage> {
  8. ...
  9. }

MyHomePage 是应用的首页,它继承自StatefulWidget类,表示它是一个有状态的widget(Stateful widget)。现在,我们可以简单认为Stateful widget 和Stateless widget有两点不同:

Stateful widget可以拥有状态,这些状态在widget生命周期中是可以变的,而Stateless widget是不可变的。

Stateful widget至少由两个类组成:

一个StatefulWidget类。

一个 State类; StatefulWidget类本身是不变的,但是 State类中持有的状态在widget生命周期中可能会发生变化。

_MyHomePageState类是MyHomePage类对应的状态类。看到这里,细心的读者可能已经发现,和MyApp 类不同, MyHomePage类中并没有build方法,取而代之的是,build方法被挪到了_MyHomePageState方法中,至于为什么这么做,先留个疑问,在分析完完整代码后再来解答。

接下来,我们看看_MyHomePageState中都包含哪些东西:

1.状态。

  1. int _counter = 0;

_counter 为保存屏幕右下角带“➕”号按钮点击次数的状态。

2.设置状态的自增函数。

  1. void _incrementCounter() {
  2. setState(() {
  3. _counter++;
  4. });
  5. }

当按钮点击时,会调用此函数,该函数的作用是先自增_counter,然后调用setState 方法。setState方法的作用是通知Flutter框架,有状态发生了改变,Flutter框架收到通知后,会执行build方法来根据新的状态重新构建界面, Flutter 对此方法做了优化,使重新执行变的很快,所以你可以重新构建任何需要更新的东西,而无需分别去修改各个widget。

3.构建UI界面

构建UI界面的逻辑在build方法中,当MyHomePage第一次创建时,_MyHomePageState类会被创建,当初始化完成后,Flutter框架会调用Widget的build方法来构建widget树,最终将widget树渲染到设备屏幕上。所以,我们看看_MyHomePageState的build方法中都干了什么事:

  1. @override
  2. Widget build(BuildContext context) {
  3. return new Scaffold(
  4. appBar: new AppBar(
  5. title: new Text(widget.title),
  6. ),
  7. body: new Center(
  8. child: new Column(
  9. mainAxisAlignment: MainAxisAlignment.center,
  10. children: <Widget>[
  11. new Text(
  12. '【全栈编程】-onajax.com提示:你已经点击了--',
  13. ),
  14. new Text(
  15. '$_counter',
  16. style: Theme.of(context).textTheme.display1,
  17. ),
  18. ],
  19. ),
  20. ),
  21. floatingActionButton: new FloatingActionButton(
  22. onPressed: _incrementCounter,
  23. tooltip: '【全栈编程】-onajax.com',
  24. child: new Icon(Icons.add),
  25. ), // This trailing comma makes auto-formatting nicer for build methods.
  26. );
  27. }

Scaffold 是 Material库中提供的一个widget, 它提供了默认的导航栏、标题和包含主屏幕widget树的body属性。widget树可以很复杂。

body的widget树中包含了一个Center widget,Center 可以将其子widget树对齐到屏幕中心, Center 子widget是一个Column widget,Column的作用是将其所有子widget沿屏幕垂直方向依次排列, 此例中Column包含两个 Text子widget,第一个Text widget显示固定文本 “【全栈编程】-onajax.com提示:你已经点击了--”,第二个Text widget显示_counter状态的数值。

floatingActionButton是页面右下角的带“➕”的悬浮按钮,它的onPressed属性接受一个回调函数,代表它被点击后的处理器,本例中直接将_incrementCounter作为其处理函数。

现在,我们将整个流程串起来:当右下角的floatingActionButton按钮被点击之后,会调用_incrementCounter,在_incrementCounter中,首先会自增_counter计数器(状态),然后setState会通知Flutter框架状态发生变化,接着,Flutter会调用build方法以新的状态重新构建UI,最终显示在设备屏幕上。

为什么要将build方法放在State中,而不是放在StatefulWidget中?

现在,我们回答之前提出的问题,为什么build()方法在State(而不是StatefulWidget)中 ?这主要是为了开发的灵活性。如果将build()方法在StatefulWidget中则会有两个问题:

状态访问不便

试想一下,如果我们的Stateful widget 有很多状态,而每次状态改变都要调用build方法,由于状态是保存在State中的,如果将build方法放在StatefulWidget中,那么构建时读取状态将会很不方便,试想一下,如果真的将build方法放在StatefulWidget中的话,由于构建用户界面过程需要依赖State,所以build方法将必须加一个State参数,大概是下面这样:

  1. Widget build(BuildContext context, State state){
  2. //state.counter
  3. ...
  4. }

这样的话就只能将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方法,代码可能如下:

  1. class MyAnimationWidget extends AnimatedWidget{
  2. @override
  3. Widget build(BuildContext context, State state){
  4. //由于子类要用到AnimatedWidget的状态对象_animatedWidgetState,
  5. //所以AnimatedWidget必须通过某种方式将其状态对象_animatedWidgetState
  6. //暴露给其子类
  7. super.build(context, _animatedWidgetState)
  8. }
  9. }

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

AnimatedWidget的状态对象是AnimatedWidget内部实现细节,不应该暴露给外部。

如果要将父类状态暴露给子类,那么必须得有一种传递机制,而做这一套传递机制是无意义的,因为父子类之间状态的传递和子类本身逻辑是无关的。

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

来源

6.1.初识Flutter应用之实现一个计数器的更多相关文章

  1. 初识Flutter

    什么是Flutter 官网的定义如下: Flutter is a new project to help developers build high-performance, high-fidelit ...

  2. 6.2.初识Flutter应用之路由管理

    路由管理 路由(Route)在移动开发中通常指页面(Page),这跟web开发中单页应用的Route概念意义是相同的,Route在Android中通常指一个Activity,在iOS中指一个ViewC ...

  3. Android studio 使用flutter插件 运行第一个flutter项目 报错 Warning: License for package Android SDK Build-Tools 28.0.3 not accepted.

    在Android studio中新建了flutter项目.运行报错licence not accepted. Warning: License for package Android SDK Buil ...

  4. FPGA学习记录_设计一个计数器

    此处设计一个数器,使 学习板上 的 LED 状态每 500ms翻转一次. 学习板上晶振为50MHz,也就是说时钟周期为 20ns , 这样可以计算得出 500ms = 500_000_000ns/20 ...

  5. 由一个计数器出发:关于vue使用独立js文件的问题

    最近有个vue项目要用ztree. 然后,我想把一些逻辑提出来作为公共的方法,放到独立的js文件里. ztreeTool.js import $ from 'jquery' export defaul ...

  6. 【Flutter 1-5】运行Flutter的第一个项目——计数器

    创建项目 创建Flutter项目有很多种方法,各个IDE工具也都集成了创建Flutter项目的快捷操作.我们这里列举三种方式:使用命令行创建.使用Android Studio创建和使用VSCode创建 ...

  7. 在windows系统搭建并运行一个Flutter项目

    搭建Flutter之前需要已经安装好相应的Flutter开发环境,如果没安装好相应环境的可以查看在windows系统搭建Flutter开发环境 搭建Flutter项目可以通过命令行搭建,或者通过and ...

  8. Flutter学习四之实现一个支持刷新加载的列表

    上一篇文章用Scaffold widget搭建了一个带底部导航栏的的项目架构,这篇文章就来介绍一下在flutter中怎么实现一个带下拉刷新和上拉加载更多的一个列表,这里用到了pull_to_refre ...

  9. Flutter入门教程(四)第一个flutter项目解析

    一.创建一个Flutter工程 1.1 命令行创建 首先我们找一个空目录用来专门存放flutter项目,然后在路径中直接输入cmd: 使用 flutter create <projectname ...

随机推荐

  1. C#图片灰度处理(位深度24→位深度8)

    #region 灰度处理 /// <summary> /// 将源图像灰度化,并转化为8位灰度图像. /// </summary> /// <param name=&qu ...

  2. SharePoint js操作原生的New/Edit表单

    列表的表单,有个类似的需求:在New需隐藏特定字段,Edit时显示. 默认是New/Edit表单的字段是一样,就算在Content type 是隐藏也是同时影响两个表单.   如何做到仅仅在New时隐 ...

  3. Color gradient in Delphi FireMonkey

    Introduction to color gradients in Delphi FireMonkey. Video This video covers the basics of color gr ...

  4. vue.js异步上传文件前后端代码

    上传文件前端代码如下: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type&q ...

  5. 【Aizu - ALDS1_1_C】Prime Numbers(素数筛法)

    Prime Numbers  Descriptions: A prime number is a natural number which has exactly two distinct natur ...

  6. 【转】Linux下添加FTP账号和服务器、增加密码和用户,更改FTP目录

    转自:http://blog.csdn.net/cloudday/article/details/8640234   1. 启动VSFTP服务器 A:cenos下运行:yum  install  vs ...

  7. 有关Html页面节点的简单理解

    这是之前研究web前端的一点经验,主要针对刚入门还没怎么研究的朋友. 因为我发现我在用js,css参与过网站开发项目后仍然没有理解文本节点与普通节点的差别,所以记下来拿来分享一下. 先上结论:< ...

  8. Markdown教程 <1>

    Markdown教程 <1> 本文在本地使用atom编辑后,直接将代码赋值到博客园中的markdown编辑器中生成 1. markdown字体,段落控制 以下引用块里面为源码,引用块下方为 ...

  9. Neo4j 爬坑笔记for3.2.6

    官网语法,非常详尽:http://neo4j.com/docs/developer-manual/current/cypher/clauses/match/ A:请对应版本号,不同大版本可能会有很大区 ...

  10. (2)Linux文件和目录操作命令

    简单就是高效 pwd cd -/~/.. tree–a/d/f/i/L mkdir–p/v/m touch ls –l/a//i/h/F cp –r/p/d/a mv rm –f/r/i rmdir ...