flutter 知识点
前沿
熟悉了下flutter ,然后把一些知识点梳理下,便于记忆:
其实很多布局样式和CSS差异并不大,我的理解 flutter的写法就是HTML 写在 标签上的style,也就是内敛样式,这样好理解,你也可以有不同的看法,
完成了之前没有完成的flutter demo
哈哈哈,OK
常用的布局和用到的组件:
其他可以去官网上去查阅
Material 风格
下面我们使用 Material
风格来优化Hello World。在上面的代码中,我们已经 import 引入了 Material
库,Material是Google提供的一个设计语言和UI组件库
import 'package:flutter/material.dart'; void main() { runApp( MaterialApp( home: Scaffold( appBar: AppBar( title: Text("第一个Flutter程序") ), body: Text("Hello World") ) ) ); }
MaterialApp 是Flutter中的一个顶级组件,它包装了整个应用程序,并提供了一些应用程序级别的功能,所以它是应用程序的根组件。在创建 MaterialApp 的时候,传递了一个 Scaffold 作为显示的主页,Scaffold 是一个布局组件,相当于一个页面。
Scaffold 的body属性表示是页面显示的body部分,Scaffold还可以包含appBar、tabBar等部分。
所以上面的操作就是使应用更加层次化,创建了一个应用的 Widget,在应用的 Widget 中又创建了一个页面的 Widget,在页面中添加了一个文本的 Widget
StatelessWidget
上面的 Hello World 感觉看上去代码很乱,各个 Widget 嵌套,都写在 main
函数里面结构很不清晰,现在还只是个 Hello World,如果是真正的项目,页面组件一多,那还了得。
所以我们需要对代码进行封装,创建我们自己的 Widget,将各种组件封装到我们的 Widget中,实现结构的清晰划分。
在 Flutter 中,我们可以创建两种 Widget,StatelessWidget
和 StatefulWidget
,什么区别呢?
- StatelessWidget: 没有状态的Widget,也就是没有数据变化的 Widget,通常这种Widget仅仅是做一些展示工作而已;
- StatefulWidget: 需要保存状态,也就是会有数据变化的Widget,例如 demo 程序中有计数的变化
Container
是 Flutter 中的一个基础布局组件,用于包裹和布局其他组件,并且可以添加装饰、边框、阴影等样式效果。它是一个非常灵活的组件,常用于创建各种样式丰富的 UI 元素。
下面我们通过 Container
实现下面的效果,下面居中的部分就是一个 Container
。
SizedBox
是 Flutter 中的一个布局组件,它可以用来创建一个固定尺寸的空间。SizedBox
可以在水平和垂直方向上设置固定的宽度和高度,用于限制子组件的大小或创建间距。
例如 Text
组件本身是不支持直接设置宽度和高度的,如果想要控制 Text
组件的宽度和高度,可以将其放在SizedBox
或 Container
里,然后设置SizedBox
或 Container
的宽高
如果只是设置尺寸,不涉及额外的装饰和样式,
优先使用 SizedBox
因为 Container
会增加一些性能开销。如果要使用样式,那么只能使用 Container
。
Image
组件是 Flutter 中用于显示图像的部件,它可以显示网络图像、本地资源图像或者内存中的图像。
Image.network( "http://.com/open-assets/img/telangpu2.jpg", // 设置背景颜色 fit: BoxFit.cover, // 设置图片显示的适配模式 )
加载本地图片
在lib 的同级别 下配置地址倒入本地图片
然后需要在 pubspec.yaml
文件中配置资源文件夹的路径。
而且必须要对其的
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// App根Widget class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: DemoPage(), ); } } /// 页面 class DemoPage extends StatelessWidget { const DemoPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Demo'), ), body: Center( child: Container( width: 300, height: 300, decoration: const BoxDecoration( color: Colors.lightBlue, // 设置背景颜色 ), // --------------------- 下面是加载本地图片 ----------------------------- child: Image.asset( "assets/images/telangpu.jpg", fit: BoxFit.cover, // 设置图片适配模式 ), ), )); } }
Container设置圆形图片
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// App根Widget class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: DemoPage(), ); } } /// 页面 class DemoPage extends StatelessWidget { const DemoPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Demo'), ), body: Center( child: Container( width: 200, height: 200, // --------------------- 通过BoxDecoration设置圆角 --------------------- decoration: BoxDecoration( color: Colors.lightBlue, // 设置背景颜色 borderRadius: BorderRadius.circular(100), image: const DecorationImage( image: NetworkImage( "http:/om/open-assets/img/telangpu.jpg" ), fit: BoxFit.cover ) ), ), )); } }
自定义布局:
我的理解就是那种CSS 中的flex 布局
水平布局组件Row
Row
是一种水平排列子组件的布局组件。它将其子组件按照水平方向依次排列,可以用来创建水平的UI布局,比如按钮、文本、图标等。
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// App根Widget class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: DemoPage(), ); } } /// 页面 class DemoPage extends StatelessWidget { const DemoPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Demo'), ), body: Row( // ---------------------在Row中添加三个按钮,按钮是自定义的组件------------------------ children: const [ IconButton(icon: Icons.shop, color: Colors.lightBlue), IconButton(icon: Icons.house, color: Colors.lightGreen), IconButton(icon: Icons.fire_truck, color: Colors.red), ], ) ); } } // 自定义一个按钮组件 class IconButton extends StatelessWidget { final Color color; final double size; final IconData icon; // 自定义按钮组件,可以传递按钮的颜色和按钮上的图标,以及图标的尺寸 const IconButton({super.key, required this.icon, this.color = Colors.blue, this.size = 32.0}); @override Widget build(BuildContext context) { return Container( height: 100.0, width: 100.0, color: color, child: Center( child: Icon(icon, size: size, color: Colors.white) ), ); } }
垂直布局组件Column
Column
是一种垂直排列子组件的布局组件。它将其子组件按照垂直方向依次排列。
Column
组件和 Row
组件是非常相似的,使用方法基本一样。只是排列方向不一样,Row
组件的主轴方向是水平的,Column
的主轴方向是垂直的。
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// App根Widget class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: DemoPage(), ); } } /// 页面 class DemoPage extends StatelessWidget { const DemoPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Demo'), ), body: Container( height: 400, width: double.infinity, color: Colors.yellow, child: Column( // ---------------------在Column中添加三个按钮,按钮是自定义的组件------------------------ mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: const [ IconButton(icon: Icons.shop, color: Colors.lightBlue), IconButton(icon: Icons.house, color: Colors.lightGreen), IconButton(icon: Icons.fire_truck, color: Colors.red), ], ), ) ); } } // 自定义一个按钮组件 class IconButton extends StatelessWidget { final Color color; final double size; final IconData icon; const IconButton({super.key, required this.icon, this.color = Colors.blue, this.size = 32.0}); @override Widget build(BuildContext context) { return Container( height: 100.0, width: 100.0, color: color, child: Center( child: Icon(icon, size: size, color: Colors.white) ), ); } }
弹性布局组件Flex和Expanded
Flex
组件可以沿着水平或垂直方向排列子组件,可以通过 direction
属性指定它是水平方向还是垂直方向。
如果指定它是水平方向的,那么它和 Row
是一样的,如果指定它是垂直方向的,
那么它和 Column
是一样的,因为 Row 和 Column 都继承自 Flex
,参数基本相同,所以能使用 Flex
的地方基本上都可以使用 Row
或 Column
来实现 。 Flex
、Row
、Column
可以结合 Expanded
组件来实现弹性布局 。
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// App根Widget class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: DemoPage(), ); } } /// 页面 class DemoPage extends StatelessWidget { const DemoPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Demo'), ), // --------------------Flex 组件---------------------- body: Flex( direction: Axis.horizontal, children: const [ // -------------------- 通过Expanded组件指定占用的比例 ---------------------- Expanded( flex: 2, // 占用2份的空间,设置flex后,child的元素的宽度是失效的。 child: IconButton(icon: Icons.shop, color: Colors.lightBlue)), Expanded( flex: 1, // 占用1份的空间 child: IconButton(icon: Icons.fire_truck, color: Colors.red)) ], ), ); } } // 自定义一个按钮组件 class IconButton extends StatelessWidget { final Color color; final double size; final IconData icon; const IconButton( {super.key, required this.icon, this.color = Colors.blue, this.size = 32.0}); @override Widget build(BuildContext context) { return Container( height: 100.0, width: 100.0, color: color, child: Center(child: Icon(icon, size: size, color: Colors.white)), ); } }
首先设置 Flex
元素的方向为水平方向,然后使用 Flex
组件包裹 Expanded
组件,通过 Expanded
设置占用的比例。
需要注意 Expanded
组件中的子组件的宽度是无效的,是由 Expanded
来控制的。
上面的 Flex
可以换成 Row
,效果是一样的。
我们还可以实现一种效果,就是固定一部分组件的尺寸不变,剩余的组件随着屏幕的尺寸进行自适应
右侧的按钮尺寸是固定的,左侧按钮随着屏幕的尺寸进行自适应。
只需要将自适应的组将使用Expanded组件包裹即可。
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// App根Widget class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: DemoPage(), ); } } /// 页面 class DemoPage extends StatelessWidget { const DemoPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Demo'), ), // --------------------Flex 组件---------------------- body: Flex( direction: Axis.horizontal, children: const [ // -------------------将自适应的组件使用Expanded组件包裹---------------------- Expanded( child: IconButton(icon: Icons.shop, color: Colors.lightBlue) ), IconButton(icon: Icons.fire_truck, color: Colors.red) ], ), ); } } // 自定义一个按钮组件 class IconButton extends StatelessWidget { final Color color; final double size; final IconData icon; const IconButton( {super.key, required this.icon, this.color = Colors.blue, this.size = 32.0}); @override Widget build(BuildContext context) { return Container( height: 100.0, width: 100.0, color: color, child: Center(child: Icon(icon, size: size, color: Colors.white)), ); } }
网格组件GridView
GridView
是一个用于展示网格布局的组件,它可以在水平和垂直方向上排列子组件,通常用于展示一系列相似的数据项,
比如图片、卡片、图标等。GridView
可以根据指定的行数、列数或交叉轴上的子组件的最大宽度来排列子组件。
GridView创建网格列表主要有下面三种方式
-
通过GridView.count 实现网格布局
-
通过GridView.extent 实现网格布局
-
通过GridView.builder实现动态网格布局
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// App根Widget class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: DemoPage(), ); } } /// 页面 class DemoPage extends StatelessWidget { const DemoPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Demo'), ), // ------------------使用GridView.count创建GridView-------------------- body: GridView.count( crossAxisCount: 4, // 设置每一行元素的个数 children: const [ IconButton(icon: Icons.shop, color: Colors.lightBlue), IconButton(icon: Icons.house, color: Colors.lightGreen), IconButton(icon: Icons.fire_truck, color: Colors.red), IconButton(icon: Icons.settings, color: Colors.orange), IconButton(icon: Icons.logout, color: Colors.cyan), IconButton(icon: Icons.star, color: Colors.yellow), IconButton(icon: Icons.delete, color: Colors.lightGreen), IconButton(icon: Icons.rule, color: Colors.lightBlue), IconButton(icon: Icons.timeline, color: Colors.lightGreen), IconButton(icon: Icons.access_alarm, color: Colors.lightBlue), ], )); } } // 自定义一个按钮组件 class IconButton extends StatelessWidget { final Color color; final double size; final IconData icon; const IconButton( {super.key, required this.icon, this.color = Colors.blue, this.size = 32.0}); @override Widget build(BuildContext context) { return Container( height: 100.0, width: 100.0, color: color, child: Center(child: Icon(icon, size: size, color: Colors.white)), ); } }
容器组件Wrap
Wrap
是一个用于流式布局的组件,它可以将一系列子组件在水平或垂直方向上自动换行排列,适用于需要根据可用空间动态调整布局的情况。Wrap
可以在超出容器边界时自动将子组件放置到下一行或下一列,以适应不同尺寸的屏幕或容器。
我们之前学习了 Row
和 Column
组件,但是 Row
组件中的元素装不下不会换到下一行,但是 Wrap
组件可以。
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// App根Widget class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: DemoPage(), ); } } /// 页面 class DemoPage extends StatelessWidget { const DemoPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Demo'), ), body: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text("热门搜索"), const Divider(color: Colors.grey), // --------------- Wrap的使用 ------------------- Wrap( spacing: 10, // 子组件之间的间距 runSpacing: 0, // 行之间的间距 children: [ OutlinedButton(onPressed: () {}, child: const Text("曹操", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("刘玄德", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("关羽", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("张飞", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("诸葛孔明", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("夏侯惇", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("司马老贼", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("孙权", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("孙策", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("孙尚香", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("典韦", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("赵云", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("吕布", style: TextStyle(color: Colors.black54))), OutlinedButton(onPressed: () {}, child: const Text("马超", style: TextStyle(color: Colors.black54))), ], ), ]), ); } }
Wrap( alignment: WrapAlignment.center, // 控制子组件的对齐方式 spacing: 10, // 子组件之间的间距 runSpacing: 0, // 行之间的间距 children: [ // ... ], )
还使用了 spacing
属性来控制各个元素之间的间距,使用 runSpacing
属性控制行之间的间距。
还可以使用 alignment
子组件的对齐方式,例如设置居中对齐:
标题栏AppBar
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// App根Widget class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: MainPage(), ); } } class MainPage extends StatefulWidget { const MainPage({Key? key}) : super(key: key); @override State<MainPage> createState() => _MainPageState(); } class _MainPageState extends State<MainPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Drawer Demo'), // 标题 centerTitle: true, // 标题居中 backgroundColor:Colors.red, // 背景颜色 leading:IconButton( // 左侧按钮 icon: const Icon(Icons.west), onPressed: (){ print('点击左侧按钮'); }), actions: [ // 右侧按钮,可以有多个 IconButton( icon: const Icon(Icons.search), onPressed: () { print('点击了搜索按钮'); }), IconButton( icon: const Icon(Icons.more_horiz), onPressed: (){ print('点击了更多按钮'); }) ], ), body: const Center(child: Text("Doubi")), ); } }
路由
什么是路由?
其实就是页面跳转。
Flutter 中的路由系统基于导航栈来管理不同页面的顺序和状态。
什么是导航栈?
导航栈(Navigation Stack)是在应用程序中用于管理页面或路由的一种数据结构。它类似于堆栈(Stack)数据结构,遵循先进后出(LIFO)的原则。
在 Flutter 中,导航栈用于存储打开的页面或路由,每当你打开一个新的页面或路由时,它会被推入(push)到导航栈的顶部。因为系统当前显示的页面就是当前在导航栈顶的的页面。当你执行返回操作时,最顶部的页面会被弹出(pop)出栈,回到上一个页面。
在 Flutter 中,Navigator
类负责管理导航栈,并提供了管理堆栈的方法。你可以使用 Navigator
的方法来推入新页面、弹出当前页面、查看当前栈中的页面等。导航栈在应用程序中起着重要的作用,它允许你实现页面之间的流畅切换、返回操作以及状态管理。
以下是 Navigator
类提供的一些常用的方法,了解一下,后面逐一介绍一下如何使用。
Navigator.push(context, MaterialPageRoute(builder: (context) => NewPage()));
- 将当前页面弹出导航栈,返回上一个页面。
Navigator.pop(context);
- 替换当前页面为新页面,从而实现页面切换并且移除当前页面。
Navigator.pushReplacementNamed(context, "/message");
- 将导航栈中的页面依次弹出,直到某个指定页面。
Navigator.popUntil(context, ModalRoute.withName('/home'));
- 检查是否可以执行返回操作(即是否还有页面可以弹出)。
Navigator.canPop(context)
封装路由
我们在 main.dart
中将路由配置定位为一个 Map
对象,其实这样代码混乱不利于管理,我们可以将路由配置和onGenerateRoute,抽离到一个单独的文件。
例如,新建一个 router.dart
,代码如下:
首先引入路由的各个页面文件,然后定义路由配置和 onGenerateRoute
函数
import 'package:flutter/material.dart'; import '../pages/home.dart'; import '../pages/search.dart'; import '../pages/message.dart'; // 1、定义Map类型的routes final Map routes = { '/': (context) => const HomePage(), '/search': (context, {arguments}) => SearchPage(arguments: arguments), '/message': (context) => const MessagePage(), }; var onGenerateRoute = (RouteSettings settings) { // 统一处理 final String? name = settings.name; final Function? pageContentBuilder = routes[name]; if (pageContentBuilder != null) { if (settings.arguments != null) { final Route route = MaterialPageRoute( builder: (context) => pageContentBuilder(context, arguments: settings.arguments)); return route; } else { final Route route = MaterialPageRoute( builder: (context) => pageContentBuilder(context)); return route; } } return null; };
重新修改 main.dart
import 'package:flutter/material.dart'; // 引入路由配置 import './router/router.dart'; void main() => runApp(const MyApp()); /// App根Widget class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, // 初始化路由,初始化进入的页面 initialRoute: "/", // onGenerateRoute处理 onGenerateRoute: onGenerateRoute, ); } }
返回根路由
我们可以使用 Navigator.pushNamedAndRemoveUntil()
方法,它用于实现页面导航并清除导航栈中的页面,以达到指定条件的效果。具体而言,它的作用如下:
- 首先,它会压入(导航到)一个新的命名路由页面到导航栈。
- 压入新页面之前,它会从导航栈中删除所有指定条件之前的页面,以确保只剩下符合条件的页面。
// ----- 返回首页 Navigator.pushNamedAndRemoveUntil( context, '/', // 跳转到的页面名称 (route) => false, // 始终返回false,表示一直删除,删除所有页面 );
上面的代码会跳转到 "/"
首页,第三个参数是一个回调函数,用于确定是否删除页面。当返回 true
时,删除停止,页面保留在导航栈中。上面一直返回false,则会清空导航栈。
再举一个例子:
从 /
跳转到 /a
,从 /a
跳转到 /b
,从 /b
跳转到 /d
,在 /d
中如何返回 /a
,在 /a
通过 Navigator.pop
返回到 /
,那么在 /d
中该如何返回到 /a
呢?
Navigator.pushNamedAndRemoveUntil( context, '/a', // 返回到/a页面 ModalRoute.withName('/'), // 删除中间的页面,直到根页面 );
页面切换风格
我们之前使用的风格是 Material
库的风格,Material
组件库中路由的切换是通过 MaterialPageRoute
组件实现的,MaterialPageRoute
组件会根据 Android
和 iOS
上显示不同的风格。例如在切换页面的时候,在 Android
上是上下滑动切换,iOS 是左右滑动切换。
如果想在 Android
上实现和 iOS
上一样的切换效果,可以使用 Cupertino
库,其中 CupertinoPageRoute
是 Cupertino
组件库提供的iOS
风格的路由切换组件。
所以修改之前的路由配置即可,也是很简单的,需要修改两个地方:
- 引入
cupertino.dart
,删除material.dart
; - 将
MaterialPageRoute
替换为CupertinoPageRoute
;
// 1.配置iOS分隔的路由,删掉material.dart,引入cupertino.dart import 'package:flutter/cupertino.dart'; // import 'package:flutter/material.dart'; import '../pages/home.dart'; import '../pages/search.dart'; import '../pages/message.dart'; // 1、定义Map类型的routes final Map routes = { '/': (context) => const HomePage(), '/search': (context, {arguments}) => SearchPage(arguments: arguments), '/message': (context) => const MessagePage(), }; var onGenerateRoute = (RouteSettings settings) { // 统一处理 final String? name = settings.name; final Function? pageContentBuilder = routes[name]; if (pageContentBuilder != null) { if (settings.arguments != null) { // 2. 将MaterialPageRoute替换为 final Route route = CupertinoPageRoute( builder: (context) => pageContentBuilder(context, arguments: settings.arguments)); return route; } else { final Route route = CupertinoPageRoute( builder: (context) => pageContentBuilder(context)); return route; } } return null; };
引入资源包
在pubspec.yaml 文件夹目录下引入
你需要的包
然后等vs code 安装完毕以后
这样就OK了