第5章 Flutter框架基本组件的使用 51min Flutter框架设计目标为给用户呈现丰富多彩的外观。我们将从基本的组件开始,逐步讲解Flutter中各种组件的用法。这一章,主要讲解Container容器组件、Text文本组件、Icon图标组件、Image图片组件、屏幕适配、生命周期、StatefulWidget有状态组件和StatelessWidget无状态组件等。 5.1Flutter架构组成 Flutter 被设计为一个可扩展的分层系统,各个组件相互独立,上层组件依赖下层组件。组件无法越权访问更底层的内容,框架层中的部分都是可选且可替代的。 Flutter框架结构图分为三部分: Framework、Engine和Embedder,如图51所示。 图51Flutter架构层 Framework指使用Dart编写的Flutter框架,其中Material指实现了材质设计(Material Design)的Flutter组件,是官方推荐使用的组件类型。在使用时需要导入import package:flutter/material.dart包。本书也是以Material Design为主讲解Flutter组件的使用。 Cupertino为iOS风格的组件,它有很多组件与Material Design类似。 Widgets库是构建Flutter框架的基础,Rendering库实现了Flutter的布局和后台绘制,Animation库实现了Flutter的动画功能,Painting库包括包装了Flutter引擎的绘制API,实现的功能如绘制缩放图像、阴影之间的插值、绘制方框周围的边框等。Gestures库主要包含和手势识别相关的类,如单击、拖曳、长按、悬浮等。foundation库中定义的功能是Flutter框架所有其他层使用的最低级别的实用程序类和函数。 Engine为Flutter 引擎,使用C/C++ 编写,并提供了 Flutter 应用所需的原码。当需要绘制新一帧的内容时,引擎将负责对需要合成的场景进行栅格化。它提供了 Flutter 核心 API 的底层实现,包括图形(通过 Skia)、文本布局、文件及网络 IO、辅助功能支持、插件架构和 Dart 运行环境及编译环境的工具链。如Platform Channels平台通道,可以实现把Flutter嵌入一个已经存在的应用中,实现Flutter框架的Dart语言与kotlin和Swift的相互通信。 Embedder指平台嵌入层,用于呈现所有 Flutter 内容的原生系统应用,它充当着宿主操作系统和 Flutter 之间的粘合剂的角色。当启动一个 Flutter 应用时,嵌入层会提供一个入口,初始化 Flutter 引擎,获取用户界面和栅格化线程,创建 Flutter 可以写入的纹理。嵌入层同时负责管理应用的生命周期,包括输入的操作(例如鼠标、键盘和触控)、窗口大小的变化、线程管理和平台消息的传递等。Flutter 拥有 Android、iOS、Windows、macOS 和 Linux 的平台嵌入层,当然,开发者可以创建自定义的嵌入层。 5.2MaterialApp Flutter材质应用 Flutter界面设计主要以Material Design 风格应用为主,Material是一种移动端和网页端通用的视觉设计语言。Flutter 提供了丰富的具有Material 风格的组件,通过它可以使用更多 Material 的特性,相应的网址为https://materialio.cn/。 MaterialApp包含材料设计应用程序通常需要的许多组件,如导航、路由、主页、样式、多语言设置、调试等。MaterialApp的主要组成功能如下: //第5章5.1 MaterialApp中的主要属性及用法 MaterialApp({ this.navigatorKey,//导航键,key的作用是提高复用性能 this.home, //主页 this.routes = const {}, //路由 this.initialRoute, //初始路由名称 this.onGenerateRoute, //路由构造 this.onUnknownRoute, //未知路由 this.builder, //建造者 this.title = '', //应用的标题 this.onGenerateTitle, //生成标题回调函数 this.color, //App 颜色 this.theme, //样式主题 this.darkTheme, //主机暗色模式 this.themeMode = ThemeMode.system, //样式模式 this.locale,//本地化 this.localizationsDelegates, //本地化代理 this.supportedLocales = const [Locale('en', 'US')],//本地化处理 this.deBugShowMaterialGrid = false,//是否调试显示材质网格 this.showPerformanceOverlay = false, //是否显示性能叠加 this.checkerboardRasterCacheImages = false,//检查缓存图片的情况 this.checkerboardOffscreenLayers = false,//检查不必要的setlayer this.showSemanticsDeBugger = false,//是否显示语义调试器 this.deBugShowCheckedModeBanner = true,//是否在右上角显示Debug标记 }); 5.3Scaffold脚手架 Scaffold 是一个页面布局脚手架,实现了基本的 Material 布局,继承自 StatefulWidget有状态组件。像大部分的应用页面,包含标题栏、主体内容、底部导航菜单或者侧滑抽屉菜单等,每次都重复写这些内容会大大降低开发效率,所以 Flutter框架提供了具有Material 风格的 Scaffold 页面布局结构,可以很快地搭建出这些元素。与Scaffold相关的组件如下: //第5章5.2 Scaffold主要属性及用法 const Scaffold({ this.appBar, //标题栏 this.body, //主体部分 this.floatingActionButton, //悬浮按钮 this.floatingActionButtonLocation, //悬浮按钮位置 this.floatingActionButtonAnimator, //悬浮按钮动画 this.persistentFooterButtons, //固定在下方显示的按钮 this.drawer, //左侧侧滑抽屉菜单 this.endDrawer, //右侧侧滑抽屉菜单 this.bottomNavigationBar, //底部导航栏 this.bottomSheet, //底部拉出菜单 this.backgroundColor, //背景色 this.resizeToAvoidBottomPadding, //自动适应底部 this.resizeToAvoidBottomInset, //重新计算body布局空间大小,避免被遮挡 this.primary = true, //是否显示在屏幕顶部,默认值为true,将显示到顶部状态栏 this. onDrawerChanged, //侧滑抽屉菜单改变回调函数 }) 5.4标题栏的显示 Scaffold是 Material 库中提供的一个组件,它提供了默认的导航栏、标题和包含主屏幕组件树的 body 属性等特性,body属性为导航栏下的区域,它是面积最大的区域。丰富的外观是由层层嵌套复杂的组件树组成的。 使用Scaffold在移动端的导航栏上显示标题,内容为Home,代码如下: //第5章5.3 Flutter应用中标题的显示 MaterialApp(//第1行 home: Scaffold(//第2行 appBar: AppBar(//第3行 title: const Text('Home'),//第4行 ), ), deBugShowCheckedModeBanner: false, //第7行 ) 上述关键代码表示的含义如下: 第1行表示使用MaterialApp构造页面界面布局。 第2行表示在屏幕的主要区域使用了Scaffold脚手架实现布局。 第3行表示应用标题栏,使用了Scaffold组件的appBar属性。 第4行表示appBar属性通过AppBar组件的title属性,通过Text文本组件将内容显示为Home的标题。 第7行表示隐藏标题栏上的Debug调试图标。 显示的效果如图52所示。 图52Flutter导航栏中显示标题 由于使用了MaterialApp,导入的库为import 'package:flutter/material.dart';,完整的main.dart源代码如下: //第5章5.4 Flutter导航栏标题的显示 import 'package:flutter/material.dart'; void main() { runApp(Center( child: Chapter5Demo(), )); } class Chapter5Demo extends StatelessWidget { const Chapter5Demo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text("Home"), ), ), ); } } 程序的运行设置类似于3.2节,可以在VS Code中输入,运行上面的代码以观看效果。 5.5Container容器组件 Container容器组件,是用得比较多的组件,常用的属性有颜色、位置、长度、宽度等,Container容器使用child属性包含其他的组件。 Container常见的属性见表51。 表51Container常见属性及含义 width设置容器的宽度 height设置容器的高度 margin容器与周围其他组件的距离 padding容器内部组件与边界及其他组件的距离 alignment容器内子节点组件的对齐方式 color容器的颜色 child包含其他子组件 decoration对容器组件的样式进行修饰 transform在绘制图形前对图形进行转换 在上面代码的基础上可以对Scaffold增加body属性,修改后的代码如下: //第5章5.5 Container容器的使用 @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text("Home"), ), //在此行下增加代码 body: Container(//增加的第1行 margin: const EdgeInsets.all(20.0), //增加的第2行 color: Colors.red, //增加的第3行 width: 96.0, //增加的第4行 height: 96.0, //增加的第5行 ), //增加的第6行 ), ); } 在上面的代码中,增加的第1行代码表示在Scoffold使用body属性添加容器属性。 增加的第2行代码表示容器的空白上下左右都为20,这里的数字表示逻辑像素。 增加的第3行代码表示容器的颜色为红色。 增加的第4行代码表示容器的宽度为96。 增加的第5行代码表示容器的高度为96。 上述代码表示在屏幕的左上角显示一个容器,颜色为红色,宽度为96,高度为96,容器和四周的空白为20,标题栏显示为Home,效果如图53所示。 图53容器的显示 5.6文本Text组件 文本Text组件主要用于显示文本内容。在容器中可以显示文本信息,只要加入一行语句就可以了。通过热加载(Hot Reload)技术自动在虚拟机的容器上显示文本内容,代码如下: //第5章5.6 Flutter应用容器中Text文本的显示 body: Container( margin: const EdgeInsets.all(20.0), color: Colors.red, width: 96.0, height: 96.0, child: Text("你好"), //此处为增加的语句 ), 增加的语句表示可以通过容器的child属性添加子组件,此处添加的子组件为Text文本组件。对文本组件中字体的大小、颜色、行高、间距等也可以设置调整,将在第7章Flutter样式中进行具体讲解。 5.7图标Icon组件 Flutter自带有大量的图标,可以以多种形式显示,可以从中选择我们需要的图标,以适合应用的要求。官方网址为https://www.fluttericon.com。在容器中显示一个心形图标,颜色为品红色,大小为72。可以修改前述代码中的child中的内容,代码如下: //第5章5.7 Flutter应用容器中Icon图标的显示 body: Container( margin: const EdgeInsets.all(20.0), color: Colors.yellow, width: 200, height: 200, child: Icon( //添加图标组件 Icons.favorite, //心形图标 color: Colors.pink, //颜色为品红 size: 72.0, //心形图标大小为72 ), ), 运行效果如图54所示。 图54在容器中显示心形图标 5.8图片Image组件 可以使用Image组件通过内存、文件、资源或网络等方式访问图片,实现图片的显示。Flutter主要支持以下格式的图片,如JPEG、PNG、 GIF、BMP、Animated GIF等。 Image组件的主要属性如下: //第5章5.8 Image图片组件中主要属性的用法 const Image({ required ImageProvider image,//要显示的图像 ImageFrameBuilder? frameBuilder,//创建图像生成器函数,例如可以设置动画效果 ImageLoadingBuilder? loadingBuilder,//正在加载时的效果 ImageErrorWidgetBuilder? errorBuilder, //加载图片错误时调用此函数 String? semanticLabel, //对图像的描述 double? width, //图像宽度的设置 double? height, //图像高度的设置 Color? color, //如果非空,则使用colorBlendMod将此颜色与每个图像像素混合 Animation? opacity, //如果非空,则在绘制到画布上之前,动画中的值将与每个图像 //像素的不透明度相乘 BlendMode? colorBlendMode,//颜色与此图像组合 BoxFit? fit, //图像在布局中的显示方式 AlignmentGeometry alignment = Alignment.center,//图像在内部的对齐方式 ImageRepeat repeat = ImageRepeat.noRepeat,//当空间多余时,是否重复显示图像 Rect? centerSlice, //图像适应目标的方式 bool isAntiAlias = false,//是否使用抗锯齿绘制图像 FilterQuality filterQuality = FilterQuality.low //图像的渲染质量 }) 5.8.1网络图片的显示 可以在Flutter应用中显示网络图片。在移动端加载网络图片时,首先要配置工程的网络请求权限。在VS Code工程中打开AndroidManifest.xml文件(路径: android/app/src/main),在application上面添加以下内容: 其次,定义变量_url,指明网络图片的地址(此为示例,由于网络图片地址可能会有变动,读者也可以自己在网络上找到一张图片地址替换书中的图片网络地址)。 最后,添加Image.network( _url, fit: BoxFit.fill,)代码实现在Flutter程序中显示图片,其中fit: BoxFit.fill表示图片填充整个容器,代码如下: //第5章5.9 网络图片的显示 //指明图片地址 static const String _url = "http://image.biaobaiju.com/uploads/20180531/02/1527706049-cVJjelLyiS.jpg"; ... body: Container( margin: const EdgeInsets.all(20.0), width: 200, height: 200, child: Image.network( //添加Image组件,以显示图像 _url, fit: BoxFit.fill, loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { //图像加载过程的处理 if (loadingProgress == null) { //为空显示图像 return child; } return Text('加载中…'); //加载图像过程显示文本提示内容 }, ), ), 运行结果如图55所示。 图55在容器中显示图像 loadingBuilder()函数表示在加载过程中,可以使用替代文本。由于网络上有的图像较大且网络速度慢等原因,加上文本提示后,可以让用户感觉到正在加载图像,这样用户的体验会更好。在loadingBuilder()中,判断是否图像已加载完成,如果未加载完成,则显示文本信息'加载中…',如果已完成,则显示图像。 Image组件中的fit属性表示图像的显示方式,如是否拉伸、填充等。常见的值见表52。 表52Image fit属性的类型 fill全图显示且填充满,图片可能会拉伸 contain全图显示但不充满,显示原比例 cover显示可能拉伸,也可能裁剪,充满 fitWidth显示可能拉伸,也可能裁剪,宽度充满 fitHeight显示可能拉伸,也可能裁剪,高度充满 scaleDown在目标框内对齐图像(默认情况下,居中),如有必要,缩小图像的比例,以确保图像适合于框内 5.8.2显示本地图片 本地图片的显示方式及步骤如下: 第1步,在VS Code资源管理器中单击新建文件夹图标,新建asset/images文件夹,并将图片拖动到这个文件夹中,如图56所示。 图56新建图片文件夹 第2步,如果是从网上下载的图片,则可能默认为锁定状态。要选中该文件,右击并选择文件属性解除锁定,如图57所示。 第3步,打开 pubspec.yaml文件,加入如图56所输入的表示图片位置的内容(此处为asset/images/)。一定要注意下面的格式,可以采用删除图中注释左边“#”和空格的方式生成下面的代码,如图58所示。 图57解除文件锁定 图58在pubspec.yaml文件中指明图片位置 第4步,在程序中加入如下语句,注意图片的名称和位置一定不要写错,与图58所示及实际的图片位置三者要保持一致。 //第5章5.10 本地资源图片的显示 Container( margin: const EdgeInsets.all(20.0), color: Colors.yellow, width: 200, height: 200, child: Image.asset("asset/images/coast.jpg"),//此处为增加的语句 ), 5.8.3加载图片过程中,显示进度条信息 一般情况下,图片加载是直接显示在屏幕上,前面的过程屏幕可能长时间为空白,尤其在加载网络上的图片时,这样会给用户不好的体验。在加载的过程中,也可以先显示替代的图片或进度信息,这样会给用户带来很好的外观体验。如有动画的效果会有更酷的体验,代码如下: //第5章5.11 加载网络图片时,显示进度条 body: Container( margin: const EdgeInsets.all(20.0), color: Colors.red, width: 200, height: 200, child: FadeInImage.assetNetwork( placeholder: "asset/images/loading.gif", image:" https://tenfei04.cfp.cn/creative/vcg/veer/800water/veer-147317368.jpg"), ), FadeInImage类的作用为在加载目标图像时显示占位符placeholder属性中的图像,然后在加载完成后占位符中的图像在新图像中淡出。placeholder属性为图片显示前的加载图片,一般为进度条图片,image属性为显示的图片地址。进度条图片是本地资源,也要复制到工程相应的位置上。FadeInImage常用属性见表53。 表53FadeInImage常用属性 alignment在图像边界内对齐图像 fadeOutCurve占位符淡出动画的随时间变化曲线 fadeInCurve图像淡入动画的随时间变化曲线 fadeOutDuration占位符加载过程中动画持续时间 fadeInDuration图像加载过程中动画持续时间 fit图像填充容器的方式,如填满、居中等 key控制一个小组件如何替换组件树中的另一个组件 在图像的显示过程中,默认情况下,不同的手机分辨率会使用不同类型的图片。在设备像素比为2.0的屏幕上,屏幕将使用如images/2x/cat.png中所示的图片文件,相应地,在设备像素比为4.0的屏幕上,将使用3.5x文件夹中的图片cat.png。在pubspec.yaml文件中与设备像素比有关的代码如下。我们可以在实际使用过程中,在相应的位置添加不同大小的图片,以适应不同的屏幕。 flutter: assets: - images/cat.png - images/2x/cat.png - images/3.5x/cat.png 5.9Flutter按钮类型 在Flutter中,按钮的类型很多,常用的有以下几种: (1) 可以由ButtonStyle统一定义风格的按钮,如TextButton、OutlinedButton、ElevatedButton。它们在单击时,界面有类似水的波纹动态效果。 (2) 带图标的按钮,如IconButton、BackButton、CloseButton。 (3) 切换按钮ToggleButton。 (4) Material Design中的浮动按钮,如FloatingActionButton。 5.9.1TextButton文本按钮 TextButton文本按钮的表现形式为普通按钮,并且没有边框,带边框的则为OutlinedButton按钮。可以在工具栏、对话框或与其他内容内联时,使用文本按钮。 下面的示例代码为TextButton的实现形式。onPressed为单击事件,如果为null,则此项禁用。其中,child显示按钮文本内容。style属性用于定义按钮的风格,通过TextButton的静态方法styleFrom复制了原来的风格,并实现了字体的大小为20。 //第5章5.12 TextButton文本按钮的使用 TextButton( style: TextButton.styleFrom( textStyle: const TextStyle(fontSize: 20), ), onPressed: () {}, child: const Text('文本按钮'), ), 5.9.2OutlinedButton强调按钮 OutlinedButton和TextButton类似,区别为OutlinedButton按钮是以边框的形式存在,代码如下: //第5章5.13 OutlinedButton强调按钮的使用 OutlinedButton( onPressed: () { print('Received click'); }, child: const Text('有边框的按钮'), ); 5.9.3ElevatedButton有阴影的按钮 ElevatedButton有阴影的按钮的表现形式更为丰富,可以在长列表或其他空间位置使用,但不要在对话框或卡片本身有阴影效果的组件上使用。ElevatedButton在显示时按钮的边界有阴影立体效果,按下时的动态效果更为明显,代码如下: //第5章5.14 ElevatedButton带阴影的按钮的使用 ElevatedButton ( style: ElevatedButton.styleFrom( textStyle: const TextStyle(fontSize: 20), ), onPressed: () {}, child: const Text('点我'), ), TextButton文本按钮、OutlinedButton强调按钮和ElevatedButton有阴影的按钮的外观表现形式如图59所示。 图59ElevatedButton、TextButton、OutlinedButton按钮的显示 5.9.4IconButton图标按钮 图标按钮,顾名思义,带图标的按钮,常见的有后退按钮BackButton、关闭按钮CloseButton等。可以放在任何位置,通常应用于屏幕最上面的导航栏AppBar.actions位置,用于单击完成一些事件。图510为星形图标按钮当单击时的实现效果,默认显示星形图标,当单击时有类似波浪向外扩散的阴影效果。 图510单击图标按钮的水波效果 icon表示按钮图标的类型,color表示按钮的颜色,iconSize表示图标的大小,splashColor表示按下这个按钮时,背景显示的动态颜色。onPressed实现单击事件,代码如下: //第5章5.15IconButton带图标的按钮的使用 IconButton( icon: const Icon(Icons.android),//按钮图标的类型 color: Colors.red,//按钮的颜色为红色 iconSize: 96, //图标的大小为96 splashColor: Colors.blue, //按下这个按钮时,背景显示的动态颜色为蓝色 onPressed: () {},//实现单击事件 ), 5.9.5FloatingActionButton浮动按钮 FloatingActionButton浮动操作按钮组件通常是一个圆形图标按钮,它悬停在屏幕内容之上,以执行应用程序中的相应操作。在Scaffold中使用floatingActionButton属性实现浮动操作按钮的显示效果,使用floatingActionButtonLocation属性设置按钮的位置,使用floatingActionButtonAnimator属性实现按钮位置变化的动画效果。浮动操作按钮组件在屏幕的显示效果如图511箭头所示。 图511FloatingActionButton浮动按钮的显示 实现FloatingActionButton浮动按钮显示的关键代码如下: //第5章5.16 FloatingActionButton浮动按钮的使用 @override Widget build(BuildContext context) { return Scaffold(//定义Scaffold appBar: AppBar( title: Text('FloatingActonButton示例'), ), //floatingActionButtonAnimator: , //实现浮动按钮的动画效果 //(1) floatingActionButton: FloatingActionButton( onPressed: () { //定义单击事件 //单击浮动按钮,以执行一些事件 }, backgroundColor: Colors.blue, //定义背景颜色 child: const Icon(Icons.add), //定义要显示的内容 ), //(2) floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, body: Center( child: FlutterLogo(), ), ); } 上述关键标记代码表示的含义分别如下: (1) 通过FloatingActionButton属性实现显示浮动按钮的功能。 (2) 通过FloatingActionButtonLocation类设置浮动按钮的位置,默认的常量值为endFloat,表示位置在右下角的位置。其他常量可以控制浮动按钮在其他相应的位置,如centerDocked表示浮动按钮在底部选项卡的中间位置,startTop表示在顶部左边的位置。