目录
[组件通信 - 父传子(构造函数传参数)](#组件通信 - 父传子(构造函数传参数))
[组件通信 - 子传父(回调函数)](#组件通信 - 子传父(回调函数))
[网络请求 - Dio插件使用](#网络请求 - Dio插件使用)
[网络请求案例 - 1. 封装Dio工具](#网络请求案例 - 1. 封装Dio工具)
[网络请求案例 - 2. 初始化获取数据](#网络请求案例 - 2. 初始化获取数据)
[网络请求案例 - 3. 解决web端跨域问题](#网络请求案例 - 3. 解决web端跨域问题)
[网络请求案例 - 4. 父传子实现](#网络请求案例 - 4. 父传子实现)
组件通信
组件通信 - 父传子(构造函数传参数)
|-----------------|-----------|----------|
| 通信方式 | 方向 | 适用场景 |
| 构造函数传递 | 父 => 子 | 简单的数据传递 |
| 回调函数 | 子 => 父 | 子组件通知父组件 |
| InheritedWidget | 祖先 => 后代 | 跨层级数据共享 |
| Provider | 任意组件间 | 状态管理推荐方案 |
| EventBus | 任意组件间 | 全局事件通信 |
| Bloc/Riverpod | 任意组件间 | 复杂状态管理 |
步骤:
- 子组件定义接收属性
- 子组件在构造函数中接收参数
- 父组件传递属性给子组件
- 有状态组件在'对外的类'接受属性,'对内的类'通过widget对象获取对应属性
子组件定义接收属性需要使用final关键字 - 因为属性由父组件决定,子组件不能随意更改
子组件属性如果没有初始值,需要在构造函数中使用required来接收属性
Dart
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MainPage());
}
// 父组件
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(color: Colors.blue, fontSize: 30)),
Child(message: "张三"),
ChildActive(message: "李四")
],
),
),
),
);
}
}
// 无状态组件的子组件
class Child extends StatelessWidget {
final String? message; // 子组件定义属性(使用final)
// 构造函数中接收参数
const Child({super.key,this.message});
@override
Widget build(BuildContext context) {
return Container(
child: Text("无状态子组件 - ${message}", style: TextStyle(color: Colors.red, fontSize: 18)),
);
}
}
// 有状态组件的子组件
class ChildActive extends StatefulWidget { // 对外接收
final String message; // 用final定义
const ChildActive({super.key,required this.message});
@override
State<ChildActive> createState() => _ChildActiveState();
}
class _ChildActiveState extends State<ChildActive> { // 对内接收
@override
Widget build(BuildContext context) {
return Container(
// 对内使用widget对象接收
child: Text("有状态的子组件 -- ${widget.message}", style: TextStyle(color: Colors.red, fontSize: 18)),
);
}
}
小案例:
Dart
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MainPage());
}
// 父组件
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(
padding: EdgeInsets.all(10),
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(
color: Colors.blue,
alignment: Alignment.center,
child: Text(widget.foodName,style: TextStyle(color:Colors.white,fontSize: 20),),
);
}
}

组件通信 - 子传父(回调函数)
步骤:
-
父组件传一个函数给子组件
-
子组件调用该函数
-
父组件通过回调函数获取参数
需求:
点击子组件删除父组件的菜品数据并更新列表
Dart
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MainPage());
}
// 父组件
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(
padding: EdgeInsets.all(10),
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: List.generate(_list.length, (index) {
return Child(foodName: _list[index],index: index, delFood: (int index) {
print("父接收$index");
_list.removeAt(index);
setState(() {});
}); // 返回整个子组件
})),
),
);
}
}
// 子组件
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(
alignment: Alignment.topRight,
children: [
Container(
color: Colors.blue,
alignment: Alignment.center,
child: Text(widget.foodName,style: TextStyle(color:Colors.white,fontSize: 20),),
),
IconButton(color:Colors.red,onPressed: (){
print("删除菜单${widget.index}");
widget.delFood(widget.index);
},icon: Icon(Icons.delete))
],
);
}
}

网络请求 - Dio插件使用
安装dio:flutter pub add dio

基本使用:
Dio( ).get( 地址 ).then( ).catchError( )
import 'package:dio/dio.dart';
void main(List<String> args) {
Dio().get("https://geek.itheima.net/v1_0/channels").then((res) {
print(res);
}).catchError((error) {});
}

一般情况下,在初始化状态initState获取页面数据
网络请求案例 - 1. 封装Dio工具
- 创建工具类
- 构造函数2中设置基础地址和超时时间
- 添加各类拦截器
- 封装统一请求方法
- 请求频道数据进行循环渲染解决web端跨域问题
- 实现UI渲染绘制
Dart
import 'package:dio/dio.dart';
void main(List<String> args) {
}
// 封装一个工具类
class DioUtils {
final Dio _dio = Dio(); // 内部Dio对象
DioUtils(){
// 基本操作
// 配置基础地址和超时时间
// _dio.options.baseUrl = "https://geek.itheima.net/v1_0/";
// _dio.options.connectTimeout = Duration(seconds: 10); // 连接超时
// _dio.options.sendTimeout = Duration(seconds: 10); // 发送超时
// _dio.options.receiveTimeout = Duration(seconds: 10); // 接收超时
// 简 写 ..连续赋值的写法
_dio.options..baseUrl = "https://geek.itheima.net/v1_0/"
..connectTimeout = Duration(seconds: 10)
..sendTimeout = Duration(seconds: 10)
..receiveTimeout = Duration(seconds: 10);
// 拦截器
_addInterceptor(); // 添加注册拦截器
}
void _addInterceptor() {
_dio.interceptors.add(InterceptorsWrapper(
// 请求拦截器
onRequest: (context,handler) {
// handler.next(requestOptions) // 放过请求
// handler.reject(error) 拦截请求
handler.next(context);
},
// 响应拦截器
onResponse: (context,handler){
// handler.next(response);
// http 状态码 2xx 成功 3xx 4xx 5xx
if(context.statusCode! >= 200 && context.statusCode! < 300){
handler.next(context); //放过
return;
}
// 说明异常
handler.reject(DioException(requestOptions: context.requestOptions));
},
// 错误拦截器
onError: (context,handler){
handler.reject(context); // 直接抛出异常
}
));
}
// 向外暴露一个get方法
get(String url,{Map<String,dynamic>? params}){
return _dio.get(url,queryParameters: params);
}
}

网络请求案例 - 2. 初始化获取数据

网络请求案例 - 3. 解决web端跨域问题
默认情况下,flutter运行web端加载网络资源会报跨域提示错误

网络请求案例 - 4. 父传子实现
Dart
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
@override
void initState() {
// TODO: implement initState
super.initState();
// 发起网络请求
__getChannels();
}
List<Map<String,dynamic>> _list = []; // 用来接收数据
void __getChannels() async{
DioUtils dioUtil = DioUtils();
Response<dynamic> result = await dioUtil.get("channels");
Map<String,dynamic> res = result.data as Map<String,dynamic>;
List data = res['data']['channels'] as List;
// cast 强制转换列表项的类型
_list = data.cast<Map<String,dynamic>>() as List<Map<String,dynamic>>;
setState(() {}); // 执行方法,UI才会更新
// channels是一个后端支持前端跨域访问的接口
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar:AppBar(title: Text("频道管理")),
body: GridView.extent(maxCrossAxisExtent: 140,
padding: EdgeInsets.all(10),
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 3,
children: List.generate(_list.length, (index){
return ChannelItem(item:_list[index],index: index,delItem: (int index){
_list.removeAt(index);
setState(() {});
});
}),
)
),
);
}
}
// 用来绘制每个频道的UI内容
class ChannelItem extends StatelessWidget {
final Map<String,dynamic> item;
final int index;
final Function(int index) delItem;
const ChannelItem({super.key,required this.item,required this.index, required this.delItem});
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.topRight,
children: [
Container(
color: Colors.blue,
alignment: Alignment.center,
child: Text(
item['name'] ?? "空",
style: TextStyle(color:Colors.white,fontSize: 14),
),
),
IconButton(icon: Icon(Icons.delete),color: Colors.red,onPressed: (){
delItem(index);
})
],
);
}
}
// 封装一个工具类
class DioUtils {
final Dio _dio = Dio(); // 内部Dio对象
DioUtils(){
// 基本操作
// 配置基础地址和超时时间
// _dio.options.baseUrl = "https://geek.itheima.net/v1_0/";
// _dio.options.connectTimeout = Duration(seconds: 10); // 连接超时
// _dio.options.sendTimeout = Duration(seconds: 10); // 发送超时
// _dio.options.receiveTimeout = Duration(seconds: 10); // 接收超时
// 简 写 ..连续赋值的写法
_dio.options..baseUrl = "https://geek.itheima.net/v1_0/"
..connectTimeout = Duration(seconds: 10)
..sendTimeout = Duration(seconds: 10)
..receiveTimeout = Duration(seconds: 10);
// 拦截器
_addInterceptor(); // 添加注册拦截器
}
void _addInterceptor() {
_dio.interceptors.add(InterceptorsWrapper(
// 请求拦截器
onRequest: (context,handler) {
// handler.next(requestOptions) // 放过请求
// handler.reject(error) 拦截请求
handler.next(context);
},
// 响应拦截器
onResponse: (context,handler){
// handler.next(response);
// http 状态码 2xx 成功 3xx 4xx 5xx
if(context.statusCode! >= 200 && context.statusCode! < 300){
handler.next(context); //放过
return;
}
// 说明异常
handler.reject(DioException(requestOptions: context.requestOptions));
},
// 错误拦截器
onError: (context,handler){
handler.reject(context); // 直接抛出异常
}
));
}
// 向外暴露一个get方法
Future<Response<dynamic>> get(String url,{Map<String,dynamic>? params}){
return _dio.get(url,queryParameters: params);
}
}
