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

特性ObxGetBuilder
监听方式自动追踪 .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. 常见误区

  1. update() vs refresh():update() 通知 GetBuilder 重建;refresh() 强制 Rx 对象重新计算(极少使用)

  2. Obx 嵌套问题:Obx 只追踪其内部使用的 .value,如果在条件分支中未使用则不会监听

  3. 内存泄漏:Get.put(permanent: true) 不会自动释放,需手动 Get.delete() 或谨慎使用

  4. 类型推断: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。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐