Flutter 上篇
📢
本文建议配合Demo食用,Demo源代码仓库
附录是一些细碎知识点补充
在 Flutter 中,Widget
是构建应用界面的基本元素。就连Flutter App本身都是一个Widget
,每个界面都由一个或多个小部件组合而成,Widget
可以是一个简单的元素,如按钮或文本,也可以是复杂的布局和功能组件,例如一个瀑布流信息浏览页面(微博 or QQ空间)
接下来,
CTRL
+鼠标左键
很有用。
新建Flutter项目
首先确保你已安装VSCode中的Flutter插件,具体流程参考:https://kdocs.cn/l/crgD6rf4y8mu?linkname=sKNsYxjiv0
首先你找到一个地方新建文件夹,然后在VSCode里面打开这个文件夹,如果这一步你不知道怎么操作,请看这个B站视频:https://www.bilibili.com/video/BV1crxxewEeD/?spm_id_from=333.999.0.0&vd_source=1b90488889528a7f994c9a8725e95872
然后按下键盘上的Ctrl + Shift + P
打开VSCode的命令面板,输入"flutter"字样(不包括引号),你会看到如下提示:
使用键盘上的上下方向进行选择,选中第一个选项,回车。这时候我们就来到了新建Flutter项目的引导:
Application选项新建的项目包含了一个Flutter官方的示例程序,有兴趣的可以看看。
我们这里选择Empty Application,回车。
然后就会打开一个文件夹选择窗口,大家选择刚才你新建的那个文件夹就行。选中后就是给项目取名了:
注意这里有一些命名规则:不同单词之间用下划线分隔,不能有空格和特殊符号,不能有大写字母。
我这里就取一个"little_demo"吧。输入完名字后回车,这时候VSCode会自动运行Flutter项目的初始化脚本,等待项目新建完毕(这一步取决与你的网速,一般来说开着魔法会很快)
导入别人的项目
这个就简单了,如果别人的项目是一个压缩包的话,那你要先找一个地方解压出来,然后进入项目文件夹,接着在文件夹的空白处右键,然后选择"通过Code打开":
打开VSCode后,会进行一些加载,待VSCode的底栏没有任何转圈或者加载中的提示时,就可以进行下一步了。
首先查看lib文件夹:
简直是一片爆红啊(
要解决上方爆红的问题,我们可以来到VSCode的控制台,执行flutter pub get
命令,记得把魔法开着,不然大概率会一直卡住:
命令执行完后可以看到爆红就消失了。
可能你会发现android那个文件夹仍然爆红,这是正常的,不用管
然后,就没有然后了,你的项目就导入成功了。下面的"运行项目"一节是针对新建项目编写的,对于导入的项目也适用,大家依葫芦画瓢就行。
运行项目
进入项目后,可以看到项目初始有一个在手机屏幕上展示Hello World的代码,就在lib/main.dart这个文件中,大家可以点进去看看。其他文件及文件夹的含义我们后面会介绍。这就是一个Flutter版本的HelloWorld程序。我们来运行一下。
首先要打开我们的虚拟机,我们把目光聚焦到VSCode的右下角:
可以看到在这个小铃铛旁边有一个选项,大家看到的可能是No Devices,没关系,我们点击它试试
点击过后,VSCode就会弹出一个设备选择框:
如果你之前已经在Android Studio中创建过虚拟机,你应该能看到上图中红色方框圈起来的两个设备,那就是你创建的虚拟机,你可能会问:“为什么有两个呀?”,那是因为第一个是热启动,第二个是冷启动,区别这里不赘述,有兴趣自行上网搜索,我们一般选择热启动,好处是速度快,冷启动一般是虚拟机遇到一些未响应的问题时使用。
点击第一个选项,等待虚拟机启动:
我们可以看到一个手机就活灵活现地出现在我们的屏幕上了(
打开了虚拟机,我们就可以开始运行项目了。这次再点右上角的播放按钮就行不通了,我们得去到下面这里:
点击Run and Debug按钮,第一次运行时VSCode会让我们选择调试器,选择Dart & Flutter选项即可。
第一次启动项目会下载一些文件,此时开着魔法会下得很快(没错又是魔法,魔法真好,等待一会,你就会看到虚拟机上有反应了:
一句Hello World就输出到屏幕上来了!
项目目录结构
接下来说一下我们刚才都新建了些啥
- .dart_tool:记录了一些dart工具库所在的位置和信息
- .idea:Android Studio的项目设置,是的,通过VSCode新建的项目也是可以通过Android Studio打开的,至于为什么叫.idea这个名字,是因为Android Studio是用IDEA实现的
- .vscode:顾名思义,是vscode的一些项目设置
- android:安卓项目文件夹
- build:flutter的最终构建完成的文件存放路径,例如安卓平台的apk
- ios:iOS项目文件夹
- lib:flutter源代码文件夹,我们新建的代码文件就存放在这个文件夹里面
- linux:Linux项目文件夹
- macos:macOS项目文件夹
- web:网页项目文件夹
- windows:Windows项目文件夹
- .gitignore:git的忽略文件,详情自行搜索,不在培训的范围内
- .metadata:IDE 用来记录某个 Flutter 项目属性的的文件
- analysis_options.yaml:静态分析文件
- 项目名.iml:工程文件的本地路径配置
- pubspec.lock:当前项目依赖所生成的文件
- pubspec.yaml:当前项目的一些配置文件,包括依赖的第三方库、图片资源文件等
- README.md:一个markdown文件,由自己编写,用于说明这个项目的信息,项目上传到github这类代码托管平台时,会将这个文件作为项目的介绍页面
Widget 的分类
Flutter 的 Widget
主要分为两类:
- **无状态 Widget (StatelessWidget):**一个不可变的组件,它的状态在构建后不会改变。适用于不需要动态更新的 UI,如显示静态文本或图标的组件。
- 有状态 Widget (StatefulWidget):一个有状态的组件,它可以在生命周期中发生变化并重新渲染。适用于需要根据用户交互或其他因素更新的 UI,如按钮点击后的状态变化或从网络加载数据的组件。
StatefulWidget
我们留到后面说。
大多数组件都可以拥有子组件,在Flutter中,一个组件的子组件是通过其child属性或children属性(有多个子组件)传给它的,我们后面会详细介绍
我们先看看StatelessWidget
,StatelessWidget
是一种不可变的组件,它在构建时就确定了,不会再改变,适用于那些只依赖于外部输入数据(如构造函数参数)的组件。它通常在build
方法中通过嵌套其他 widget 来构建UI(自己搓心仪的组件),在构建过程中会递归的构建其嵌套的 widget。
StatelessWidget 示例
1 | import 'package:flutter/material.dart'; |
更详细的内容可以看看这里,或者自己看看底层代码(bushi
常用StatelessWidget
下面介绍几个基础的StatelessWidget
Row和Column
根据它们的英文含义就能猜出来,他们可以将其他组件按照行和列的形式排列起来。
基础用法
1 | Row( |
效果:
1 | Column( |
效果:
被这该死的摄像头挡住了(
这里的摄像头遮挡我们会在后面学习如何解决
Text组件我们后面介绍,在这里你只需要知道它可以显示一个文本
常用属性
💡
-
mainAxisAlignment
:主轴对齐方式。 -
crossAxisAlignment
:交叉轴对齐方式。 -
children
:子组件列表。
Row和Column的属性完全是对应的,这里以Row为例来介绍
mainAxisAlignment
主轴方向上的对齐方式,Row的主轴是x轴,Column的主轴是y轴,使用示例:
1 | Row( |
效果:
除此之外还有其他的属性,见下图:
crossAixsAlignment
纵轴方向上的对齐方式,纵轴就是与主轴垂直的轴,Row的纵轴是y轴,Column的纵轴是x轴,使用示例:
1 | Row( |
效果:
我们把后面的那个Text组件的字体设置的很大,撑开整个Row在y轴上的空间,这样我们设置的对齐效果才能看得出来。可以发现"Hello,"字样已经在Row的顶上去了。CrossAxisAlignment还有其他值,大家可以动手试试。
📌
下图是Row和Column的主轴和纵轴的区别
Container:容器
这是最为基础的组件,可以用于包裹其他组件以限制其他组件的大小、提供间距、背景颜色、装饰等等
基础用法
1 | Container( |
效果:
上面的代码创建了一个宽为200,高为100,背景颜色为黄色且子组件是一个文本的Container容器。
In other words,这个Text组件被一个宽为200,高为100,背景颜色为黄色的容器包裹起来了
有关如何设置颜色,请查看Flutter中的颜色一节
📌
Tips:child属性表示子组件,Flutter中的大部分组件都有这个属性(如果有多个子组件,那么该属性名为children,是一个数组类型,复数很合理吧),其实Flutter App就是一个很大的组件树,这个组件树就是通过父子组件关系来构建的。
常用属性
💡
-
margin
: 外边距。 -
padding
:内边距。width和height包含padding值。 -
width
:宽度。 -
height
:高度。 -
alignment
:child对齐的属性,居中、左对齐、右对齐之类的。 -
color
:背景颜色。 -
decoration
:设置装饰,如边框border
、圆角borderRadius
、背景色color
、背景图片backgroundBlendMode
等。不能给Container同时设置decoration和color属性,如果要在有decoration时设置背景颜色,给decoration设置color就可以。 -
constraints
:大小范围约束,constraints有四个属性:minWidth、minHeight、maxWidth、maxHeight。 -
child
:子组件,可以不设置,但是最多只能有一个。
margin、padding
margin:外边距,padding:内边距,这俩的区别见下图:
大家只需关注上图中的Content、Margin和Padding部分
我们来修改一下上面的代码:
1 | Container( |
可以看到发生了如下变化:
文字离边沿更远了,这说明我们设置的内边距生效了
对与margin外边距,可以控制不同组件之间的间距,例如:
1 | Row( |
效果:
没有margin:
alignment
控制子元素在其内部的对齐方式
例如:
1 | Container( |
效果:
如果没有alignment: Alignment.center
,那么将会是这样的:
decoration
该属性不能和color同时使用!否则会导致异常。如果你需要设置背景颜色,在decoration中也有一个color属性。
设置装饰,这里面有许多有趣的属性
下面是一个使用decoration属性制作的卡片:
1 | Container( |
效果:
PS:这堆代码咱们上课来分析,现在看不懂没关系
从这个例子也可以看出,通过不同组件之间的嵌套,我们可以设计出任何想要的UI界面
Expanded:自动扩展组件(实在没找到中文名(
❗
该组件仅在作为Row和Column直接子组件时才有效果!
子组件:包括了子组件的子组件
**直接子组件:**不包括子组件的子组件
这个组件可以让其内部的子组件大小扩展,扩展的幅度通过其flex
属性来控制,如果不指定flex
则默认为1,example:
1 | Row( |
效果:
加上flex
:
1 | Row( |
效果:
实际上,Expanded组件有以下规律:
- Expanded包裹一个组件时,这个组件自身的大小设置会被忽略
- 被拓展后的大小取决于其flex值,拥有较大 flex 值的组件将占据更多空间。具体来说是,flex1:flex2:flex3:…:flexn
- 拓展的方向取决于是在Row中还是在Column中,在Row中时为x轴拓展,在Column中时则为 y轴拓展。大家可以自己写写Column中的Expanded
- 拓展的范围取决于Row或者Column组件的大小,上例中由于Row组件和屏幕一样宽,所以才将整个屏幕的宽占满了。
- 未被Expanded包裹的组件尺寸不会受到影响,且其尺寸会减少Expanded的范围
Text:文本组件
这个组件用于在Flutter App中显示最基础的文字,它给我们提供了丰富的文本样式,是每个Flutter应用都会大量使用的组件。
基本用法
1 | Text( |
这会显示为一个普通的文本
常用属性
style
用于设置文本的样式,例如,我想要一个字号为30,颜色为红色、粗体、背景为绿色、字体为San Francisco的样式:
1 | Text( |
效果:
这配色太丑了(
这里同时使用了位置参数(第一个字符串参数)和命名参数(style参数)
如何添加并使用自定义字体请查看这一节:添加自定义字体
📌
定义文本样式使用了TextStyle类,这个类中除了可以定义字体大小、颜色、背景颜色、粗细、字体样式,还有许多有趣的属性,想详细了解可以去看看这篇文章:https://juejin.cn/post/6844903829389967368
富文本
简单来说,富文本就是在普通文本基础上增加了多种格式和对象,让文本内容更加丰富和具有表现力。
在Flutter中,我们通过Text.rich()
构造函数来使用富文本,Here is a example:
1 | Text.rich( |
效果:
这段代码基本上没什么好解释的,大家仿照上面的写法就能写出自己想要的样式
Stack:堆叠容器
Stack
组件允许子组件重叠显示。
*常用属性***
💡
-
alignment
:此参数决定如何去对齐没有定位(没有使用Positioned
)或部分定位的子组件。所谓部分定位,在这里特指没有在某一个轴上定位:left、right为横轴,top、bottom为纵轴,只要包含某个轴上的一个定位属性就算在该轴上有定位,而没有定位的那个轴就会遵循alignment
的定位方案 -
fit
:用来决定没有Positioned
方式时候子组件的大小,StackFit.loose
指的是子组件多大就多大,StackFit.expand
使子组件的大小和父组件一样大。 -
clipBehavior
:此属性决定对超出Stack
显示空间的部分如何剪裁,Clip枚举类中定义了剪裁的方式,Clip.hardEdge
表示直接剪裁,不应用抗锯齿(性能较高)。 -
children
:子组件列表。
*示例***:
1 | Stack( |
写到children****列表后面的子组件会显示在写在前面的子组件的上面,类似PS中的图层******
Positioned:堆叠定位
❗
Positioned仅在作为Stack的直接子组件时起作用!
Positioned 的默认构造函数如下:
1 | const Positioned({ |
left
、top
、right
、 bottom
分别代表离Stack
左、上、右、底四边的距离。
width
和height
用于指定需要定位元素的宽度和高度。
注意,Positioned
的width
、height
和其他地方的意义稍微有点区别,此处用于配合left
、top
、right
、 bottom
来定位组件,举个例子,在水平方向时,你只能指定left
、right
、width
三个属性中的两个,如指定left
和width
后,right
会自动算出(left
+width
),如果同时指定三个属性则会报错,垂直方向同理。
1 | Stack( |
📢
写在后面:上面讲的是平时用得比较多的布局组件,除此之外还有流式组件wrap等,感兴趣的同学们可以自己研究一下。
按钮组件
按钮有很多种类,但是都是大差不差,仅仅是外观上的区别,它们基本上都有一个onPressed属性,这个属性是一个回调函数,指定按钮被按下时会发生什么。
ElevatedButton
一个胶囊状的按钮,例如:
1 | ElevatedButton( |
当我们按下按钮时,注意观察控制台,输出了Hello ElevatedButton字样,看到这里大家是否又更深刻地理解了回调函数了呢?
在这个onPressed回调函数里,我们能做的可不止简单地打印语句,进行网络请求、打开新页面、改变UI显示…各种功能都可以完成,只有你想不到,没有它做不到,这就是回调函数的魅力所在。
TextButton
1 | TextButton( |
这个按钮看起来就非常简陋了,只有文本
IconButton
1 | IconButton( |
图标按钮,我们可以用它来当作诸如点赞、收藏等按钮
Scaffold:页面骨架
实际上,我们上面写的所有代码都是作为Scaffold的子组件,如果没有Scaffold的话样式就会变得很奇怪,那么这个Scaffold有什么用呢?
它可以大幅简化我们的页面构建,它提供了一些基本的配置,例如它会把我们设置的主题传递给其下的所有子组件,这样我们才能访问到主题色及配色方案,它还为我们提供了appBar、body等属性帮助我们快速构建页面,初次之外他还有bottomNavigationBar、floatingActionButton等等属性,大家可以自行尝试。
示例代码请查看Demo
资源管理
Flutter 中的资源指的是你应用中的非代码文件,比如图片、图标、字体、音频等。为了让这些资源在应用中可用,你需要把它们添加到项目中,除了将你的资源文件移动到项目文件夹中,还需要在你的 pubspec.yaml
文件中进行配置,这样Flutter才能成功识别你的文件。
下面介绍如何添加图片和字体资源,其他资源类似,请大家自行上网搜索。
添加图片资源
准备图片
在项目的根目录下(即 pubspec.yaml 文件所在的目录),创建一个名为 assets 的文件夹,然后将你需要的图片放入该文件夹中。比如:
1 | /assets/ |
配置图片资源
接下来,我们需要在 pubspec.yaml 文件中配置这些图片资源。打开 pubspec.yaml 文件,找到flutter字样,并添加如下代码:
1 | flutter: |
如果有多个图片,也可以一次性添加整个文件夹:
1 | flutter: |
这样你就可以使用 asset/images/
文件夹中的所有图片资源了。
图片资源的使用方法请跳转到:Image.asset():从资源加载图片
添加自定义字体
接下来,我们来看如何在 Flutter 中添加自定义字体。
准备字体文件
首先,和图片类似,创建一个文件夹来存放你的字体文件。比如:
1 | /assets/fonts/ |
配置字体资源
同样需要在 pubspec.yaml 文件中进行配置,找到 flutter 部分并添加为如下代码:
1 | flutter: |
这里 family 是自定义字体的名称,稍后我们将在代码中使用。asset:
后面的内容是字体文件的存放位置。
使用自定义字体
我们可以在TextStyle
的fontFamily
属性中使用自定义字体,如下:
1 | Text( |
注意,有些字体本身仅某些语言,这意味着当前字体不支持的语言字符将会显示为默认字体
Image图片组件
Image.asset():从资源加载图片
如何将资源文件添加到项目中请参考:添加图片资源
配置完成后,你可以在代码中使用图片资源。Flutter 提供了 Image 组件来加载图片资源:
1 | import 'package:flutter/material.dart'; |
Image.network():从网络加载图片
这个比从资源加载图片稍微简单一些,你只需要找到网上图片的URL就可以将它加载到Flutter里面了,具体来说就是当你在浏览器中看到一张图片时,**你可以右键它,然后复制图片地址(或者其他类似的选项),**接下来就可以按照下面的方法来将其显示在你的App中了:
1 | Image.network('https://picsum.photos/250?image=9'), // 加载图片 |
其余代码和上面的例子相同,注意如果你网络情况较差或者你的图片来自外国网站,可能要等一会才能加载出图片来
附
Flutter中的颜色
Flutter中给我们预设了很多颜色,我们可以通过Colors.颜色名称
的方式来使用这些颜色:
1 | Colors.red |
当然,这些颜色肯定是不全的,我们可以通过ARGB或者HEX的方式来自定义颜色,例如:
1 | // 使用ARGB的方式 |
📌
ARGB:Alpha,Red,Green,Blue的缩写,后面三个好理解,Alpha表示颜色的透明度。这四个值的范围都是[0, 255],对于Alpha,值越小颜色越透明,色彩理论不在我们的讨论范围内,我们只需要知道通过调整ARGB这四个值的比例,可以调出任意颜色就可以了
这里要解释一下HEX色值,HEX其实就是十六进制,十六进制中的A,B,C,D,E,F分别表示十进制中的10,11,12,13,14,15,在Dart中,为了区分十六进制和十进制的数,十六进制的数都要使用0x
打头。而对于颜色值,除开打头的0x
,我们有八位,从左至右每两个一组,分别表示A,R,G,B的值:
那么两位十六进制数能表示的范围又是多少呢?应该是[00, ff]对吧,我们将其转换为十进制:[00, 255],
会发现与ARGB的范围是相同的!所以HEX和ARGB在表示颜色的能力方面都是一致的。
📌
有些时候我们发现了一些好看的颜色,想要知道它的HEX或者RGB值是多少的话该怎么办呢?目前有很多颜色吸取工具,这里推荐一个Windows平台上工具,PowerToys,是微软官方的一个工具,除了颜色吸取还有很多其他方便的功能,在Windows自带的应用商店即可下载。
EdgeInsets
这个类可以用来创建Flutter中的内外边距,以下是几个常见的用法:
1 | EdgetInsets.all(10); // 在上下左右均有10的间距 |
SizedBox
一个透明的,大小固定的矩形组件,一般用于我们想要让两个组件之间有所间隔,但是又不想设置margin的情况,例子:
1 | Row( |
加上SizedBox:
1 | Row( |
肉眼可见地间距变大了(
SizedBox就两个属性,一个width,一个height,分别控制其宽和高
Icon:图标
这个含义不用多解释了吧(,直接上代码:
1 | Center( |
Icon组件需要一个IconData来知道它自己该显示什么图标,Flutter给我们预设了很多图标,通过Icons.图标名
就可以访问了,除此之外,Icon还能设置颜色和大小。
analysis_options.yaml文件
这是用来配置我们编写程序时的语法检查规则的文件。所谓的语法检查,就是当我们写了一些错误的代码时,能在运行前给我们报错,写了多余代码或者是可以有明显优化的代码时会给我一些警告或者建议。
当然由于有一些警告实在是太烦人了(,有些甚至导致你满屏的代码都出现蓝色的波浪线(优化建议),看着糟心。我们可以通过这个文件来自定义语法检查规则。
现在打开demo中的analysis_options.yaml文件,我在里面预先填写了一些内容,它一开始的内容只有一行代码:
1 | include: package:flutter_lints/flutter.yaml |
大家可以试试只保留上面这一行代码,保存后回到main.dart里面看看:
真是一篇爆蓝啊(
所以还是加上我预设好的代码吧(,不然写const
能让你写到似((
至于这个文件的具体配置规则和配置项,大家可以参考官方文档:https://dart.cn/tools/analysis/#individual-rules
Widget Inspector:很好用的调试工具
大家在运行Demo项目时,可能注意到了,当项目跑起来的时候,VSCode会自动给我们打开一个名为Widget Inspector的窗口:
如果你的界面不是向我这样左右分开的,你可以按照以下步骤将某个标签页分到侧边去:
右击你想要分屏的页面的Tab标签,然后根据你想要分屏的位置,选择红色框框起来的选项:
至于如何使用,大家可以自己用这个工具点一点探索一下,在点击时注意虚拟机上的变化
Flutter中的主题
主题就是整个应用看上去的视觉效果,包括了整个App的颜色、字体、阴影、动画等等。大家可以试着修改一下Demo中设置App主题的那一段代码(在main.dart文件中,已用注释标出),看看界面有什么变化。如果遇到程序崩溃的问题那就Ctrl+Z。
Flutter中的上下文BuildContext
涉及到Flutter的底层原理了,去看看大佬写的:https://book.flutterchina.club/chapter14/element_buildcontext.html
不用看也可以,这个确实太底层了(
怎么越来越深入了啊喂