flutter Widget、状态管理、Obx、GetX、dio、异步编程 、GetXController 详解
Flutter 核心技术详解
一、Dart 语言基础
1. 核心特性
-
强类型 + 类型推断:
var name = 'Kimi';编译器自动推断为String -
空安全(Null Safety):
String? nullable; String nonNull = '必须赋值'; -
一切皆对象:包括
null、函数、数字 -
单线程事件循环:基于
Isolate实现并发,主线程通过 Event Loop 处理异步任务
2. 关键语法
dart
// 命名参数 & 默认值
void greet({String name = 'World', required int age}) {}
// 级联运算符
var paint = Paint()
..color = Colors.black
..strokeWidth = 2.0;
// 扩展运算符
var list = [1, 2, ...anotherList];
// 箭头函数
var doubled = numbers.map((n) => n * 2);
二、Widget 体系
1. 两大分类
| 类型 | 特点 | 示例 |
|---|---|---|
| StatelessWidget | 无状态,UI 静态不变 | Text, Icon, Container |
| StatefulWidget | 有状态,通过 setState() 触发重建 | Checkbox, TextField |
2. Widget 树与三棵树
Widget Tree(配置层) → Element Tree(实例层) → RenderObject Tree(渲染层)
-
Widget:轻量、不可变、描述配置
-
Element:
Widget的挂载实例,管理生命周期 -
RenderObject:真正负责布局和绘制的对象
3. 常用布局 Widget
-
单孩子:
Container、Padding、Center、Expanded -
多孩子:
Row、Column、Stack、Wrap、ListView -
滚动:
SingleChildScrollView、CustomScrollView、PageView
三、状态管理概述
Flutter 状态管理方案演进:
| 方案 | 复杂度 | 适用场景 |
|---|---|---|
setState | 低 | 局部状态、简单页面 |
InheritedWidget | 中 | 跨组件传值(底层机制) |
| Provider / Riverpod | 中 | 中大型项目,官方推荐 |
| GetX | 低 | 快速开发,全功能框架 |
| BLoC | 高 | 复杂业务逻辑,严格分层 |
四、GetX 与 Obx 详解
GetX 是一个轻量级、高性能的全能型 Flutter 框架,集状态管理、路由管理、依赖注入于一体。
1. 核心概念
响应式状态(Reactive State)
使用 .obs 将普通变量变为可观察对象:
dart
// 定义
var count = 0.obs; // int
var name = ''.obs; // String
var items = <String>[].obs; // List
Obx 监听机制
Obx 是 GetX 的自动监听 Widget,只包裹需要刷新的部分:
dart
Obx(() => Text(
'${controller.count}', // 当 count 变化时,仅这个 Text 重建
style: TextStyle(fontSize: 20),
));
2. 完整示例
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// 1. 定义 Controller(业务逻辑层)
class CounterController extends GetxController {
// 响应式变量
var count = 0.obs;
// 计算属性
String get display => '当前计数: $count';
void increment() => count++;
void decrement() => count--;
}
// 2. 页面
class CounterPage extends StatelessWidget {
// 依赖注入:将 Controller 放入内存
final controller = Get.put(CounterController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('GetX 计数器')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 局部刷新:只有 Obx 包裹的部分会重建
Obx(() => Text(
controller.display,
style: TextStyle(fontSize: 24),
)),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: controller.decrement,
child: Icon(Icons.remove),
),
SizedBox(width: 20),
ElevatedButton(
onPressed: controller.increment,
child: Icon(Icons.add),
),
],
),
],
),
),
);
}
}
// 3. main.dart
void main() {
runApp(GetMaterialApp( // 必须用 GetMaterialApp 替换 MaterialApp
home: CounterPage(),
));
}
3. Obx vs GetBuilder
| 特性 | Obx | GetBuilder |
|---|---|---|
| 监听方式 | 自动追踪 .obs 变量 | 手动调用 update() |
| 重建粒度 | 精确到使用变量的 Widget | 整个 GetBuilder 范围 |
| 性能 | 更高(局部刷新) | 中等 |
| 代码量 | 更少 | 稍多 |
dart
// GetBuilder 示例(手动更新)
class ManualController extends GetxController {
int count = 0;
void increment() {
count++;
update(); // 必须手动触发刷新
}
}
GetBuilder<ManualController>(
builder: (ctrl) => Text('${ctrl.count}'),
)
4. 依赖注入 API
dart
// 放入内存
final c1 = Get.put(Controller()); // 实例化并注册
final c2 = Get.lazyPut(() => Controller()); // 懒加载
final c3 = Get.putAsync(() async => await Controller.init()); // 异步
// 读取
final ctrl = Get.find<Controller>(); // 全局获取
// 删除
Get.delete<Controller>(); // 释放资源
五、Dio 网络请求库
Dio 是 Dart 最强大的 HTTP 客户端,支持拦截器、全局配置、FormData、文件上传/下载、请求取消等。
1. 基础配置
dart
import 'package:dio/dio.dart';
class HttpClient {
static final Dio dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 10),
receiveTimeout: Duration(seconds: 10),
headers: {'Content-Type': 'application/json'},
),
)..interceptors.addAll([
// 日志拦截器
LogInterceptor(requestBody: true, responseBody: true),
// 自定义拦截器
InterceptorsWrapper(
onRequest: (options, handler) {
// 自动添加 Token
options.headers['Authorization'] = 'Bearer $token';
return handler.next(options);
},
onResponse: (response, handler) {
// 统一处理响应
return handler.next(response);
},
onError: (error, handler) {
// 统一错误处理(如 401 跳转登录)
if (error.response?.statusCode == 401) {
Get.toNamed('/login');
}
return handler.next(error);
},
),
]);
}
2. 请求示例
dart
// GET 请求
Future<List<User>> fetchUsers() async {
try {
final response = await HttpClient.dio.get('/users');
return (response.data as List)
.map((json) => User.fromJson(json))
.toList();
} on DioException catch (e) {
// 类型化错误处理
switch (e.type) {
case DioExceptionType.connectionTimeout:
throw '连接超时';
case DioExceptionType.badResponse:
throw '服务器错误: ${e.response?.statusCode}';
default:
throw '网络异常';
}
}
}
// POST 请求(JSON)
Future<void> createUser(String name) async {
final response = await HttpClient.dio.post(
'/users',
data: {'name': name, 'age': 25},
);
}
// 文件上传
Future<void> uploadAvatar(File file) async {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(file.path, filename: 'avatar.jpg'),
});
final response = await HttpClient.dio.post('/upload', data: formData);
}
// 下载文件 + 进度监听
Future<void> downloadFile(String url, String savePath) async {
await HttpClient.dio.download(
url,
savePath,
onReceiveProgress: (received, total) {
if (total != -1) {
final progress = (received / total * 100).toStringAsFixed(0);
print('下载进度: $progress%');
}
},
);
}
// 请求取消(防止页面销毁后回调)
final cancelToken = CancelToken();
HttpClient.dio.get('/long-request', cancelToken: cancelToken);
// 页面 dispose 时调用:
cancelToken.cancel('用户离开页面');
六、异步编程详解
Dart 是单线程事件循环模型,异步通过 Future 和 Stream 实现。
1. Future 与 async/await
dart
// 三种等价写法
// 1. Future 链式调用
fetchUser().then((user) {
print(user.name);
}).catchError((e) {
print('错误: $e');
});
// 2. async/await(推荐)
Future<void> getUser() async {
try {
final user = await fetchUser();
print(user.name);
} catch (e) {
print('错误: $e');
}
}
// 3. 并发执行(Future.wait)
Future<void> getDashboard() async {
final results = await Future.wait([
fetchUser(),
fetchOrders(),
fetchNotifications(),
]);
// 三个请求并行执行,总耗时 = 最慢的那个
}
2. Stream 流式编程
dart
// 创建 Stream
Stream<int> countStream(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(Duration(seconds: 1));
yield i; // 类似 return,但不结束函数
}
}
// 监听 Stream
void main() async {
await for (var count in countStream(5)) {
print(count); // 每秒输出 1, 2, 3, 4, 5
}
}
// Stream 转换
stream
.where((event) => event > 10) // 过滤
.map((event) => event * 2) // 映射
.distinct() // 去重
.listen((data) => print(data));
3. 在 GetX 中结合 Dio
dart
class UserController extends GetxController {
var users = <User>[].obs;
var isLoading = false.obs;
var error = ''.obs;
Future<void> loadUsers() async {
try {
isLoading.value = true;
error.value = '';
final result = await HttpClient.dio.get('/users');
users.value = (result.data as List)
.map((json) => User.fromJson(json))
.toList();
} catch (e) {
error.value = e.toString();
} finally {
isLoading.value = false;
}
}
}
// UI 层
Obx(() {
if (controller.isLoading.value) return CircularProgressIndicator();
if (controller.error.value.isNotEmpty) return Text('错误: ${controller.error}');
return ListView.builder(
itemCount: controller.users.length,
itemBuilder: (_, i) => ListTile(title: Text(controller.users[i].name)),
);
});
七、完整架构建议
plain
lib/
├── main.dart # 入口,GetMaterialApp
├── routes/
│ └── app_pages.dart # GetX 路由配置
├── modules/
│ └── home/
│ ├── bindings/ # 依赖绑定
│ ├── controllers/ # GetXController(状态+逻辑)
│ └── views/ # 页面(纯 UI)
├── data/
│ ├── providers/ # Dio 封装
│ ├── models/ # 数据模型
│ └── repositories/ # 数据仓库
└── core/
├── values/ # 常量、主题
└── utils/ # 工具函数
这套技术栈组合(Dart + Widget + GetX + Dio)能够支撑从中小型到大型 Flutter 项目的全周期开发,GetX 的响应式编程模型与 Dio 的拦截器机制配合,可以实现非常优雅的代码分层。
--------------------------------------------------------------------------------------------------
Flutter GetXController 详解
1. 什么是 GetXController
GetXController 是 GetX 状态管理框架的核心类,它负责:
-
管理状态:持有可变数据,并在数据变化时通知 UI 更新
-
处理业务逻辑:将 UI 与业务逻辑分离
-
管理生命周期:提供类似 StatefulWidget 的生命周期钩子
-
依赖注入:通过
Get.put()/Get.find()实现全局或局部状态共享
2. 核心用法
2.1 基础定义
import 'package:get/get.dart';
class CounterController extends GetxController {
// 1. 定义可观察状态
var count = 0.obs; // RxInt(响应式)
var name = 'Flutter'.obs; // RxString
var items = <String>[].obs; // RxList
// 2. 定义业务方法
void increment() {
count.value++;
// 响应式变量自动通知,无需手动 update()
}
void changeName(String newName) {
name.value = newName;
}
}
2.2 两种状态管理方式
| 方式 | 特点 | 适用场景 |
|---|---|---|
| 响应式 (Rx) | 使用 .obs + Obx(),自动追踪依赖 | 频繁变化、复杂状态 |
| 简单 (Update) | 手动调用 update() + GetBuilder() | 简单状态、性能敏感 |
响应式方式(推荐)
class UserController extends GetxController {
var user = User(name: '', age: 0).obs;
void updateUser(String name, int age) {
user.update((val) { // 更新对象内部值
val?.name = name;
val?.age = age;
});
// 或 user.value = User(name: name, age: age);
}
}
// UI 层
Obx(() => Text(controller.user.value.name));
简单方式(GetBuilder)
class CounterController extends GetxController {
int count = 0;
void increment() {
count++;
update(); // 手动触发重建
}
}
// UI 层
GetBuilder<CounterController>(
builder: (ctrl) => Text('${ctrl.count}'),
);
3. 生命周期方法
GetXController 提供完整的生命周期钩子,无需 StatefulWidget:
class HomeController extends GetxController {
@override
void onInit() {
super.onInit();
// 初始化:类似于 initState
// 适合:数据加载、监听事件、初始化 Worker
ever(count, (value) => print('count changed: $value'));
}
@override
void onReady() {
super.onReady();
// 首次渲染完成后:类似于 didChangeDependencies
// 适合:需要 BuildContext 的操作、动画启动
}
@override
void onClose() {
// 控制器销毁时:类似于 dispose
// 适合:关闭 Stream、取消定时器、释放资源
scrollController.dispose();
super.onClose();
}
}
生命周期流程图
Get.put() / Get.lazyPut()
↓
onInit() ──→ 数据初始化
↓
Widget 首次构建完成
↓
onReady() ──→ 需要 Context 的操作
↓
... 运行中 ...
↓
路由移除 / 手动删除
↓
onClose() ──→ 资源释放
↓
控制器销毁
4. Workers(状态监听)
在 onInit() 中设置自动监听,替代 StreamSubscription:
@override
void onInit() {
super.onInit();
// 每次变化都触发
ever(count, (value) => print('Ever: $value'));
// 只触发一次
once(count, (value) => print('Once: $value'));
// 防抖(停止输入 1 秒后触发)
debounce(username, (value) => validateUsername(value),
time: Duration(seconds: 1));
// 节流(每 2 秒最多触发一次)
interval(counter, (value) => sendToServer(value),
time: Duration(seconds: 2));
}
5. 依赖注入(依赖管理)
5.1 注册控制器
// 方式1:立即注册(最常用)
Get.put(CounterController());
// 方式2:懒加载(首次使用时才创建)
Get.lazyPut(() => CounterController());
// 方式3:异步依赖(如需要等待初始化)
Get.putAsync(() async => await SharedPrefsController.init());
// 方式4:工厂模式(每次获取都创建新实例)
Get.create(() => CounterController());
// 方式5:永久存活(即使页面关闭也不销毁)
Get.put(CounterController(), permanent: true);
5.2 获取控制器
// 在任意位置获取
final controller = Get.find<CounterController>();
// 在 Widget 中绑定(自动管理生命周期)
GetX<CounterController>(
builder: (controller) => Text('${controller.count}'),
);
// 或使用更轻量的 Obx(仅响应式)
Obx(() => Text('${controller.count.value}'));
5.3 绑定路由(Bindings)
推荐做法:将控制器注册与路由分离:
class HomeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => HomeController());
Get.lazyPut(() => UserController());
}
}
// 路由配置
GetPage(
name: '/home',
page: () => HomePage(),
binding: HomeBinding(), // 自动注入依赖
);
6. 完整实战示例
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// ========== Controller ==========
class TodoController extends GetxController {
var todos = <String>[].obs;
var newTodo = ''.obs;
void addTodo() {
if (newTodo.value.isNotEmpty) {
todos.add(newTodo.value);
newTodo.value = '';
}
}
void removeAt(int index) {
todos.removeAt(index);
}
@override
void onInit() {
super.onInit();
// 持久化监听示例
ever(todos, (list) => print('Todos updated: ${list.length} items'));
}
}
// ========== View ==========
class TodoPage extends StatelessWidget {
const TodoPage({super.key});
@override
Widget build(BuildContext context) {
// 页面级注入(页面关闭自动销毁)
final controller = Get.put(TodoController());
return Scaffold(
appBar: AppBar(title: const Text('GetX Todo')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: TextField(
onChanged: (v) => controller.newTodo.value = v,
decoration: const InputDecoration(
hintText: '输入待办事项...',
),
),
),
IconButton(
icon: const Icon(Icons.add),
onPressed: controller.addTodo,
),
],
),
),
// 响应式列表
Expanded(
child: Obx(() => ListView.builder(
itemCount: controller.todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(controller.todos[index]),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => controller.removeAt(index),
),
);
},
)),
),
],
),
);
}
}
// ========== Main ==========
void main() {
runApp(GetMaterialApp(
home: const TodoPage(),
));
}
7. 最佳实践
| 实践 | 说明 |
|---|---|
| 一个页面一个 Controller | 保持职责单一,避免 God Controller |
优先使用 final 声明 | final controller = Get.find<X>() 防止意外重新赋值 |
| 响应式 vs GetBuilder | 简单计数用 GetBuilder,复杂表单用 Obx |
| 及时释放资源 | 在 onClose() 中关闭 TextEditingController、ScrollController 等 |
| 使用 Bindings | 路由与依赖解耦,便于测试和维护 |
避免在 build() 里 Get.put() | 会导致每次重建都重新注册,应在 initState 或页面顶层 |
8. 常见误区
-
update()vsrefresh():update()通知 GetBuilder 重建;refresh()强制 Rx 对象重新计算(极少使用) -
Obx嵌套问题:Obx只追踪其内部使用的.value,如果在条件分支中未使用则不会监听 -
内存泄漏:
Get.put(permanent: true)不会自动释放,需手动Get.delete()或谨慎使用 -
类型推断:
Get.find()必须指定泛型,否则可能找到错误类型
GetXController 的设计哲学是 "用最小的心智负担管理状态"。通过响应式编程 + 依赖注入 + 生命周期管理,它能让你用更少的代码实现清晰的状态架构。
GetXController : find 和 put的区别
put 是 存,find 是 取。两者是「注册 → 查找」的关系。
Get.put<T>() | Get.find<T>() | |
|---|---|---|
| 作用 | 创建实例,注册到 GetX 内存池 | 从内存池按类型查找实例 |
| 会不会创建 | ✅ 会 | ❌ 不会,只取已有的 |
| 调用次数 | 通常 1 次(同类型同 tag 重复注册会报错) | 可调用 无数次 |
| 找不到时 | 直接创建成功 | ❌ 抛异常 not found |
| 典型位置 | main()、页面顶部、Binding | 任意 Widget、Service、其他 Controller |
// 1. 先 put(注册)
final c1 = Get.put(CounterController());
// 2. 再 find(获取)—— c1 和 c2 是同一个对象
final c2 = Get.find<CounterController>();
一句话:put 负责「生和存」,find 负责「拿来用」。先 put 才能 find。
更多推荐
所有评论(0)