# Flutter教程 - 3 Flutter初体验

在上面我们刚创建一个项目就有了一堆的代码,看上去云里雾里不知所云,下面我们从0开始,从Hello World开始熟悉Flutter应用的实现。

# 3.1 Hello World

现在我们来写HelloWorld程序,自动生成的demo程序中的 lib/main.dart 文件是app的程序入口文件,而文件中的 main 方法是app的入口方法,也就是程序是从这里开始执行的。

# 1 初级Hello World

现在将 main.dart 文件中内容全部删掉,从 main方法开始编写。

import 'package:flutter/material.dart';

void main() {
  runApp(从这里开始);
}
1
2
3
4
5

查看 runApp 方法,会看到参数 Widget 类型,那么什么是 Widget

在 Flutter 中,万物都是 Widget,可以理解为**组件、部件、控件。**文本组件,甚至边距都是 Widget。

在 Flutter 有一个文本的 Widget,就是 Text,现在给 runApp 传递一个文本的 Widget

import 'package:flutter/material.dart';

void main() {
  runApp(Text("Hello World", textDirection:TextDirection.ltr));
}
1
2
3
4
5

创建 Text 的 Widget,传递了两个参数,一个是用来显示的文本,一个是设置文字的方向,因为这里 Text 没有被任何 Widget 包裹,所以才需要设置 textDirection 属性,否则会报错,在后面的开发中,没有特殊的要求文字显示的方向,不需要设置 textDirection 属性。

重新运行项目,显示效果如下:

可以看到在屏幕的左上角,显示了 Hello World。

但这个 Hello World 也太™寒碜了,下面我们使用 Material库来优化一下我们的Hello World程序。

在优化 Hello World 之前,先讲解一下热重载和热启动。

# 2 热重载和热重启

在开发 Android 原生 app 的时候,我们修改了代码就需要重新运行程序,效率很低,不能实现热重载。

但在进行 Flutter 开发的时候,我们修改了代码,可以直接进行热重载或热重启。

举个栗子:

我们修改上面的代码中的Hello World文本内容:

import 'package:flutter/material.dart';

void main() {
  runApp(Text("Hello World!!!!!!", textDirection:TextDirection.ltr));
}
1
2
3
4
5

修改完成,直接保存,可以直接点击下面的热重启按钮,则会立刻显示修改的内容,是不是麻雀啄了牛屁股——雀食牛逼啊。

后面我们修改了内容保存后,可以直接使用热重载和热重启。

那么热重载和热重启有什么区别呢?

热重载 可以快速更新 UI,并且保持应用程序的状态,保存应用的状态就是数据还在,就像前面的demo程序,如果我们修改了代码,然后进行热重载,如果我们点击了3次按钮,计数器变为3,使用热重载后,计数器还是3,数据还在。

热启动 会重新启动整个应用程序,并重置状态。

# 3 Material 风格

下面我们使用 Material 风格来优化Hello World。在上面的代码中,我们已经 import 引入了 Material 库,Material是Google提供的一个设计语言和UI组件库。

修改代码如下:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: Scaffold(body: Text("Hello World"))));
}
1
2
3
4
5

在开发 Flutter 的时候,创建 Widget 的时候,需要传递很多命名参数,为了便于阅读,一般会进行换行处理。上面的代码经过格式化如下:

import 'package:flutter/material.dart';

void main() {
  runApp(
      MaterialApp(
          home: Scaffold(
              body: Text("Hello World")
          )
      )
  );
}
1
2
3
4
5
6
7
8
9
10
11

解释一下上面的代码:

MaterialApp 是Flutter中的一个顶级组件,它包装了整个应用程序,并提供了一些应用程序级别的功能,所以它是应用程序的根组件。在创建 MaterialApp 的时候,传递了一个 Scaffold 作为显示的主页,Scaffold 是一个布局组件,相当于一个页面

Scaffold 的body属性表示是页面显示的body部分,Scaffold还可以包含appBar、tabBar等部分。

所以上面的操作就是使应用更加层次化,创建了一个应用的 Widget,在应用的 Widget 中又创建了一个页面的 Widget,在页面中添加了一个文本的 Widget。

最终的显示效果如下:

卧槽,你会说这是上坟烧报纸——糊弄鬼呢,和之前除了背景不一样,没啥不一样。

下面在这基础上继续进行拓展。

我们先给页面添加一个 appBar

import 'package:flutter/material.dart';

void main() {
  runApp(
      MaterialApp(
          home: Scaffold(
              appBar: AppBar(
                  title: Text("第一个Flutter程序")
              ),
              body: Text("Hello World")
          )
      )
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Scaffold 添加了 appBar,并设置了 appBar 的 title 属性。

显示效果如下:

终于像个 Hello World 了吧。下面我们调整一下Hello World显示的位置,将Hello World显示在屏幕中间,并修改一下Hello World的字体大小。

# 4 设置Hello World样式

想要 Hello World 居中显示,这里用到一个 Center 的 Widget,它可以将其子 Widget 相对于中心位置居中对齐。

所以这里我们将 Scaffold 的 body 使用 Center 组件,然后在 Center 组件中放置 Hello World 的 Text。

代码如下:

Center 通过 child 属性设置子组件,Text 通过 style 属性设置样式,上面设置了字体大小和字体颜色。

我们再将右上角的debug给去掉,给 MaterialApp 添加 debugShowCheckedModeBanner 属性。

现在是不是看上去像个Hello World了。

# 3.2 StatelessWidget

上面的 Hello World 感觉看上去代码很乱,各个 Widget 嵌套,都写在 main 函数里面结构很不清晰,现在还只是个 Hello World,如果是真正的项目,页面组件一多,那还了得。

所以我们需要对代码进行封装,创建我们自己的 Widget,将各种组件封装到我们的 Widget中,实现结构的清晰划分。

在 Flutter 中,我们可以创建两种 Widget,StatelessWidgetStatefulWidget ,什么区别呢?

  • StatelessWidget: 没有状态的Widget,也就是没有数据变化的 Widget,通常这种Widget仅仅是做一些展示工作而已;
  • StatefulWidget: 需要保存状态,也就是会有数据变化的Widget,例如 demo 程序中有计数的变化;

# 1 重构Hello World

下面对 Hello World 进行优化,因为 Hello World 中没有数据的变化,所以使用 StatelessWidget 即可。

我们将 MaterialApp 封装在一个 Widget 中,表示整个应用。将 Scaffold 封装在一个 Widget 中,表示的是一个页面。

继承 StatelessWidget 需要重写 build 方法,build 方法需要返回一个 Widget,也就是告诉 Flutter,我们要渲染一个什么组件,例如可以返回Text Widget、Scaffold Widget等。

将 MaterialApp 封装在一个 Widget 中:

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        home: HomePage(),
    );
  }
}
1
2
3
4
5
6
7
8
9

home 是一个页面,也就是之前的 Scaffold,我们将 Scaffold 封装到 HomePage 中。

class HomePage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text("第一个Flutter程序")
        ),
        body: Center(
          child: Text(
            "Hello World!",
            style: TextStyle(
                fontSize: 30,
                color: Colors.blue
            ),
          ),
        )
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

当页面内容很多的时候,为了结构清晰,很多页面的组件可能都需要单独封装成 Widget,这里结构简单,只封装到了页面这一层。

这样我们的 main 方法就很简单了,直接创建一个 MyApp就可以了。

void main() {
  runApp(MyApp());
}
1
2
3

因为只有一行代码,所以可以使用箭头函数:

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

完整的代码如下:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

/**
 * App根Widget
 */
class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomePage(),
    );
  }
}

/**
 * 页面Widget
 */
class HomePage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text("第一个Flutter程序")
        ),
        body: Center(
          child: Text(
            "Hello World!",
            style: TextStyle(
                fontSize: 30,
                color: Colors.blue
            ),
          ),
        )
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 2 优化代码

Hello World已经实现了,但是代码有一些规范性的提示,提示我们优化代码。

下面提示添加一个 key 参数的构造函数。

点击添加会生成一个带有key参数的构造函数。

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomePage(),
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11

这个key有什么作用呢?

这个key是widget的唯一标识,Flutter 可以识别小部件是否在不同的渲染帧之间保持不变,控制是否更新、重建或重新创建小部件,从而优化性能。

但是我们现在的代码还很简单,虽然定义了带key的构造函数,但是根本就没有传递这个参数,没有用到,后面我们再介绍如何使用。这里我们先按照建议,针对widget统一都生成带key的构造函数。

下面提示使用常量构造函数。

在学习dart的时候,已经知道了使用常量构造函数在创建属性值相同的对象时,使用的是相同的内存空间。而在Flutter中,const不仅仅节省创建组件的内存开销,还可以在重新构建组件的时候,不重新构建const组件,从而提高性能。

所以我们根据提示的建议优化我们的代码,代码如下:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

/// App根Widget
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomePage(),
    );
  }
}

/// 页面Widget
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: const Text("第一个Flutter程序")
        ),
        body: const Center(
          child: Text(
            "Hello World!",
            style: TextStyle(
                fontSize: 30,
                color: Colors.blue
            ),
          ),
        )
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

需要注意:当最一个组件使用了const的时候,他的子组件也将是const的,所以子组件不用再添加了。在开发中,我们使用const修饰了父组件,后面添加了非const的子组件,要将父组件的const去掉,同时查看是否需要给其他子组件单独添加const。

后面我们在编写代码的时候,可以根据建议进行代码优化。甚至注释也进行了优化。

在 Flutter 中,约定是使用三斜线 /// 来表示文档注释。在使用工具(如 dartdoc)生成代码文档时,使用 /// 格式的文档注释会被识别,并生成详细的代码文档。

# 3 实现消息列表

下面再来实现一个简单的消息列表,进一步熟悉一下 StatelessWidget

实现下图的列表。

我们现在不用太关心布局,后面会学习布局相关的 Widget。

另外我们现在的数据是写死的,所以不涉及状态(数据)的变化,所以这里都使用 StatelessWidget 来实现。

实现上面的列表,我们先构建应用的 Widget,然后构建页面的 Widget,页面的 body 部分是一个列表,列表中是一个个 Item 的 Widget,结构清晰,开搞:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

/// App根Widget
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MessagePage(),
    );
  }
}

/// 消息页面
class MessagePage extends StatelessWidget {
  const MessagePage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: const Text("消息")
        ),
        body: ListView(
          children: [

          ],
        )
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

上面实现了应用、页面的 Widget,页面的 Body 是一个 ListView,ListView 中有一个 children 参数,里面需要填写列表中的 Item。下面我们来构建一下 消息的 Item 的 Widget。

/// 消息Item
class MessageItem extends StatelessWidget {

  final imageUrl;
  final name;
  final message;

  const MessageItem(this.imageUrl, this.name, this.message, {super.key});

  
  Widget build(BuildContext context) {
    return Row(													// 行
      children: [
        Image.network(imageUrl),
        Column(												// 列
          children: [
            Text(name),
            Text(message)
          ],
        ),
      ],
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

我们定义了三个属性,imageUrl、name、message,在 StatelessWidget 中只能定义 final 类型的常量,通过构造函数进行初始化。

然后在 build 方法中返回一个 行组件,行组件中包含了一个图片组件 和 一个 列组件,在 列组件中添加了两个Text 组件,这样构成一个Item的布局。

然后我们在ListView中创建几个 Item。

/// 消息页面
class MessagePage extends StatelessWidget {
  const MessagePage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: const Text("消息")
        ),
        body: ListView(
          children: const [
            MessageItem("http://doubibiji.com/open-assets/img/telangpu.jpg", "特朗普", "拜登今天肯定拉在裤裆里啦"),
            MessageItem("http://doubibiji.com/open-assets/img/pujing.jpg", "普京", "你怎么知道的"),
            MessageItem("http://doubibiji.com/open-assets/img/baideng.jpg", "拜登", "胡说,不是今天"),
          ],
        )
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

显示效果:

上面显示的黄黑条是因为内容超过了屏幕。

现在没有设置图片的大小,现在调整一下图片的尺寸,同时设置一下文字的尺寸。

/// 消息Item
class MessageItem extends StatelessWidget {

  final imageUrl;
  final name;
  final message;

  const MessageItem(this.imageUrl, this.name, this.message, {super.key});

  
  Widget build(BuildContext context) {
    return Row(
      children: [
        Image.network(
          imageUrl,
          width: 60,									// 设置图片尺寸
          height: 60,
        ),
        Column(
          children: [
            Text(
              name,
              style: const TextStyle(				// 设置字体样式
                  fontSize: 18
              ),
            ),
            Text(
              message,
              style: const TextStyle(
                  fontSize: 14
              ),
            )
          ],
        ),
      ],
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

显示效果:

再修改一下文字的对齐方式,文字的颜色和文字的间距:

使用 crossAxisAlignment: CrossAxisAlignment.start, 修改文字的方向。间距也是使用 Widget 来实现的,通过 SizedBox 来增加间距。


/// 消息Item
class MessageItem extends StatelessWidget {

  final imageUrl;
  final name;
  final message;

  const MessageItem(this.imageUrl, this.name, this.message, {super.key});

  
  Widget build(BuildContext context) {
    return Row(
      children: [
        Image.network(
          imageUrl,
          width: 60,									                        // 设置图片尺寸
          height: 60,
        ),
        const SizedBox(width: 10),														// 增加间距
        Column(
          crossAxisAlignment: CrossAxisAlignment.start,				// 文字方向
          children: [
            Text(
              name,
              style: const TextStyle(				                  // 设置字体样式
                  fontSize: 18
              ),
            ),
            const SizedBox(height: 3),											  // 增加间距
            Text(
              message,
              style: const TextStyle(
                  fontSize: 14,
                  color: Color.fromARGB(255, 100, 100, 100)		// 文字颜色
              ),
            )
          ],
        ),
      ],
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

显示效果:

下面再给每个Item增加一个 padding 间距,padding 也是一个 Widget,我们使用 Padding 组件将 Item 包裹起来。

/// 消息Item
class MessageItem extends StatelessWidget {

  final imageUrl;
  final name;
  final message;

  const MessageItem(this.imageUrl, this.name, this.message, {super.key});
  
  
  Widget build(BuildContext context) {
      return Padding(
          padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),			// 增加间距
          child: Row(
              // ...
          )
      );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

显示效果:

现在先不用太纠结如何实现布局,后面再详细介绍布局相关的 Widget。

最终完整代码:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

/// App根Widget
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MessagePage(),
    );
  }
}

/// 消息页面
class MessagePage extends StatelessWidget {
  const MessagePage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: const Text("消息")
        ),
        body: ListView(
          children: const [
            MessageItem("http://doubibiji.com/open-assets/img/telangpu.jpg", "特朗普", "拜登今天肯定拉在裤裆里啦"),
            MessageItem("http://doubibiji.com/open-assets/img/pujing.jpg", "普京", "你怎么知道的"),
            MessageItem("http://doubibiji.com/open-assets/img/baideng.jpg", "拜登", "胡说,不是今天"),
          ],
        )
    );
  }
}

/// 消息Item
class MessageItem extends StatelessWidget {

  final imageUrl;
  final name;
  final message;

  const MessageItem(this.imageUrl, this.name, this.message, {super.key});

  
  Widget build(BuildContext context) {
    return Padding(
        padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
        child: Row(
          children: [
            Image.network(
              imageUrl,
              width: 60,									                        // 设置图片尺寸
              height: 60,
            ),
            const SizedBox(width: 10),														// 增加间距
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,				// 文字方向
              children: [
                Text(
                  name,
                  style: const TextStyle(				                  // 设置字体样式
                      fontSize: 18
                  ),
                ),
                const SizedBox(height: 3),											  // 增加间距
                Text(
                  message,
                  style: const TextStyle(
                      fontSize: 14,
                      color: Color.fromARGB(255, 100, 100, 100)		// 文字颜色
                  ),
                )
              ],
            ),
          ],
        )
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

# 3.3 StatefulWidget

下面来讲解一下带状态的 Widget。

现在也是以实现一个样例的方式,来介绍 StatefulWidget,我们来实现一开始创建项目时实现的计数器功能。

先将页面结构搭建出来,先创建应用的 Widget,然后创建页面的 Widget。

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

/// App根Widget
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: CounterPage(),
    );
  }
}

/// 计数器页面
class CounterPage extends StatefulWidget {
	const CounterPage({super.key});
  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

下面主要来实现CounterPage的 Widget,CounterPage因为有计数的变化,是有状态的StatefulWidget。

编写CounterPage,继承自StatefulWidget。

/// 计数器页面
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  
  State<StatefulWidget> createState() {
    
  }
}
1
2
3
4
5
6
7
8
9

我们会发现 StatefulWidgetStatelessWidget 有区别,其中实现的方法不是 build,而是 createState方法,返回的是一个 State

虽然 StatefulWidget 是有状态的,但是 Widget 类中是无法定义状态的,需要重新定一个状态的类。

所以在 CounterPage 中返回一个 CounterState

/// 计数器页面
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  
  State<StatefulWidget> createState() {
    return CounterState();
  }
}
1
2
3
4
5
6
7
8
9

下面我们来创建 CounterState 类,在状态类中,我们构建整个页面的显示内容,状态类的build的方法是返回 Widget 的:

class CounterState extends State<CounterPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: const Text("计数器")
        ),
        body: const Center(
            child: Text(
              "0",
              style: TextStyle(
                  fontSize: 30
              ),
            )
        )
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

我们在页面添加了AppBar,并在页面中间添加了一个 Text ,并设置了字体大小。

保存运行,显示效果如下:

现在我们要操作,那么需要给页面添加一个按钮,点击按钮来增加计数。

像 demo 一样,我们在右下角添加一个悬浮按钮:

/// 计数器页面
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  
  State<StatefulWidget> createState() {
    return CounterState();
  }
}

class CounterState extends State<CounterPage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {				// 更新变量的值必须在setState方法中更新才会让页面刷新
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: const Text("计数器")
      ),
      body: Center(
          child: Text(
            "$_counter",													// 读取_counter的值
            style: const TextStyle(
                fontSize: 30
            ),
          )
      ),
      floatingActionButton: FloatingActionButton(	// 添加悬浮按钮
        onPressed: _incrementCounter,							// 绑定按钮的点击事件
        child: const Icon(Icons.add),						
      ),
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

直接在 Scaffold 中添加一个 floatingActionButton,floatingActionButton 需要设置点击事件,点击事件需要传递的是一个方法,所以我们创建了一个私有方法 _incrementCounter,在方法中,我们将 _counter++

你会发现,我们根本就没有直接操作组件去修改组件中的内容或显示,这和以前的Android和iOS区别很大,和 Vue 的双向数据绑定是一样的。通过 Widget 和 State 的绑定,当我们修改了 State,Widget会自动更新显示。

所以在上面的代码中,我们点击了按钮,修改了 _counter 的值后,Text 引用并显示了 _counter 的值,所以 Text 会自动重新渲染。

需要注意,修改 State 的值,需要在 setState() 方法中进行修改才有效

最终的显示效果:

完整代码:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

/// App根Widget
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: CounterPage(),
    );
  }
}

/// 计数器页面
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  
  State<StatefulWidget> createState() {
    return CounterState();
  }
}

class CounterState extends State<CounterPage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {     // 一定要在setState方法中更新变量
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: const Text("计数器")
      ),
      body: Center(
          child: Text(
            "$_counter",
            style: const TextStyle(
                fontSize: 30
            ),
          )
      ),
      floatingActionButton: FloatingActionButton( // 添加悬浮按钮
        onPressed: _incrementCounter,             // 绑定按钮的点击事件
        child: const Icon(Icons.add),
      ),
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

总结:

  • 通过 Widget 和 State 的绑定,当我们修改了 State,Widget会自动更新显示;
  • 修改 State 的值,需要在 setState() 方法中进行修改才会重新渲染