Loading... ## 一.基础组件 ### 1.MaterialApp 常见属性: `title` `theme` `home` title: 用来展示窗口的标题内容(可以不设置) theme: 用来设置整个应用的主题 home: 用来展示窗口的主体内容 ```dart void main(List<String> args) { runApp( MaterialApp( title: "Flutter组件初体验", theme: ThemeData(scaffoldBackgroundColor: Colors.greenAccent), home: Scaffold(), ), ); } ``` ### 2.Scaffold 用于构建MaterialDesign风格页面的核心布局组件,提供标准、灵活配置的页面骨架 | 属性 | 主要作用说明 | | --- | --- | | appBar | 页面顶部的应用栏,通常用于显示标题、导航按钮和操作菜单 | | body | 页面的主要内容区域,可以放置任何其他组件,是页面的核心 | | bottomNavigationBar | 底部导航栏,方便用户在不同核心功能页面间切换 | | backgroungColor | 设置整个Scaffold的背景颜色 | | floatingActionButton | 悬浮操作按钮,常用于触发页面的主要动作 | | ... | 其他 | ```dart void main(List<String> args) { runApp( MaterialApp( title: "Flutter组件初体验", theme: ThemeData(scaffoldBackgroundColor: Colors.greenAccent), home: Scaffold( appBar: AppBar( title: Text("头部区域"), centerTitle: true ), body: Container( child: Center( child: Text("中间区域") ) ), bottomNavigationBar: Container( height: 80, child: Center( child: Text("底部区域") ), ), ), ), ); } ``` ## 二.自定义组件 定义:根据自己特定的需求创建自己的widget 分类:Flutter分为无状态组件和有状态组件 | 特性 | StatelessWidget(无状态) | StatefulWidget(有状态) | | --- | --- | --- | | 核心特征 | 一旦创建,内部状态不可变 | 持有可在其生命周期内改变的状态 | | 使用场景 | 静态内容展示,外观仅由配置参数决定 | 交互式组件,如计数器、可切换开关、表单输入框 | | 生命周期 | 相对简单,主要是构建(`build`) | 更为复杂,包含状态创建、更新和销毁 | | 代码结构 | 单个类 | 两个关联的类:Widget 本身和单独的 State类 | ### 1.无状态组件 定义:创建一个新的类,继承StatelessWidget类并实现build方法 要点:build返回一个widget 场景:纯展示型组件,没有用户交互操作 需求:把之前案例的骨架换成无状态组件 ```dart void main(List<String> args) { runApp(MainPage()); } // 无状态组件 自定义组件中的一种 class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return Container(); } } ``` ### 2.有状态组件 `定义`:有状态组件是构建动态交互界面的核心,能够管理变化的内部状态,当状态改变时,组件会更新显示内容 实现1:创建两个类,第一个类继承Statefulwidget类,主要接收和定义最终参数,核心作用是创建State对象 实现2:第二个类继承State<第一个类名>,负责管理所有可变的数据和业务逻辑,并实现build构建方法 `要点`:build方法需要返回一个Widget `需求`:将之前骨架组件换成有状态组件 ```dart // 有状态组件 第一个类 对外 class MainPage extends StatefulWidget { @override State<StatefulWidget> createState() { // return 第二个类 return _MainPageState(); } } // 有状态组件 第二个类 内部类 负责管理数据 处理业务逻辑 并且渲染视图 class _MainPageState extends State<MainPage> { @override Widget build(Object context) { return Container(); } } ``` ## 三.生命周期 ### 1.无状态组件生命周期 无状态组件-唯一阶段 `build`方法 当组件被创建或父组件状态变化导致其需要重新构建时,build方法会被调用 ```dart class MainPage extends StatelessWidget { const MainPage({super.key}); @override Widget build(BuildContext context) { print("无状态组件构建执行..."); return Container(); } } ``` ### 2.有状态组件生命周期 有状态组件-三个阶段 - 1.`创建阶段` Statefulwidget被创建 -> createState -> State对象创建 -> initState -> didChangeDependencies -> build - 2.`更新阶段` 父组件重建-配置变更 -> didUpdateWidget -> build - 3.`销毁阶段` 组件被移除 -> deactivate -> dispose | 生命周期阶段 | 函数名 | 调用时机与核心任务 | | --- | --- | --- | | 创建阶段 | createState() | Widget 初始化时调用,创建 State 对象 | | 创建阶段 | initState() | State 对象插入 Widget 树后立刻执行,仅执行一次 | | 创建阶段 | didChangeDependencies() | initState 后立刻执行,当依赖的 InheritedWidget 更新时调用,可能多次 | | 构建与更新阶段 | build() | 构建 UI 的方法,在初始化或更新后多次调用 | | 构建与更新阶段 | didUpdateWidget() | 父组件传入新配置时调用,用于比较新旧配置 | | 销毁阶段 | deactivate() | 当 State 对象从树中暂时移除时调用 | | 销毁阶段 | dispose() | 当 State 对象被永久移除时调用,释放资源,仅执行一次 | ```dart class MainPage extends StatefulWidget { const MainPage({super.key}); // 创建阶段 @override State<MainPage> createState(){ print("1.createState阶段执行"); return _MainPageState(); } } class _MainPageState extends State<MainPage> { @override void initState() { print("2.initState阶段执行"); super.initState(); } @override void didChangeDependencies() { print("3.didChangeDependencies阶段执行"); super.didChangeDependencies(); } // 更新阶段 @override Widget build(BuildContext context) { print("4.build阶段执行"); return Container(); } @override void didUpdateWidget(covariant MainPage oldWidget) { print("5.didUpdateWidget阶段执行"); super.didUpdateWidget(oldWidget); } // 销毁阶段 @override void deactivate() { print("6.deactivate阶段执行"); super.deactivate(); } @override void dispose() { print("7.dispose阶段执行"); super.dispose(); } } ``` ### 3.生命周期总结 * `无状态组件`(StatelessWidget) * 仅执行 build 方法 * `有状态组件`(StatefulWidget) - 1.创建阶段: createState() -> initState -> didChangeDependencies -> build - 2.更新阶段: didUpdateWidget -> build - 3.销毁阶段: deactivate -> dispose - 仅执行一次的函数: createState、initState、dispose - InheritedWidget: 专门用于在Widget树中自顶向下高效共享数据, 顶层组件提供数据, 子孙节点直接获取。 ## 四.事件 ### 1.点击事件GestureDetector - **事件**:用户与应用程序交互时触发的各种动作,比如触摸屏幕、滑动、点击等 - **点击事件**:当点击某个元素触发的动作 - **常规方案**:`GestureDetector` 是 Flutter 中最常用、功能最丰富的手势检测组件。 ```dart // 有状态组件 MaterialApp( home: Scaffold( body: Container( child: Center( child: GestureDetector( // 点击事件 onTap: () { print("点击了该区域"); }, // 双击事件 onDoubleTap: () { print("双击了该区域"); }, child: Text("中间区域"), ) ) ), ) ); ``` ### 2.组件点击事件 Flutter 提供了多种方式为组件添加点击交互。 | 组件类别 | 核心组件 | 主要特点 / 使用场景 | |---|---|---| | 专用按钮组件 | ElevatedButton、TextButton、OutlineButton、FloatingActionButton | 内置点击动画和样式,通过 `onPressed` 参数处理点击逻辑 | | 视觉反馈组件 | InkWell | 提供点击事件(`onTap`),有 Material Design 风格的水纹扩散效果 | | 其他交互组件 | IconButton、Switch、Checkbox | 具有特定功能的交互式控件,点击事件(`onPressed`) | ```dart // 有状态组件 MaterialApp( home: Scaffold( body: Container( child: Center( child: TextButton(onPressed:() { print("点击了按钮"); }, child: Text("按钮")), ) ), ) ); ``` ## 五.状态更新-setState - **场景**:计数器,点击 `+` 进行数量 `+1`,点击 `-` 进行数量 `-1`,UI 视图需要进行相应更新 - **语法**:数据的变化要更新 UI 视图,需要执行 `setState` 方法,`setState` 方法会造成 `build` 的重新执行。 实现计数器示例: ```dart // 有状态组件 class _MainPageState extends State<MainPage> { int count = 0; // 状态变量 @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Center( child: Row( children: [ TextButton(onPressed: () { setState(() { count--; }); }, child: Text("减")), Text("$count"), TextButton(onPressed: () { count++; setState(() { // 只要执行了setState()方法,就会重新执行build方法 }); }, child: Text("加")), ], ), ), ), ); } } ``` ## 六.Flutter组件 ### 布局组件-介绍 Flutter 提供了丰富强大的布局组件来构建各种用户界面。下面这个表格汇总了最核心的几类布局组件。 | 组件类别 | 核心组件 | 主要特点 / 使用场景 | |---|---|---| | 基础容器 | Container、Center、Align、Padding | 提供装饰、对齐、边距等基础样式和布局控制,是使用频率极高的组件 | | 线性布局 | Row、Column | 在水平或垂直方向线性排列子组件,是构建界面的基础 | | 弹性布局 | Flex、Expanded、Flexible | 按照比例分配剩余空间,实现自适应布局,常与 `Row` 和 `Column` 配合使用 | | 层叠布局 | Stack、Positioned | 让子组件重叠堆叠,用于实现如图片上叠加文字、悬浮按钮等效果 | | 流式布局 | Wrap、Flow | 当主轴空间不足时自动换行或换列,常用于标签、滤镜等动态宽高内容的排列 | | 滚动布局 | ListView、GridView | 提供可滚动的列表或网格视图,高效展示大量数据 | ### 1.基础容器 #### Container-容器组件 - **定义**:Container 是功能丰富的布局组件,是一个多功能组合容器 - **尺寸控制**:可通过多种方式定义大小,有明确优先级规则 - **优先级**:明确宽高 > constraints 约束 > 父组件约束 > 自适应组件大小 - **装饰系统**:通过 `decoration` 属性实现视觉效果,但和 `color` 属性互斥 - **布局控制**:提供内外边距和对齐方式 - **可选变化**:支持绘制时进行矩阵变换,如旋转、倾斜、平移等 Container 常用属性 | 属性类别 | 关键属性 | 作用说明 | |---|---|---| | 布局定位 | alignment | 控制其 child(子组件)在容器内部的对齐方式。例如:`Alignment.center`(居中)、`Alignment.topLeft`(左上角) | | 尺寸控制 | width / height / constraints | 设置容器的宽度和高度 / 为容器设置更复杂的尺寸约束(如最小/最大宽高) | | 间距留白 | padding / margin | 按照比例分配剩余空间,实现自适应布局,常与 Row 和 Column 配合使用 | | 装饰效果 | color / decoration | 为容器设置一个简单的背景颜色 / 为容器设置复杂的背景装饰 | | 变换效果 | transform | 对容器及其内容进行矩阵变换 | | 子组件 | child | 容器内包含的唯一直接子组件 | 代码示例: ```dart MaterialApp( home: Scaffold( body: Container( transform: Matrix4.rotationZ(0.05), // 旋转 alignment: Alignment.center, // 居中 width: 200, height: 200, margin: EdgeInsets.all(20), // 外边距 // color: Colors.blueAccent, // 简单的背景与颜色 decoration: BoxDecoration( color: Colors.blueAccent, borderRadius: BorderRadius.circular(15), // 圆角 border: Border.all(width: 5, color: Colors.amber), ), child: Center( child: Text( "Hello Container", style: TextStyle(fontSize: 20, color: Colors.white), ), ), ), ), ); ``` #### Center-居中组件 - **作用**:将其子组件在父容器空间内进行水平和垂直方向上的居中排列 - **应用场景**:页面内容整体居中,如将一个登录表单或一个加载中的提示图标在页面正中显示 - **注意事项**: - `Center` 自身不能设置宽高,其最终大小取决于父组件传递的约束 - `Center` 会向父组件申请尽可能大的空间 - **实现固定宽高且居中的组件**:用 `Center` 包裹一个具有固定宽高的子组件(如 `Container` / `SizedBox`) 代码示例: ```dart MaterialApp( home: Scaffold( appBar: AppBar(title: Text("Center代码示例"), centerTitle: true), body: Center( child: Container( width: 100, height: 100, color: Colors.cyan, child: Center(child: Text("居中内容")), ), ), ), ); ``` #### Align-对齐组件 - **作用**:精确控制其子组件在父容器空间内的对齐位置 - **alignment**(对齐方式):子组件在父容器内的对齐方式 - **widthFactor**(宽度因子):`Align` 的宽度将是子组件宽度乘以该因子 - **heightFactor**(高度因子):`Align` 的高度将是子组件高度乘以该因子 代码示例: ```dart MaterialApp( home: Scaffold( appBar: AppBar(title: Text("Align代码示例"), centerTitle: true), body: Container( color: Colors.blue, child: Align( alignment: Alignment.center, // 对其方式 // widthFactor: 3, // Align的宽度是子图标宽度的3倍 // heightFactor: 3, // Align的高度是子图标高度的3倍 child: Icon(Icons.star, size: 150, color: Colors.yellow), ), ), ), ); ``` - **与 Center 的区别**: `Center` 是 `Align` 的一个特例,继承自 `Align`,相当于将 `alignment` 属性固定为 `Alignment.center` 的 `Align`。 - **使用场景**: 当需要将一个组件放置在父容器的特定角落时,`Align` 是理想选择。 - **动态尺寸**: 通过 `widthFactor` 和 `heightFactor`,可以创建出与子组件大小成比例的容器,在动态布局中很有用。 #### Padding-内边距组件 - **作用**:为其子组件添加内边距 | 属性 | 类型 | 作用说明 | |------|------|----------| | padding | EdgeInsetsGeometry | **必需**。定义内边距的大小和方向,通常使用 `EdgeInsets` 类来设置 | | child | Widget | 需要被添加内边距的子组件 | - **四个方向设置相同间距**:使用 `EdgeInsets.all` 进行四个方向统一设置 - **四个方向设置不同间距**:使用 `EdgeInsets.only` 进行四个方向分别设置 - **设置对称方向的内边距**: 使用 `EdgeInsets.symmetric` 进行设置 代码示例: ```dart MaterialApp( home: Scaffold( appBar: AppBar(title: Text("Padding代码示例"), centerTitle: true), body: Container( // padding: EdgeInsets.all(20), // 属性 decoration: BoxDecoration(color: Colors.amber), child: Padding( // 容器 // padding: EdgeInsets.all(20), // 上下左右的四个内边距 // padding: EdgeInsets.only(left: 20, right: 20, top: 20, bottom: 20), // 单独设置上下左右 padding: EdgeInsets.symmetric(vertical: 20, horizontal: 20), // 对称方向 child: Container(color: Colors.blue), ), ), ), ); ``` Padding 总结 - **特点**:功能单一而纯粹,就是添加内边距。如果需求仅是为组件添加间距,那么直接使用 `Padding` 组件 - **区别**:`Container` 也有 `padding` 属性;单一需求用 `Padding` 组件,复杂样式用 `Container` ### 2.线性布局 #### Column-垂直布局 - **作用**:用于垂直排列其子组件的核心布局容器。 | 属性 | 类型 | 作用说明 | |----------------------|----------------|--------------------------------------------------------------------------| | mainAxisAlignment | MainAxisAlignment | 控制子组件在主轴(垂直方向)上的排列方式,如顶部对齐、居中或均匀分布。 | | crossAxisAlignment | CrossAxisAlignment | 控制子组件在交叉轴(水平方向)上的对齐方式,如左对齐、右对齐或拉伸填满。 | | mainAxisSize | MainAxisSize | 决定 Column 本身在垂直方向上的尺寸策略:是占满所有可用空间(max),还是仅仅包裹子组件内容(min)。 | | children | List | 需要被垂直排列的子组件列表。 | <img src="https://picture.qfxaile.top/i/0/2025/12/23/ikr6jt-0.png" alt="image-20251223112324494" style="zoom:50%;" style=""> 代码示例: ```dart MaterialApp( home: Scaffold( appBar: AppBar(title: Text("Column代码示例"), centerTitle: true), body: Container( width: double.infinity, // 宽度尽可能占满父组件 decoration: BoxDecoration(color: Colors.amber), child: Column( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // 两头对齐 // mainAxisAlignment: MainAxisAlignment.spaceAround, // 环绕模式 // mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 环绕模式 // mainAxisAlignment: MainAxisAlignment.start, // 从头排列 // mainAxisAlignment: MainAxisAlignment.end, // 从尾排列 mainAxisAlignment: MainAxisAlignment.center, // 居中排列 // crossAxisAlignment: CrossAxisAlignment.start, // 交叉轴start // crossAxisAlignment: CrossAxisAlignment.center, // 交叉轴居中 // crossAxisAlignment: CrossAxisAlignment.end, // 交叉轴end crossAxisAlignment: CrossAxisAlignment.stretch, // 填充交叉轴 children: [ Container(width: 100, height: 100, color: Colors.blue), Container( margin: EdgeInsets.only(top: 10, bottom: 10), width: 100, height: 100, color: Colors.blue, ), Container(width: 100, height: 100, color: Colors.blue), ], ), ), ), ); ``` 适用场景: 几乎在所有需要垂直排列元素的界面中都能看到它的身影。 - **表单**:如登录页面的用户名输入框、密码输入框和登录按钮的垂直排列。 - **设置列表**:如设置页面中多个选项项的垂直堆叠。 - **卡片布局**:如新闻流中多个新闻卡片的垂直排列。 - **图文混排**:如商品详情页的图片、标题、描述和价格等信息从上到下的展示。 注意事项: 1. Column 本身不支持滚动;若内容超出,需用 `ListView` 或 `SingleChildScrollView` 包裹。 2. 明确尺寸约束:父组件的大小直接影响 Column 的最终尺寸及子组件的布局行为。 3. 避免过度嵌套:过深的嵌套会影响性能并增加代码维护难度。 #### Row-水平布局 **作用**:用于水平排列其子组件的核心布局容器。 | 属性 | 类型 | 作用说明 | |----------------------|---------------------|--------------------------------------------------------------------------| | mainAxisAlignment | MainAxisAlignment | 控制子组件在主轴(水平方向)上的排列方式,如起始对齐、居中或均匀分布。 | | crossAxisAlignment | CrossAxisAlignment | 控制子组件在交叉轴(垂直方向)上的对齐方式,如顶部对齐、底部对齐或拉伸填满。 | | mainAxisSize | MainAxisSize | 决定 Row 本身在水平方向上的尺寸策略:占满所有可用空间(max)或仅包裹子组件内容(min)。 | | children | List | 需要被水平排列的子组件列表。 | <img src="https://picture.qfxaile.top/i/0/2025/12/23/ixzayc-0.png" alt="image-20251223114546013" style="zoom:50%;" style=""> 代码示例: ```dart MaterialApp( home: Scaffold( appBar: AppBar(title: Text("Row代码示例"), centerTitle: true), body: Container( width: double.infinity, // 宽度尽可能占满父组件 height: double.infinity, // 高度尽可能占满父组件 decoration: BoxDecoration(color: Colors.amber), child: Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // 两头对齐 // mainAxisAlignment: MainAxisAlignment.spaceAround, // 环绕模式 // mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 环绕模式 // mainAxisAlignment: MainAxisAlignment.start, // 从头排列 // mainAxisAlignment: MainAxisAlignment.end, // 从尾排列 mainAxisAlignment: MainAxisAlignment.center, // 居中排列 // crossAxisAlignment: CrossAxisAlignment.start, // 交叉轴start // crossAxisAlignment: CrossAxisAlignment.center, // 交叉轴居中 // crossAxisAlignment: CrossAxisAlignment.end, // 交叉轴end // crossAxisAlignment: CrossAxisAlignment.stretch, // 填充交叉轴 children: [ Container(width: 100, height: 100, color: Colors.blue), Container( margin: EdgeInsets.only(left: 10, right: 10), // 注意:与Column不同 width: 100, height: 100, color: Colors.blue, ), Container(width: 100, height: 100, color: Colors.blue), ], ), ), ), ); ``` 适用场景 几乎所有需要水平排列元素的界面都能看到它的身影。 - **导航栏**:如顶部或底部的标签栏、按钮组。 - **图文混排**:如列表项左侧的图标与右侧的文本描述。 - **表单行**:如标签和输入框的组合。 注意事项 1. Row 本身不支持滚动;若内容超出,需用 `ListView` 或 `SingleChildScrollView` 包裹。 2. 明确尺寸约束:父组件的大小直接影响 Row 的最终尺寸及子组件的布局行为。 ### 3.弹性布局 #### Flex-弹性布局 - **作用**:允许沿一个主轴(水平或垂直)排列其子组件,灵活地控制这些子组件在主轴上的尺寸比例和空间分配。 | 属性 | 类型 | 作用说明 | |----------------------|---------------------|--------------------------------------------------------------------------| | direction | Axis.horizontal 或 Axis.vertical | 主轴方向,决定子组件的排列方向。 | | mainAxisAlignment | MainAxisAlignment | 子组件在主轴方向上的对齐方式。 | | crossAxisAlignment | CrossAxisAlignment | 子组件在交叉轴方向上的对齐方式。 | | mainAxisSize | MainAxisSize | Flex 容器自身在主轴上的尺寸策略。 | - **子组件**:Flex的子组件常使用Expanded或Flexible来控制空间分配 - Flex是Column和Row的结合体 #### Expanded/Flexible-弹性子组件 - Expanded/Flexible作为Flex的子组件通过flex属性来分配Flex组件空间 - **布局约束**:Flex 布局受父组件传递的约束影响,务必确保父组件提供了适当的布局约束。 - **Expanded vs Flexible** - `Expanded`:强制子组件填满所有剩余空间。 - `Flexible`:仅根据自身大小调整,不强制占满空间。 代码示例: ```dart Flex( direction: Axis.horizontal, // 方向: 水平 children: [ Expanded( // Flexible // fit: FlexFit.tight, flex: 2, // 占2份 child: Container(color: Colors.red, height: 100, width: 100,), ), Expanded( // Flexible // fit: FlexFit.tight, flex: 1, // 占1份 child: Container(color: Colors.green, height: 100, width: 100,), ), ], ) ``` #### 案例 <img src="https://picture.qfxaile.top/i/0/2025/12/23/k18sto-0.png" alt="image-20251223121140324" style="zoom: 35%;" style=""> 代码: ```dart Flex( direction: Axis.vertical, children: [ Container(color: Colors.blue, height: 100), Expanded( child: Container(color: Colors.grey), ), Container(color: Colors.red, height: 100), ], ) ``` ### 4.流式布局 #### Wrap-流式布局容器 **作用**: 流式布局容器;当子组件在主轴方向排不下时,自动换行(或换列)。 | 属性 | 常用值 | 作用说明 | |----------------|---------------------------------|--------------------------------------| | direction | Axis.horizontal / Axis.vertical | 主轴方向,即排列方向。 | | spacing | 数值 | 主轴方向上子组件之间的间距。 | | runSpacing | 数值 | 交叉轴方向上“行/列”之间的间距。 | | alignment | WrapAlignment | 子组件在主轴方向上的对齐方式。 | | runAlignment | WrapAlignment | 交叉轴方向上的对齐方式。 | - **注意**: `Column` / `Row` / `Flex` 内容超出均不会换行。 - `Wrap` 可视为“带换行能力的 Flex”。 代码示例: ```dart // 创建10个容器 List<Widget> getList() { return List.generate(10, (index) { return Container(width: 100, height: 100, color: Colors.blue); }); } Wrap( spacing: 10, // 主轴 runSpacing: 10, // 交叉轴 alignment: WrapAlignment.spaceBetween, // 主轴对齐方式 direction: Axis.horizontal, children: getList() // 创建10个容器 ) ``` ### 5.层叠布局 #### Stack/Positioned-层叠布局容器 - **作用**:层叠布局容器,允许多个子组件沿 Z 轴(深度方向)叠加排列。 | 属性 | 类型 | 作用说明 | |-------------|--------------------|--------------------------------------------------------------------------| | alignment | AlignmentGeometry | 控制**非定位**子组件在 Stack 内的对齐方式,默认左上角。 | | fit | StackFit | 控制**非定位**子组件如何适应 Stack 的尺寸。 | | clipBehavior | Clip | 控制子组件超出 Stack 边界时的裁剪方式。 | | children | List | 需要被层叠排列的子组件列表。 | - **黄金搭档:Positioned**, 必须作为 Stack 的**直接子组件**。 - 通过 `left` / `right` / `top` / `bottom` 四个属性将子组件“钉”在 Stack 的任意角落或边缘,实现精确定位。 Stack基础用法: ```dart Stack( alignment: Alignment.center, // 对齐方式 children: [ Container(width: 300, height: 300, color: Colors.blue), Container(width: 200, height: 200, color: Colors.red), Container(width: 100, height: 100, color: Colors.amber), Container(width: 80, height: 80, color: Colors.green), ], ) ``` Positioned用法: ```dart Stack( children: [ Container(width: 300, height: 300, color: Colors.grey), Positioned( left: 10, top: 10, child: Container(width: 100, height: 100, color: Colors.red), ), Positioned( right: 10, bottom: 10, child: Container(width: 100, height: 100, color: Colors.blue), ), // Positioned( // left: 0, // right: 0, // top: 0, // bottom: 0, // 实现占满整个容器 // child: Container(width: 100, height: 100, color: Colors.green), // ) ], ) ``` 适用场景: 几乎所有需要叠加效果的界面都能看到它的身影。 - **叠加效果**:图像上的水印、文本、徽章。 - **浮层交互**:模态对话框、提示弹窗、操作菜单。 - **悬浮按钮**:按钮悬浮在特定内容之上。 注意事项 1. 层叠顺序由 `children` 列表中的**先后顺序**决定,后出现者居上。 2. 必须明确尺寸约束:父组件的大小直接影响 Stack 的最终尺寸及子组件的布局行为。 ### 6.滚动布局 常用滚动组件 | 组件 | 特点 | 典型使用场景 | |--------------------|----------------------------------------------------------------------|-------------------------------------------------| | SingleChildScrollView | 仅支持单个子组件滚动,内容一次性全部渲染。 | 长表单、设置页、内容总量不多的页面。 | | ListView | 线性列表;可通过 `builder` 实现懒加载,性能优异。 | 聊天记录、新闻流、单列数据列表。 | | GridView | 网格布局列表;支持懒加载,可固定列数。 | 图片墙、商品网格、应用图标列表。 | | CustomScrollView | 组合多个 Sliver 实现复杂滚动效果,布局灵活。 | 电商首页、社交 App 个人主页、多区域联动滚动。 | | PageView | 整页滚动,支持横向/纵向切换,自带页面指示器。 | 应用引导页、图片轮播、电子书翻页。 | #### SingleChildScrollView-单子组件滚动 - **用法**:包裹一个子组件,让单个子组件具备滚动能力 - controller:给组件的controller绑定ScrollController对象 - 只能包含一个子组件;若要滚动多个组件,通常将其嵌套在 Column 或 Row 中 - 滚动方向:通过 scrollDirection 属性控制,默认为垂直方向(Axis.vertical),也可设置为水平方向(Axis.horizontal) - 特点:一次性构建所有子组件;若嵌套的 Column 或 Row 中包含大量子项,可能会导致性能问题,建议使用 ListView - 控制滚动:绑定一个 ScrollController 对象给 controller 属性,使用 animateTo / jumpTo 方法控制滚动 - 滚动到顶部:controller.jumpTo(0) - 滚动到底部:controller.jumpTo(controller.position.maxScrollExtent) 示例代码: ```dart class _MainPageState extends State<MainPage> { ScrollController _controller = ScrollController(); // 滚动控制器 @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text("登录"), centerTitle: true), body: Stack( children: [ SingleChildScrollView( controller: _controller, padding: EdgeInsets.all(20), child: Column( children: List.generate(100, (index) { return Container( margin: EdgeInsets.only(top: 10), width: double.infinity, height: 100, color: Colors.blue, child: Text( "我是第${index + 1}个", style: TextStyle(fontSize: 20, color: Colors.white), ), alignment: Alignment.center, ); }), ), ), Positioned( right: 10, top: 10, child: GestureDetector( onTap: () { // _controller.jumpTo(_controller.position.maxScrollExtent); // 直接跳转 _controller.animateTo( // 动画跳转 _controller.position.maxScrollExtent, duration: Duration(seconds: 1), curve: Curves.ease, ); }, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(40), color: Colors.red, ), width: 80, height: 80, alignment: Alignment.center, child: Text("去底部", style: TextStyle(color: Colors.white)), ), ), ), Positioned( right: 10, bottom: 10, child: GestureDetector( onTap: () { _controller.jumpTo(_controller.position.minScrollExtent); }, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(40), color: Colors.red, ), width: 80, height: 80, alignment: Alignment.center, child: Text("去顶部", style: TextStyle(color: Colors.white)), ), ), ), ], ), ), ); } } ``` #### ListView-线性列表 - **作用**:用于构建可滚动列表的核心部件,并提供流畅滚动体验。 - 方式:提供多种构造函数,如`默认构造函数`、`ListView.builder`、`ListView.separated`。 - 机制:采用按需渲染(懒加载),只构建当前可见区域的列表项,极大提升长列表性能。 ##### **默认构造函数** ```dart ListView( children: List.generate(100, (index) { return Container( margin: EdgeInsets.only(top: 10), color: Colors.blue, width: double.infinity, height: 80, child: Text( "第${index + 1}个", style: TextStyle(fontSize: 30, color: Colors.white), ), alignment: Alignment.center, ); }), ) ``` ##### **builder模式** - **作用**:处理长列表或动态数据的首选和推荐方式。 - 方式:接受一个 `itemBuilder` 回调函数来按需构建列表项,通过 `itemCount` 控制列表长度。 ```dart ListView.builder( itemCount: 100, // 列表项的长度 itemBuilder: (BuildContext context, int index) { return Container( margin: EdgeInsets.only(top: 10), color: Colors.blue, width: double.infinity, height: 80, child: Text( "第${index + 1}个", style: TextStyle(fontSize: 30, color: Colors.white), ), alignment: Alignment.center, ); }, ) ``` ##### **separated模式** - **作用**:在 `ListView.builder` 的基础上,额外提供了构建分割线的能力。 - 方式:需要同时提供 `itemBuilder`、`separatorBuilder`、`itemCount` 三个属性。 ```dart ListView.separated( itemCount: 100, // 列表项的长度 itemBuilder: (BuildContext context, int index) { return Container( color: Colors.blue, width: double.infinity, height: 80, child: Text( "第${index + 1}个", style: TextStyle(fontSize: 30, color: Colors.white), ), alignment: Alignment.center, ); }, // 分割线 separatorBuilder: (context, index) { return Container(height: 5, color: Colors.red); }, ) ``` #### GridView-网格列表 - 作用:用于创建二维可滚动网格布局的核心组件。 - 方式:提供多种构建方式 - **GridView 默认构造** —— 写法最繁琐,很少使用。 - **GridView.count** —— 基于固定列数的网格布局(最常用之一)。 - **GridView.extent** —— 基于固定子项最大宽度/高度的网格布局(最常用之二)。 - **GridView.builder** —— 用于网格项数量巨大或动态生成的情况,需接收 gridDelegate 布局委托属性。 - gridDelegate - **SliverGridDelegateWithFixedCrossAxisCount**:固定列数,mainAxisSpacing 主轴间距。 - **SliverGridDelegateWithMaxCrossAxisExtent**:最大宽度,crossAxisSpacing 交叉轴间距。 - scrollDirection:设置滚动方向横向/纵向(默认纵向)。 ##### **GridView.count构造** - **作用**:使用GridView.count创建固定列数网格 - Gridview.count以列数为优先。指定网格多少列,Flutter自动计算列的宽度,在空间内均匀排列 ```dart GridView.count( scrollDirection: Axis.horizontal, // 横向滚动 padding: EdgeInsets.all(10), crossAxisCount: 3, // 设置固定的列数或者行数 mainAxisSpacing: 10, // 主轴间隔 crossAxisSpacing: 10, // 交叉轴间隔 children: List.generate(100, (index) { return Container( alignment: Alignment.center, color: Colors.blue, child: Text( "第${index + 1}个Item", style: TextStyle(color: Colors.white, fontSize: 20), ), ); }), ) ``` ##### **GridView.extent构造** - **作用**:使用GridView.extent指定子项最大宽度或者高度 - Gridview.extent通过`maxCrossAxisExtent`设置子项最大宽度或者高度来计算横向或者纵向的列数 ```dart GridView.extent( maxCrossAxisExtent: 100, // 表示网格中每个子项的最大宽度为 100 像素 mainAxisSpacing: 10, // 主轴间隔 crossAxisSpacing: 10, // 交叉轴间隔 children: List.generate(100, (index) { return Container( alignment: Alignment.center, color: Colors.blue, child: Text( "第${index + 1}个", style: TextStyle(color: Colors.white, fontSize: 20), ), ); }), ) ``` ##### **GridView.builder构造** - **作用**:使用 `GridView.builder` 实现动态长网格(懒加载,仅渲染可见区域)。 - 注意:需接收 `gridDelegate` 布局委托、`itemBuilder` 构建函数、`itemCount` 构建数量。 ```dart GridView.builder( // 布局委托 // 按照最大宽度进行布局 gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 100, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 1.5, // 宽高比 ), // 按照列数进行布局 // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // crossAxisCount: 4, // mainAxisSpacing: 10, // crossAxisSpacing: 10, // ), itemCount: 100, itemBuilder: (context, index) { return Container( alignment: Alignment.center, color: Colors.blue, child: Text( "第${index + 1}个", style: TextStyle(color: Colors.white, fontSize: 20), ), ); }, ) ``` #### CustomScrollView-自定义滚动 - 作用:用于组合多个可滚动组件(如列表、网格),实现统一协调的滚动效果。 - sliver:Flutter 中描述可滚动视图内部一部分内容的组件,它是滚动视图的切片。 - 用法:通过 slivers 属性接收一个 **Sliver 组件列表**。 - sliver 组件对应关系 - **SliverList** 对应 ListView - **SliverGrid** 对应 GridView - **SliverAppBar** 对应 AppBar - **SliverPadding** 对应 Padding - **SliverToBoxAdapter** 对应 ToBoxAdapter(用于包裹普通 Widget) - **SliverPersistentHeader**:粘性吸顶 代码示例: ```dart CustomScrollView( slivers: [ // 切片列表 SliverToBoxAdapter( // 包裹普通的widget child: Container( color: Colors.blue, alignment: Alignment.center, height: 260, child: Text( "轮播图", style: TextStyle(color: Colors.white, fontSize: 20), ), ), ), SliverToBoxAdapter(child: SizedBox(height: 10)), SliverPersistentHeader( delegate: _StickyCategory(), // 吸顶组件 pinned: true, // 固定吸顶 ), SliverList.separated( itemCount: 100, itemBuilder: (context, index) { return Container( color: Colors.blue, alignment: Alignment.center, height: 100, child: Text( "商品$index", style: TextStyle(color: Colors.white, fontSize: 20), ), ); }, separatorBuilder: (context, index) { return SizedBox(height: 10); }, ), ], ) class _StickyCategory extends SliverPersistentHeaderDelegate { @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent,) { return Container( color: Colors.white, child: ListView.builder( itemCount: 30, scrollDirection: Axis.horizontal, itemBuilder: (context, index) { return Container( width: 100, margin: EdgeInsets.symmetric(horizontal: 10), color: Colors.blue, alignment: Alignment.center, child: Text("分类$index", style: TextStyle(color: Colors.white)), ); }, ), ); } @override // 最大展开高度 double get maxExtent => 100; @override // 最小折叠高度 double get minExtent => 60; @override bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { // 是否需要重新构建 return false; } } ``` #### PageView-整页滚动 - **作用**:用于实现分页滚动视图的核心组件。 - 方式:提供多种构建方式,如`默认构造方式`、`PageView.builder` 等。 - 优势:支持懒加载(按需渲染)。 ***轮播图代码示例: ** ```dart class _MainPageState extends State<MainPage> { PageController _pageController = PageController(); // 页面控制器 int _currentIndex = 0; // 当前激活索引 @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: CustomScrollView( slivers: [ SliverToBoxAdapter( child: Stack( children: [ Container( color: Colors.blue, alignment: Alignment.center, height: 260, child: PageView.builder( controller: _pageController, // 页面控制器 itemCount: 10, itemBuilder: (context, index) { return Container( alignment: Alignment.center, child: Text( '轮播图$index', style: TextStyle(fontSize: 20, color: Colors.white), ), ); }, ), ), Positioned( bottom: 0, left: 0, right: 0, height: 20, child: Container( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(10, (index) { return GestureDetector( // 点击事件 onTap: () { // _pageController.jumpToPage(index); // 跳转到指定页面 _pageController.animateToPage( index, duration: Duration(milliseconds: 500), curve: Curves.ease, ); setState(() { _currentIndex = index; }); }, child: Container( margin: EdgeInsets.only(left: 5), width: 10, height: 10, decoration: BoxDecoration( color: _currentIndex == index ? Colors.red : Colors.white, borderRadius: BorderRadius.circular(5), ), ), ); }), ), ), ), ], ), ), ], ), ), ); } } ``` ### 7.其他 #### Text-文本组件 - **作用**: 在用户界面中显示文本的基础组件。 | 属性 | 类型 | 作用说明 | |------------|-------------|--------------------------------------------------------| | data | String | **必需**。要显示的文本内容。 | | style | TextStyle | 文本样式,可设置颜色、字号、字重等。 | | textAlign| TextAlign | 文本在自身容器内的水平对齐方式,如 `left`、`center`。 | | maxLines | int | 文本允许显示的最大行数,超出部分默认截断并省略。 | 示例代码: ```dart // 基本使用 Text( "Hello Flutter!", style: TextStyle( fontSize: 20, // 字体大小 color: Colors.blue, // 字体颜色 fontStyle: FontStyle.italic, // 字体样式 fontWeight: FontWeight.w900, // 字体粗细 decoration: TextDecoration.underline, // 字体装饰 decorationColor: Colors.red // 字体装饰颜色 ) ) // 截断并省略 Text( "今天是一个好日子, 今天是一个好日子, 今天是一个好日子, 今天是一个好日子, 今天是一个好日子, 今天是一个好日子", style: TextStyle(color: Colors.blue, fontSize: 30), maxLines: 1, // 最大行数 overflow: TextOverflow.ellipsis, // 超出省略号 ) ``` **TextSpan**-文本片段 - 如果需要在同一段文本中显示不同样式,可用Text.rich构造函数配合TextSpan来实现 代码示例: ```dart Text.rich( TextSpan( text: "Hello ", children: [ TextSpan( text: "Flutter", style: TextStyle(color: Colors.blue), ), TextSpan(text: "!"), ], style: TextStyle( fontSize: 30, color: Colors.red, fontWeight: FontWeight.bold, ), ), ) ``` - 适用场景: 所有文本展示都必须使用 Text 组件。 - 注意事项: 1. 优先级:`Text` 自身属性 > `TextStyle` 中的同名属性(如 `overflow`)。 2. 长文本务必显式设置 `maxLines` 与 `overflow`,防止布局异常。 3. 对大量重复出现的文本样式,请统一定义成共享 `TextStyle`,既保证视觉一致,又能提升渲染性能。 #### Image-图片组件 **作用**: 在用户界面中显示图片的核心部件。 图片分类: | 分类 | 作用说明 | |----------------|--------------------------------------------------------------------------| | Image.asset() | 加载项目资源目录(`assets`)中的图片;需在 `pubspec.yaml` 声明资源路径。 | | Image.network() | 直接从网络地址加载图片。 | | Image.file() | 加载设备本地存储中的图片文件。 | | Image.memory() | 加载内存中的图片数据(`Uint8List`)。 | 常用属性: | 分类 | 类型 | 作用说明 | |-------------|----------------------|--------------------------------------------------------------------------| | width / height | double | 设置图片显示区域的宽度与高度。 | | fit | BoxFit | 控制图片如何适应显示区域(拉伸、裁剪、保持比例等)。 | | alignment | AlignmentGeometry | 图片在显示区域内的对齐方式,如 `Alignment.center`。 | | repeat | ImageRepeat | 当图片小于显示区域时,设置是否及如何重复平铺。 | 以Image.network为例: ```dart Image.network( "图片链接", width: 100, height: 100, fit: BoxFit.fill, // 填充方式 ) ``` #### TextField-文本输入组件 - **作用**: 实现文本输入功能的核心组件 | 属性 | 作用说明 | |-------------|--------------------------------------------------------------------------| | controller | 文本编辑控制器,用于获取/设置文档内容并监听变化。 | | decoration | 控制输入框外观,如标签、提示文字、图标、边框等。 | | style | 定义输入文本的样式。 | | maxLines | 最大行数。 | | onChanged | 输入内容发生变化时执行的回调函数。 | | onSubmitted | 用户提交输入(如按回车)时的回调函数。 | 登录页面案例: ```dart class MainPage extends StatefulWidget { const MainPage({super.key}); @override State<MainPage> createState() => _MainPageState(); } class _MainPageState extends State<MainPage> { TextEditingController _phoneController = TextEditingController(); // 账号控制器 TextEditingController _codeController = TextEditingController(); // 密码控制器 @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text("登录"), centerTitle: true), body: Container( padding: EdgeInsets.all(20), color: Colors.white, child: Column( children: [ TextField( controller: _phoneController, onChanged: (value) { // 输入框内容改变 print("用户名->$value"); }, onSubmitted: (value) { // 键盘回车键 print("用户名->$value"); }, decoration: InputDecoration( hintText: "请输入用户名", filled: true, fillColor: Colors.amber[50], border: OutlineInputBorder( borderSide: BorderSide.none, // 除去边框 borderRadius: BorderRadius.circular(25), // 圆角 ), ), ), SizedBox(height: 20), TextField( controller: _codeController, obscureText: true, // 不显示实际内容 decoration: InputDecoration( hintText: "请输入密码", // 提示文本 filled: true, // 允许填充 fillColor: Colors.amber[50], border: OutlineInputBorder( borderSide: BorderSide.none, // 除去边框 borderRadius: BorderRadius.circular(25), // 圆角 ), ), ), SizedBox(height: 20), Container( height: 50, width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(25), color: Colors.black, ), child: TextButton( onPressed: () { print( "登录->${_phoneController.text}-${_codeController.text}", ); }, child: Text( "登录", style: TextStyle(color: Colors.white, fontSize: 16), ), ), ), ], ), ), ), ); } } ``` TextField 使用要点 - 必须使用有状态组件 使用 TextField 时必须将其放在 StatefulWidget 中。 - 内容管理 用 TextEditingController 控制输入内容;通过 onChanged 监听实时变化。 - 外观定制 借助 decoration: InputDecoration(...) 设置边框、背景、提示文字等样式。 - 密码输入 设置 obscureText: true 即可隐藏输入内容,用于密码框。 ## 七.组件通信 | 通信方式 | 方向 | 适用场景 | |------------------|------------|----------------------| | 构造函数传递 | 父 => 子 | 简单的数据传递 | | 回调函数 | 子 => 父 | 子组件通知父组件 | | InheritedWidget | 祖先 => 后代 | 跨层级数据共享 | | Provider | 任意组件间 | 状态管理推荐方案 | | EventBus | 任意组件间 | 全局事件通信 | | Bloc/Riverpod | 任意组件间 | 复杂状态管理 | ### 1.父传子(构造函数传参数) - 步骤: 1. 子组件定义接收属性 2. 子组件在构造函数中接收参数 3. 父组件传递属性给子组件 4. 有状态组件在‘对外的类’接收属性,‘对内的类’通过widget对象获取对应属性 5. 注意⚠️:子组件定义接收属性需要使用final关键字-因为属性由父组件决定,子组件不能随意更改 - 需求: 定义父子组件,父组件传递一个message变量给子组件并显示 ```dart // 父组件 class MainPage extends StatelessWidget { const MainPage({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Container( alignment: Alignment.center, child: Column( children: [ Text("父组件", style: TextStyle(fontSize: 20, color: Colors.blue)), Child(message: "父组件给子组件传递的数据"), ], ), ), ), ); } } // 无状态组件子组件 class Child extends StatelessWidget { final String? message; // 定义属性 const Child({super.key, this.message}); @override Widget build(BuildContext context) { return Container( child: Text( "子组件-$message", style: TextStyle(fontSize: 20, color: Colors.red), ), ); } } // 有状态组件子组件 class Child extends StatefulWidget { final String message; const Child({super.key, required this.message}); @override State<Child> createState() => _ChildState(); } class _ChildState extends State<Child> { @override Widget build(BuildContext context) { return Text( "子组件-${widget.message}", style: TextStyle(fontSize: 20, color: Colors.red), ); } } ``` #### 案例 ```dart // 父组件 class MainPage extends StatefulWidget { const MainPage({super.key}); @override State<MainPage> createState() => _MainPageState(); } class _MainPageState extends State<MainPage> { List<String> _list = ["鱼香肉丝", "宫保鸡丁", "西红柿鸡蛋", "尖椒肉丝", "小炒肉"]; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: GridView.count( crossAxisCount: 2, mainAxisSpacing: 10, crossAxisSpacing: 10, children: List.generate(_list.length, (index) { return Child(foodName: _list[index]); }), ), ), ); } } // 子组件 class Child extends StatefulWidget { final String foodName; const Child({super.key, required this.foodName}); @override State<Child> createState() => _ChildState(); } class _ChildState extends State<Child> { @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, color: Colors.blue, child: Text( widget.foodName, style: TextStyle(fontSize: 20, color: Colors.white), ), ); } } ``` ### 2.子传父(回调函数) - 步骤: 1. 父组件传递一个函数给子组件 2. 子组件调用该函数 3. 父组件通过回调函数获取参数 - 需求:点击子组件删除父组件的菜品数据并更新列表 ```dart // 父组件 class MainPage extends StatefulWidget { const MainPage({super.key}); @override State<MainPage> createState() => _MainPageState(); } class _MainPageState extends State<MainPage> { List<String> _list = ["鱼香肉丝", "宫保鸡丁", "西红柿鸡蛋", "尖椒肉丝", "小炒肉"]; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: GridView.count( crossAxisCount: 2, mainAxisSpacing: 10, crossAxisSpacing: 10, children: List.generate(_list.length, (index) { return Child( foodName: _list[index], index: index, delFood: (int i) { setState(() { _list.removeAt(i); }); }, ); }), ), ), ); } } // 子组件 class Child extends StatefulWidget { final String foodName; // 食物名称 final int index; // 索引 final Function(int index) delFood; // 声明函数 const Child({ super.key, required this.foodName, required this.index, required this.delFood, }); @override State<Child> createState() => _ChildState(); } class _ChildState extends State<Child> { @override Widget build(BuildContext context) { return Stack( children: [ Container( alignment: Alignment.center, color: Colors.blue, child: Text( widget.foodName, style: TextStyle(fontSize: 20, color: Colors.white), ), ), Positioned( top: 10, right: 10, child: IconButton( color: Colors.red, onPressed: () { print("点击了删除, 删除的是${widget.index}"); widget.delFood(widget.index); }, icon: Icon(Icons.delete), ), ), ], ); } } ``` ### 其它的组件通信在后续综合案例里 ## 八.网络请求 ### DIO插件使用 - 网络请求是Fluter移动应用开发的核心功能,最常用的网络请求工具是使用Dio插件 - 安装dio:flutter pub add dio - 基本使用:Dio().get(地址).then().catchError() ### DIO网络请求案例 #### 1.创建DIO工具类 ```dart class DioUtils { final Dio _dio = Dio(); // 创建dio实例 DioUtils() { // 做一些基本的操作 // 配置基础地址 和 超时时间 // _dio.options.baseUrl = "https://geek.itheima.net/v1_0/"; // _dio.options.connectTimeout = Duration(seconds: 5); // 连接超时 // _dio.options.sendTimeout = Duration(seconds: 10); // 发送超时 // _dio.options.receiveTimeout = Duration(seconds: 10); // 接收超时 // 简写: ..连续赋值写法 _dio.options ..baseUrl = "https://geek.itheima.net/v1_0/" ..connectTimeout = Duration(seconds: 5) ..sendTimeout = Duration(seconds: 10) ..receiveTimeout = Duration(seconds: 10); // 拦截器(请求/响应/错误) _addInterceptor(); } void _addInterceptor() { _dio.interceptors.add( InterceptorsWrapper( // 请求拦截器 onRequest: (options, handler) { // return handler.next(requestOptions); // 放过请求 // return handler.reject(error); // 拦截请求 return handler.next(options); }, // 响应拦截器 onResponse: (reponse, handler) { // http状态码 2xx 3xx 4xx 5xx // return handler.reject(error); if (reponse.statusCode! >= 200 && reponse.statusCode! < 300) { return handler.next(reponse); } // 说明出异常 return handler.reject( DioException(requestOptions: reponse.requestOptions), ); }, // 错误拦截器 onError: (error, handler) { return handler.reject(error); // 直接抛出异常 }, ), ); } // 向外暴露一个get方法 get(String url, {Map<String, dynamic>? params}) { return _dio.get(url, queryParameters: params); } } ``` #### 2.初始化获取数据 ```dart class _MainPageState extends State<MainPage> { @override void initState() { super.initState(); // 发起网络请求 _getChannels(); } List<Map<String, dynamic>> _list = []; // 用来接收数据 void _getChannels() async { DioUtils dioUtils = DioUtils(); Response<dynamic> result = await dioUtils.get("/channels"); Map<String, dynamic> res = result.data as Map<String, dynamic>; // print(res["data"]["channels"] as List<Map<String, dynamic>>); List data = res["data"]["channels"] as List; // print(data.cast<Map<String,dynamic>>() as List<Map<String, dynamic>>); _list = data.cast<Map<String, dynamic>>(); // cast方法强制转化列表项的类型 setState(() { print(_list); }); // channels是一个后端支持前端跨域访问的接口 cros 支持任何域名进行访问 } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text("频道管理"), centerTitle: true), body: Center(child: Text("频道管理")), ), ); } } ``` #### 3.解决web端的跨域问题 默认情况下,flutter 运行 web 端加载网络资源会报跨域提示错误。以下是解决步骤: 1. 在 `flutter/packages/flutter_tools/lib/src/web/chrome.dart` 文件中,添加 `--disable-web-security` 参数,如图: <img src="https://picture.qfxaile.top/i/0/2025/12/28/10thm52-0.png" alt="image-20251228222629547" style="zoom:60%;" style=""> 2. 删除 flutter/bin/cache/ 下的 flutter_tools.snapshot 和 flutter_tools.stamp 文件。 3. 执行 flutter doctor -v 命令,然后重新运行项目。 #### 4.父传子实现 ```dart // 父组件 @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text("频道管理"), centerTitle: true), body: GridView.extent( maxCrossAxisExtent: 140, children: List.generate(_list.length, (index) { return ChannelItem(item: _list[index]); }), ), ), ); } // 子组件 class ChannelItem extends StatelessWidget { final Map<String, dynamic> item; const ChannelItem({super.key, required this.item}); @override Widget build(BuildContext context) { return Card( child: Container( alignment: Alignment.center, child: Text(item['name'] ?? "空", style: TextStyle(fontSize: 20)), ), ); } } ``` ## 九.路由管理 - 定义:路由管理是构建多页面应用的核心,它通过 `Navigator` 和 `Route` 来管理页面栈,实现页面跳转与返回。 <img src="https://picture.qfxaile.top/i/0/2025/12/29/ioi26o-0.png" alt="image-20251229112953052" style="zoom:50%;" style=""> - 跳转方法 | 方法 | 核心作用 | 使用场景 | 典型场景 | |---|---|---|---| | pushNamed | 进入新页面 | [A,B] → [A,B,C] | 常规跳转,如列表页进详情页 | | pushReplacementNamed | 替换当前页面 | [A,B] → [A,C] | 登录成功后进主页,无法返回登录页 | | pushNamedAndRemoveUntil | 跳转新页面并清空栈 | [A,B,C,D] → [A,E] | 退出登录后跳登录页并清空历史 | | popAndPushNamed | 返回并立即跳转新页面 | [A,B,C] → [A,B,D] | 购物车结算后返回商品列表并跳订单页 | | popUntil | 连续返回直到条件满足 | [A,B,C,D] → [A,B] | 从深层设置页一键回到主设置页 | ### 1.基本路由 - 场景:基本路由适合页面不多、跳转逻辑简单的场景。 - 用法:无需提前注册路由,跳转时创建 `MaterialPageRoute` 实例即可。 <img src="https://picture.qfxaile.top/i/0/2025/12/29/qj310g-0.png" alt="image-20251229160415493" style="zoom:33%;" style=""> - 跳转新页面:`Navigator.push(BuildContext context, Route route)` - 返回上一页:`Navigator.pop(BuildContext context)` ```dart // 路由跳转-Material风格 只能有一个MaterialApp class MainPage extends StatelessWidget { const MainPage({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: ListPage(), // 列表页 详情页 ); } } // 列表页 class ListPage extends StatefulWidget { const ListPage({super.key}); @override State<ListPage> createState() => _ListPageState(); } class _ListPageState extends State<ListPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('列表页')), body: ListView.builder( itemCount: 100, itemBuilder: (context, index) { return GestureDetector( onTap: () { // 跳转到详情页 Navigator.push( context, MaterialPageRoute(builder: (context) => DetailPage()), ); }, child: Container( color: Colors.blue, alignment: Alignment.center, margin: EdgeInsets.all(5), height: 50, child: Text( "列表项$index", style: TextStyle(color: Colors.white, fontSize: 20), ), ), ); }, ), ); } } // 详情页 class DetailPage extends StatefulWidget { const DetailPage({super.key}); @override State<DetailPage> createState() => _DetailPageState(); } class _DetailPageState extends State<DetailPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('详情')), body: Center( child: TextButton( onPressed: () { Navigator.pop(context); }, child: Text("返回上一个页面"), ), ), ); } } ``` ### 2.命名路由 - 场景:应用页面增多后,使用命名路由提升代码可维护性。 - 用法:需先在 `MaterialApp` 中注册路由表(`routes`)并设置 `initialRoute`(首页)。 <img src="https://picture.qfxaile.top/i/0/2025/12/29/rct1sf-0.png" alt="image-20251229165407256" style="zoom:50%;" style=""> ```dart // 路由跳转-Material风格 只能有一个MaterialApp class MainPage extends StatelessWidget { const MainPage({super.key}); @override Widget build(BuildContext context) { return MaterialApp( initialRoute: "/list", // 初始路由 routes: { // 路由表 "/list": (context) => ListPage(), "/detail": (context) => DetailPage(), }, home: ListPage(), // 列表页 详情页 ); } } // 使用方法 Navigator.pushNamed(context, '/detail'); ``` - 命名路由 vs 简单路由 - 命名路由:需在 `MaterialApp` 的 `routes` 中预先注册路由表,适合中大型项目管理。 - 简单路由:直接构建页面,更灵活,适合简单应用或快速原型开发。 ### 3.传递参数 - 作用:通过路由传递参数是实现页面间数据通信的常用方式。 #### 命名路由 - 传递参数(命名路由):`Navigator.pushNamed(context, '地址', arguments: 参数)` - 接收参数(命名路由):`ModalRoute.of(context)?.settings.arguments` - 接收时机:`initState` 中拿不到路由参数,需放在 `Future.microtask((){ ... })` 里异步获取。 ```dart // 路由跳转 传递参数 Navigator.pushNamed( context, '/detail', arguments: {'id': index + 1}, ); // 子组件获取参数 String _id = ""; @override void initState() { super.initState(); // 开启一个微任务,微任务会等所有的同步任务以及当前队列的任务执行完成才会执行 Future.microtask(() { // 获取路由参数 if (ModalRoute.of(context) != null) { // 能获取路由参数 print("获取路由参数成功"); Map<String, dynamic> params = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>; print(params["id"]); _id = params["id"].toString(); setState(() {}); } else { // 无法获取路由参数 print("获取路由参数失败"); } }); } ``` #### 基础路由 - 传递参数(基础路由):通过组件构造函数传递参数(父传子)。 - 接收参数(基础路由):通过组件构造函数接收参数(父传子)。 - 接收时机:`initState` 中可直接获取构造函数传参。 ```dart // 跳转到详情页 Navigator.push( context, MaterialPageRoute( builder: (context) => DetailPage(id: index + 1), ), ); // 详情页 class DetailPage extends StatefulWidget { final int id; const DetailPage({super.key, required this.id}); @override State<DetailPage> createState() => _DetailPageState(); } class _DetailPageState extends State<DetailPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('详情${widget.id}')), body: Center( child: TextButton( onPressed: () { Navigator.pop(context); }, child: Text("返回上一个页面"), ), ), ); } } ``` ### 4.动态路由与高级控制 - 场景:更复杂的场景,如需根据参数动态生成页面,或实现路由拦截,可使用 `onGenerateRoute` 与 `onUnknownRoute`。 <img src="https://picture.qfxaile.top/i/0/2025/12/29/sv8ofv-0.png" alt="image-20251229174550721" style="zoom:67%;" style=""> #### onGenerateRoute - `onGenerateRoute`:允许根据 `RouteSettings`(含路由名称与参数)动态创建不同 `Route`。 <img src="https://picture.qfxaile.top/i/0/2025/12/29/swb6tj-0.png" alt="image-20251229174730751" style="zoom: 50%;" style=""> ```dart class MainPage extends StatelessWidget { const MainPage({super.key}); @override Widget build(BuildContext context) { return MaterialApp( initialRoute: "/goodsList", routes: {"/goodsList": (context) => GoodsList()}, // 登录页和购物车页面不在路由表里面 onGenerateRoute: (settings) { // 路由表里面没有的页面 // settings.name print(settings.name); // 去的是不是购物车列表页 if (settings.name == "/cartList") { bool isLogin = false; // 检查是否登录 if (isLogin) { return MaterialPageRoute(builder: (context) => CartList()); } else { return MaterialPageRoute(builder: (context) => LoginPage()); } } }, ); } } // 商品列表 class GoodsList extends StatefulWidget { const GoodsList({super.key}); @override State<GoodsList> createState() => _GoodsListState(); } class _GoodsListState extends State<GoodsList> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("商品列表")), body: Center( child: TextButton( onPressed: () { Navigator.pushNamed(context, "/cartList"); }, child: Text("加入购物车"), ), ), ); } } // 购物车列表 class CartList extends StatefulWidget { const CartList({super.key}); @override State<CartList> createState() => _CartListState(); } class _CartListState extends State<CartList> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("购物车列表")), body: Center( child: TextButton(onPressed: () {}, child: Text("去支付")), ), ); } } // 登录界面 class LoginPage extends StatefulWidget { const LoginPage({super.key}); @override State<LoginPage> createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("登录")), body: Center( child: TextButton(onPressed: () {}, child: Text("去登录")), ), ); } } ``` #### onUnknownRoute - `onUnknownRoute`:当跳转到既未在路由表注册、也未被 `onGenerateRoute` 处理的路由时调用,通常用于展示“404 页面”。 ```dart onUnknownRoute: (settings) { return MaterialPageRoute( builder: (context) => Scaffold( body: Center(child: Text("404")), ), ); } ``` 最后修改:2025 年 12 月 29 日 © 允许规范转载 赞 1 如果觉得我的文章对你有用,请随意赞赏