Flutter使用的是声明式UI,是通过状态去更新UI组件的,因此我们首先就要学习状态的使用。同样为了简化原本的状态使用,我们会使用Getx库。
之前说要写Flutter,一拖就是一年多,有些不好意思。现在都24年了,终于等到你,下面还是按照我们最属于的思路来吧。
首先我们创建项目,Flie → New → New Flutter Project...
然后选择Flutter,这里可以看到Flutter SDK path,已经配置好了,点击Next。
然后输入工程名为study_state
,目前我们只考虑Android和iOS两个平台,因此其他的就不勾选了。
点击Create按钮,完成项目的创建。
创建后我们可以看到main.dart
,这里是flutter启动文件,同时我启动了一个模拟器,用的雷电模拟器,至于为什么不用AS自带的模拟器,只能说懂的都懂,不懂的也劝你别去用。
下面我们首先运行在模拟器上看看,运行到模拟器的时间会比较长,不过运行之后当你再次修改代码之后可以通过热重载直接在模拟器上显示出修改后的内容,快捷键是Ctrl + S,图标是一个黄色的闪电。有时候快捷键不生效则你可以手动的点击这个闪电按钮,如果也不生效就重新安装,总会生效的,做开发就要有一颗平常心。
运行好了,效果如下图所示:
当我们点击右下角的浮动按钮之后就会看到屏幕中的数字加1,关于这个里面的内容我在第一篇Flutter文章中就介绍过了,因此下面我们就不过多介绍代码,我们将main.dart中的代码全部清空,如下图所示:
然后我们再来一步一步的写代码,在这个过程中我们能了解到更多的东西。
首先我们写一个main函数,代码如下所示:
void main() {
}
然后我们在这个当前这个文件中再写一个MyApp类,如下图所示:
这里我继承了StatelessWidget
,这是一个无状态组件,在你输入之后会有提示,注意一下导包是material.dart
中的,推荐你使用这个里面的StatelessWidget
,MyApp的代码如下所示:
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
这里重新里面的build()函数,在这里面我们就可以写UI了,但是我们先不写,你可以把MyApp理解为Android的Application,然后我们再写一个HomePage,继承StatefulWidget
这是一个有状态的组件,重写里面的createState()
,代码我们也先不改,你可以把HomePage 理解为Activity。
class HomePage extends StatefulWidget {
State<StatefulWidget> createState() {
// TODO: implement createState
throw UnimplementedError();
}
}
然后我们再写一个_HomePageState
类,继承自State,这是一个抽象类,支持有状态组件,那么我们传递HomePage进去,代码如下所示:
class _HomePageState extends State<HomePage> {
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
这里面的build()
函数中我们同样可以设置UI,现在我们就了解了无状态和有状态两种组件,在 Flutter 中,有两种类型的小部件:StatelessWidget
和 StatefulWidget
。它们在功能和使用上有一些区别。
StatelessWidget
(无状态小部件):
props
)在创建时被设置,并且在整个生命周期中保持不变。StatelessWidget
将重新构建,但状态不会发生变化。StatelessWidget
的构建过程更加高效。StatefulWidget
(有状态小部件):
State
),用于存储和跟踪小部件的变化。StatefulWidget
通过更新关联的状态对象来重新构建。StatefulWidget
通常用于处理需要响应用户交互或动态变化的情况。在实践中,以下是一些使用场景的示例:
StatelessWidget
:当小部件的外观和内容不会随时间而改变时,推荐使用 StatelessWidget
,例如静态文本、图标等。StatefulWidget
:当小部件的外观和内容需要根据用户交互、数据变化或其他条件动态更新时,需要使用 StatefulWidget
,例如表单、列表视图等。 需要注意的是,StatefulWidget
与 State
对象一起工作,后者存储和管理小部件的状态。当使用 StatefulWidget
时,通常需要同时创建一个与之关联的状态类。
总结起来,StatelessWidget
是一个不可变的小部件,适用于静态内容,而 StatefulWidget
是一个可变的小部件,适用于需要跟踪状态变化的场景。
下面我们将前面所写的代码给串起来,首先是main函数中,修改后代码如下所示:
void main() {
return runApp(MyApp());
}
这里使用了一个runApp()
函数,里面传入一个组件即可,无论是什么组件,这里我们传入MyApp()
,那么当我们启动之后会运行main函数,然后渲染MyApp()组件,下面我们修改MyApp的代码,如下所示:
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: "Study State",
theme: ThemeData(useMaterial3: true),
home: HomePage(),
);
}
}
这里我们通过构建MaterialApp()
,在home属性中添加了HomePage()
,那么内容就以HomePage()
为主了,再看HomePage
,代码如下所示:
class HomePage extends StatefulWidget {
const HomePage({super.key});
State<HomePage> createState() {
return _HomePageState();
}
}
这里面的key是一个标识HomePage组件的唯一键,createState()
方法在创建HomePage时会被调用。createState()
方法返回一个_HomePageState对象,它是HomePage的具体状态类。状态类(State)的主要作用是管理StatefulWidget的状态,并根据需要更新小部件的UI。每当HomePage的状态发生变化时,Flutter会调用_HomePageState类中的build方法来构建最新的UI,下面我们再来看_HomePageState
,代码如下所示:
class _HomePageState extends State<HomePage> {
Widget build(BuildContext context) {
return Scaffold(
body: Align(
alignment: Alignment.center,
child: Container(
width: 200,
height: 200,
alignment: Alignment.center,
child: Column(
children: [
Text("Study State"),
ElevatedButton(onPressed: () {
print("点击了");
}, child: Text("按钮"))
],
),
),
),
);
}
}
这里首先使用了一个Scaffold()
,这是一个页面的构成组件,body
属性定义了页面的主要内容区域。在body
中,使用Align
组件将其子组件在父容器中居中显示。Alignment.center
表示子组件在父容器中的居中对齐。
Align
的子组件是一个Container
,设置宽度和高度(200x200)。alignment
属性设置为Alignment.center
,将子组件在自身容器中进行居中对齐。child
属性是一个Column
组件,这是一个纵向布局组件,里面是一个数组,可以包含多个组件,它包含了两个子小部件:一个Text
组件和一个带有文本的ElevatedButton
组件。在按钮的点击事件中我们打印一下日志,下面我们重新运行一下。
点击按钮后,看控制台。
下面我们通过状态来更新UI,比如我们将点击按钮将文本内容改成大写,再点击改成小写,下面我们修改_HomePageState
类中的代码,如下所示:
class _HomePageState extends State<HomePage> {
String test = "study state";
bool isUppercase = false;
void changeText() {
setState(() {
isUppercase = !isUppercase;
print(isUppercase ? "大写" : "小写");
});
}
Widget build(BuildContext context) {
return Scaffold(
body: Align(
alignment: Alignment.center,
child: Container(
width: 200,
height: 200,
alignment: Alignment.center,
child: Column(
children: [
Text(isUppercase ? test.toUpperCase() : test.toLowerCase()),
ElevatedButton(onPressed: () {
changeText();
}, child: const Text("按钮"))
],
),
),
),
);
}
}
首先我们定义了两个变量,内容和是否大小写,然后写了一个changeText()
函数,这个函数中使用了setState(() {})
,用于改变状态,在这里面修改了isUppercase 的值,然后Text(isUppercase ? test.toUpperCase() : test.toLowerCase())
这行代码,在初始情况下显示为小写,然后我们点击按钮调用changeText()
函数,函数中更改isUppercase 的值,通过setState就会刷新UI,此时isUppercase 为true,则Text中显示大写,再点击一下为false就变成小写。这就是状态改变驱动UI。
主要改动地方如下图所示:
控制台日志如下图所示:
通过这种方式当我们有数据改变时就可以更新UI了,只不过刚开始你需要习惯这种方式。声明式UI基本上都是这种方式,了解了Flutter基本的状态更新UI,下面我们再来学习一下GetX
这个库。
GetX
是一个基于Flutter的状态管理和路由导航的解决方案,提供了简单、强大、高性能的工具和功能,以简化Flutter应用程序的开发过程。地址是:GetX,可以去了解一下,下面我们来使用它。
在项目的pubspec.yaml
文件中,将GetX添加为依赖项:
dependencies:
get:
添加位置如下图所示:
这里后面我并没有写版本号,这表示获取最新的版本,如需获取指定版本,写法如下所示:
dependencies:
get: ^4.3.8
然后点击Pub get
,获取并安装GetX库,如下图所示:
你也可以在Terminal
命令行中输入flutter pub get
命令,获取并安装GetX库。安装成功后你可以在控制台看到,如下图所示:
GetX安装成功,同时我们知道最新的版本是4.6.6。
在使用之前我们先将原有的代码分离一下,也就是将Application和Activity分开,在lib下创建一个home文件夹,文件夹下新建一个home_page.dart
文件,然后我们将main.dart
中的部分代码挪到这里面,代码如下所示:
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
State<HomePage> createState() {
return _HomePageState();
}
}
class _HomePageState extends State<HomePage> {
String test = "study state";
bool isUppercase = false;
void changeText() {
setState(() {
isUppercase = !isUppercase;
print(isUppercase ? "大写" : "小写");
});
}
Widget build(BuildContext context) {
return Scaffold(
body: Align(
alignment: Alignment.center,
child: Container(
width: 200,
height: 200,
alignment: Alignment.center,
child: Column(
children: [
Text(isUppercase ? test.toUpperCase() : test.toLowerCase()),
ElevatedButton(onPressed: () {
changeText();
}, child: const Text("按钮"))
],
),
),
),
);
}
}
然后我们再看main.dart
中的代码:
import 'package:flutter/material.dart';
import 'home/home_page.dart';
void main() {
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: "Study State",
theme: ThemeData(useMaterial3: true),
home: HomePage(),
);
}
}
现在我们就进行了分离,下面就可以在home_page.dart
中进行GetX的使用了,注意在使用的时候需要导包,在哪里用就在哪里导包,然后编辑器也会提示你导包的。
import 'package:get/get.dart';
修改代码如下所示:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class HomePage extends StatelessWidget {
HomePage({super.key});
RxString test = "study state".obs;
RxBool isUppercase = false.obs;
void changeText() {
isUppercase.value = !isUppercase.value;
if (isUppercase.value) {
test.value = test.value.toUpperCase();
} else {
test.value = test.value.toLowerCase();
}
print(isUppercase.value ? "大写" : "小写");
}
Widget build(BuildContext context) {
return Scaffold(
body: Align(
alignment: Alignment.center,
child: Container(
width: 200,
height: 200,
alignment: Alignment.center,
child: Column(
children: [
Obx(() => Text(test.value)),
ElevatedButton(onPressed: () {
changeText();
}, child: const Text("按钮"))
],
),
),
),
);
}
}
这里我们说明一下,在首先使用GetX之后,我将HomePage所继承的组件由StatefulWidget
改成了StatelessWidget
,然后为变量添加了.obs
后缀,使这个变量可观察。然后在changeText()
方法中修改可观察变量的值。最后使用Obx包裹需要局部刷新的组件,例如:Obx(() => Text(test.value))
,当可观察变量值更新时,Obx包裹中的内容就会进行刷新。
现在这种模式我们还可以再改一下,将涉及到数据改变的部分剥离出去,让我们的页面只专注于显示和更新即可,在home目录下新建一个home_controller.dart
文件,里面的代码如下:
import 'package:get/get.dart';
///Home页面控制器
class HomeController extends GetxController {
RxString test = "study state".obs;
RxBool isUppercase = false.obs;
void changeText() {
isUppercase.value = !isUppercase.value;
if (isUppercase.value) {
test.value = test.value.toUpperCase();
} else {
test.value = test.value.toLowerCase();
}
print(isUppercase.value ? "大写" : "小写");
}
}
这里就是创建一个HomeController类,继承自GetX的GetxController,注意导包别导错了,然后将changeText()
方法挪过来即可,下面我们再会到home_page.dart
中,修改代码如下所示:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:study_state/home/home_controller.dart';
class HomePage extends StatelessWidget {
HomePage({super.key});
Widget build(BuildContext context) {
final HomeController homeController = Get.put(HomeController());
return Scaffold(
body: Align(
alignment: Alignment.center,
child: Container(
width: 200,
height: 200,
alignment: Alignment.center,
child: Column(
children: [
Obx(() => Text(homeController.test.value)),
ElevatedButton(onPressed: () {
homeController.changeText();
}, child: const Text("按钮"))
],
),
),
),
);
}
}
在build()
方法中通过Get.put(HomeController())
,得到控制器对象,然后在Text
中通过控制器得到里面的值,同样可以调用里面的方法,这样我们就将业务和UI分离了。你现在运行之后效果是和之前一样的,可以试试呢。
全局刷新我们需要使用到GetBuilder
,实际上他就是setState的优化,下面我们改动一下home_controller
中的代码,如下所示:
import 'package:get/get.dart';
///Home页面控制器
class HomeController extends GetxController {
String test = "study state";
bool isUppercase = false;
String btnText = "大写";
void changeText() {
isUppercase = !isUppercase;
if (isUppercase) {
test = test.toUpperCase();
btnText = "小写";
} else {
test = test.toLowerCase();
btnText = "大写";
}
update();
}
}
这里就将.obs
后缀去掉了,就不是一个可观察变量了,同时我增加了一个参数,用于显示按钮的文字,在changeText()
方法中进行修改,最后调用update()
进行全局更新,注意update()
方法是结合GetBuilder
使用的,下面我们改写home_page中的代码,如下所示:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:study_state/home/home_controller.dart';
class HomePage extends StatelessWidget {
HomePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: GetBuilder<HomeController>(
init: HomeController(),
builder: (controller) {
return Align(
alignment: Alignment.center,
child: Container(
width: 200,
height: 200,
alignment: Alignment.center,
child: Column(
children: [
Text(controller.test),
ElevatedButton(
onPressed: controller.changeText,
child: Text(controller.btnText))
],
),
),
);
}),
);
}
}
使用GetBuilder包裹构建一个可以全局刷新的组件,在初始化时得到HomeController()
,然后在builder中就可以返回一个组件,组件中直接使用controller对象进行参数和方法的使用。
效果图如下所示:
源码地址:study_state