learn-tech/专栏/Flutter入门教程/07使用组件构建静态界面.md
2024-10-16 00:01:16 +08:00

346 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
07 使用组件构建静态界面
1. 代码的分文件管理
在计数器项目中,我们知道界面中展示的内容和组件息息相关,其中其决定性作用的是 MyHomePage 组件。但所有的代码都塞在了 main.dart 文件中,随着需求的增加,把所有代码都放一块,显然是不明智的。所以首先来看一下如何分文件来管理代码。
如下所示,先创建一个 counter 文件夹,用于盛放计数器界面的相关代码文件;然后创建 counter_page.dart 文件,并把 MyHomePage 的相关代码放入其中:
这时,可以将 main.dart 中 MyHomePage 的相关代码删除;会发现红色的波浪线,表示找不到 MyHomePage 类型:
此时只需要通过 import 关键字,在 main.dart 上方导入文件即可:
---->[main.dart]----
import 'counter/counter_page.dart';
// 略同...
分文件管理代码就像整理书籍,分门别类地进行摆放,各个区域各司其职,自己容易检阅,别人也容易看懂。下面创建一个 guess 文件夹,用于盛放本模块 猜数字 小项目的相关代码:
2. 创建你自己的组件
首先要明确一点,文件夹的名称、文件的名称、类型的名称、属性的名称、函数的名称,都是可以任意的,甚至可以使用汉字(但不建议),只要在使用时对应访问即可。但一个好名字对于阅读来非常重要,取 a1,c,b,d45,rrr 这样的名字,对阅读者而言是灾难,也许过两天,连你自己也认不得。所以一个好名字是个非常重要,不要偷懒, 最好有明确的含义。
比如对于猜数字这个需求来说,整体的界面可以叫 GuessPage , 这里我们可以先借用一下计数器中 MyHomePage 代码,照葫芦画瓢,改巴改巴。先把 MyHomePage 代码复制到 guess_page.dart 中。
重命名小技巧: 当你想对一个类型、函数、属性名进行重命名,并且想让在它们使用使用处自动修改。可以将鼠标点在名称上,右键 -> Refactor -> Rename ; 也可以使用后面的快捷键直接操作。
输入新名称后,点击 Refactor , 所有使用处都会同步更新:
然后在 main.dart 中,将 GuessPage 作为 home 参数,即可展示 GuessPage 组件中的界面效果:
---->[main.dart]----
import 'guess/guess_page.dart';
// 略同...
home: const GuessPage(title: '猜数字'),
接下来的任务是如何修改代码,来完成猜数字的功能需求。首先我们来完成一件简单的事:
点击按钮生成随机 0~99 之间的数字
初始效果
点击生成随机数
3.随机数的生成与界面更新
计数器项目中,点击按钮之所以界面数字自加,是因为 _incrementCounter 方法中,触发了 _counter++ 。所以想要在点击时显示随机数,思路很简单:将 _counter 变量赋值为随机数即可。
void _incrementCounter() {
setState(() {
_counter++;
});
}
所以首先需要了解一下 Dart 中如何生成随机数:在 dart 的 math 包中,通过 Random 类型可以生成随机数。这里生成的是随机整数,使用 nextInt 方法,它有一个入参,表示生成随机数的最大值(不包含)。比如传入 100 时,将返回 0~99 之间的随机整数:
import 'dart:math';
Random _random = Random();
_random.nextInt(100);
这样,点击按钮时只要将 _counter 赋值为随机数即可,如下所示:
void _incrementCounter() {
setState(() {
_counter = _random.nextInt(100);
});
}
但之前说过,名字非常重要。但这里 _counter 含义是计数器_incrementCounter 含义是自增计数器,在当前需求的语境中并不是非常适合。起名字最好和其功能相关,比如可以将数值变量可以称之 _value ; 方法可以称之 _generateRandomValue这样代码阅读起来就会更容易理解。
同样,也可以使用 Refactor 重命名:
class _GuessPageState extends State<GuessPage> {
int _value = 0;
Random _random = Random();
void _generateRandomValue() {
setState(() {
_value = _random.nextInt(100);
});
}
4. 头部栏 AppBar 和输入框 TextFiled 的使用
接下来,我们要将头部的标题栏 AppBar 改成如下的样式,中间是可以输入的输入框,右侧是一个运行的图标按钮。
AppBar 常用于左中右布局结构,如下所示:
AppBar(
leading: 左侧,
actions: [右侧列表],
title: 中间部分,
)
也就是说在不同的参数中,可以插入不同的组件进行显示。比如这里 leading 指定为 Icon 组件,展示图标:
leading: Icon(Icons.menu, color: Colors.black,),
actions 入参是一个组件列表,这里放入一个 IconButton 组件,展示图标按钮:
actions: [
IconButton(
splashRadius: 20,
onPressed: (){},
icon: Icon(Icons.run_circle_outlined, color: Colors.blue,)
)
],
title 入参是中间部分,使用 TextField 组件展示输入框。这里组件构造时的入参对象都是用于配置展示信息的,可以简单认识一下,不用急着背诵,以后慢慢接触,早晚都会非常熟悉。
TextField(
keyboardType: TextInputType.number, //键盘类型: 数字
decoration: InputDecoration( //装饰
filled: true, //填充
fillColor: Color(0xffF3F6F9), //填充颜色
constraints: BoxConstraints(maxHeight: 35), //约束信息
border: UnderlineInputBorder( //边线信息
borderSide: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(6)),
),
hintText: "输入 0~99 数字", //提示字
hintStyle: TextStyle(fontSize: 14) //提示字样式
),
),
最后说一下如何去掉顶部的灰块AppBar 组件在构造时,可以通过 systemOverlayStyle 入参控制状态类和导航栏的信息。如下代码可以使顶部状态栏变成透明色,文字图标是暗色:
AppBar(
systemOverlayStyle: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.dark,
statusBarColor: Colors.transparent
),
5. 叠放 Stack 组件和列 Column 组件的使用
当用户输入的数字大了,或小了。需要界面上给予提示。为了更加醒目,这里给出的设计如下所示。如果大了,上半屏亮红色,展示 大了;如果小了,下半屏亮蓝色,展示 小了:
小了提示
大了提示
可以看出此时中间的文字是浮在提示色块之上的,想实现这种多个组件层层堆叠的效果,可以使用 Stack 组件来完成。其构造函数中 children 入参传入组件列表,在显示层次上后来居上。
如下所示,将 Stack 作为 body , 在其中放入一个红色的 Container 容器组件, 以及之前的主内容。运行后会看到:中间文字就会浮在红色容器之上。
body: Stack(
children: [
Container(color: Colors.redAccent),
//主内容略...
],
),
除了堆叠的效果,还有一个问题,如何实现 上下平分区域 呢? 我们前面知道 Column 组件可以让两个组件竖直排列。在 Column 中可以通过 Expanded 组件延展剩余区域,另外 Spacer() 组件相当于空白的 Expanded。当存在多个 Expanded 组件时,就可以按比例分配剩余空间,默认是 1:1 ,也可以通过 flex 入参调节占比。举个小例子,如下所示:
Expanded 红+Expanded 蓝
Expanded 红+ Spacer
---->[红+蓝]----
Column(
children: [
Expanded( child: Container(color: Colors.redAccent)),
Expanded( child: Container(color: Colors.blueAccent)),
],
),
---->[红+空]----
Column(
children: [
Expanded( child: Container(color: Colors.redAccent)),
Spacer()
],
),
到这里,猜数字项目中需要使用的基础组件就已经会师完毕。大家可以基于现在已经掌握的知识,完成如下效果:
注: Container 组件的 child 入参可以设置内容组件。 本例参考代码见: guess_page.dart
6.组件的简单封装
上面虽然完成了布局效果,但是从 guess_page.dart 中可以感觉到,随着需求的增加,各种组件全都塞到了一块。这才只是一个简单的布局,就已经有点不堪入目了;如果再加上交互的逻辑,恐怕要乱成一锅粥了。
其实对于新手而言,编程语言语法本身并不是什么难事,对规整代码的把握才是最大的挑战;很容易要什么,写什么,最后什么东西都塞在一块,连自己都看不下去了,从而心灰意冷,劝退放弃。小学时,老师就教导我们,遇到巨大的问题,要尝试将它分解成若干个小问题,逐一解决。
其实有些大问题在肢解过程中,会有某些类似的小问题,这些小问题可以通过某种相同的解决方案来处理。这种通用解决方案,就是一种封装的思想,问题的专属解决方案一旦封装完毕,输入问题,就可以解决问题,使用者不必在意处理的过程,可以大大提升解决问题的效率。回忆一下,在介绍函数时,通过 bmi 函数,封装体质指数的计算公式,就是通过函数来封装解决方案。
对于组件来说,也是一样:某些相似的结构,也可以通过 封装 进行复用。比如这里 大了 和 小了 只是颜色和文字不同,两者的结构类似。就可以通过封装来简化代码:
最简单的封装形式是通过函数封装,通过入参来提供界面中差异性的信息,如下所示 _buildResultNotice 函数接收颜色和消息,返回 Widget 组件:
Widget _buildResultNotice(Color color, String info) {
return Expanded(
child: Container(
alignment: Alignment.center,
color: color,
child: Text(
info,
style: TextStyle(
fontSize: 54, color: Colors.white, fontWeight: FontWeight.bold),
),
));
}
如果把相似的结果写两遍,只会徒增无意义的代码。而封装之后,只需要调用方法,就可以完成任务:
Column(
children: [
_buildResultNotice(Colors.redAccent,'大了'),
_buildResultNotice(Colors.blueAccent,'小了'),
],
),
如果封装体的代码非常复杂,或者需要单独维护,以便之后修改方便定位,也可以通过新组件的形式来封装组件。如下所示,新创建 result_notice.dart 文件,在其中定义 ResultNotice 组件,专门处理结果提示信息的展示任务。
一方面,专人专用,需要更改时直接在这里更改。比如你让另一个人帮忙改改某处的代码,而他对项目不熟悉,如果代码分离的得当,你告诉他这个界面由 xxx.dart 文件负责,他就可以在不了解项目的前提下,对界面进行修改。这就是 职责分离 的益处。不同人干自己擅长的事,有利于整体结构的稳定。
另一方面也能缓解 guess_page.dart 中的代码压力,不至于随着需求的增加代码量激增,对可读性友好。在使用时,可以将 ResultNotice 视为普通的组件,放在 Column 之中:
Column(
children: [
ResultNotice(color:Colors.redAccent,info:'大了'),
ResultNotice(color:Colors.blueAccent,info:'小了'),
],
),
同样,这里 AppBar 组件的构建逻辑也是太复杂的,可以在 guess_app_bar.dart 中单独维护。这里主要是出于简化 guess_page.dart 中代码的考量,不强求可复用性。
这样 guess_page.dart 中的代码就整洁了很多,其他两个文件也在各司其职。相对与之前全塞在一块,更便于阅读,封装之后的代码见 guess_page.dart 。
7.本章小结
本章主要学习了如何通过 Flutter 框架提供的组件,来搭建期望的界面呈现效果。期间介绍了如何分文件来管理代码、以及通过自定义组件来封装构建逻辑。最后简单分析了一下组件封装的优势。
学完本章,你应该能够自己动手搭建一些简单的静态界面了。但应用程序是要和用户进行交互的,就需要界面随着用户的交互进行变化。下一篇将从用户交互的角度,通过代码来实现猜数字的具体功能。