what is flutter
Flutter概念
Flutter is a framework for building user interface with Dart
单一代码库可以生存广泛的目标平台的应用程序
与react native和uniapp进行比较
那么如何实现跨平台的渲染呢,因为不同的操作系统它暴露出来的渲染的方法当然会不同,如何实现一套代码在不同的操作系统上面都可以使用呢,这个就是跨平台框架再做的事情。
与其他的跨平台开发框架对比来说,就以我们比较熟悉的两个前端的跨平台的框架来比较。
那么其实我们前端也是有很多跨平台的框架的,比如说 react native 和 uniapp(就是我们公司现在正在用的这么一个框架,我们可以从这几个框架实现跨平台的方案来进行 flutter 的学习)
由于 Android 和 iOS 的 UI 渲染机制完全不同(分别基于 Skia 与 CoreGraphics,并最终通过 OpenGL / Metal 等图形接口驱动 GPU 绘制),跨平台框架在调用系统绘制接口时需要适配大量差异。 uni-app 的方案是利用各操作系统原生提供的 WebView 控件 作为统一渲染容器,借助 Web 技术完成界面绘制,并通过 JSBridge 与原生层通信,从而实现单一代码库、多平台一致的运行效果。
那么我们可以比较一下这三个跨平台的框架都是如何实现的。
-
uni-app 的做法是:开发者使用 HTML、CSS、JavaScript(基于 Vue 语法) 编写页面逻辑,框架在编译时会将这些代码打包成可在 WebView 中运行的资源文件。 当 App 启动后,WebView 负责渲染 UI 界面,而通过 JSBridge,JavaScript 层可以与原生层交互(如调用相机、蓝牙、定位等原生能力)。 这样,开发者只需维护一套前端代码,就能在 Android、iOS 乃至 Web 等多个平台上运行,实现真正的“一次开发,多端运行”。
-
react native,从名字就可以看出点端倪,native 就是原生的,在 uni-app 的虽然也通过 jsbrdge 与原生层交互,但 uni-app 渲染的是 WebView 内的网页内容,而 react native 完全是通过 jsbridge, React Native 最终渲染的是系统原生控件,而 uni-app 渲染的是 WebView 内的网页内容;两者都依赖 JSBridge,但 RN 的 Bridge 更深入地参与了 UI 渲染流程。
-
flutter,实现跨平台的方案就是使用自绘引擎,不使用对应系统的控件而是自己调用绘制图形的接口实现了自己的渲染引擎,所以无论在任何系统都能保证 ui 的一致性
| 对比项 | uni-app | React Native | Flutter |
|---|---|---|---|
| 技术栈 | Vue / JavaScript | React / JavaScript | Dart |
| 渲染方式 | WebView(或 nvue 原生渲染) | 原生控件渲染 | 自绘引擎(Skia) |
| 性能 | 中等 | 接近原生 | 接近原生甚至更优 |
| 开发成本 | 低(Web 开发者易上手) | 中(需前端+原生知识) | 高(需学 Dart) |
| 生态 | 成熟,小程序支持强 | 成熟,生态稳定 | 快速增长,生态完善 |
| 典型场景 | 中小型 App、小程序、混合应用 | 原生 App、高性能需求 | 高性能跨平台 App、全自定义 UI |
| 代表产品 | 支付宝、头条小程序、快应用 | Facebook、滴滴、携程 | 闪送、阿里咸鱼、京东 |
flutter
Flutter 之所以能够真正实现跨平台,是因为它并不依赖各操作系统原生的控件体系来渲染界面,而是通过自身内置的高性能渲染引擎(Skia),直接调用系统底层暴露的图形接口(如 Android 的 OpenGL / Vulkan、iOS 的 Metal、Windows 的 DirectX 等)在屏幕上进行绘制。
这样一来,无论运行在什么平台上,Flutter 都能以完全一致的渲染逻辑和视觉效果生成界面,实现了“一次绘制,处处一致”的跨平台体验。
[Dart层:Widget树]
↓ 转换为
[RenderObject树]
↓ 交给
[Flutter Engine (C++)]
↓ 调用
[Skia 渲染引擎]
↓ 通过
[OpenGL / Metal / Vulkan]
↓ 最终输出
[屏幕帧缓存 Framebuffer]
┌──────────────────────────┐
│ Flutter Framework (Dart) │ ← 写的部分(Widget、State、布局逻辑)
├──────────────────────────┤
│ Flutter Engine (C++) │ ← 负责渲染、动画、GPU调用
├──────────────────────────┤
│ Embedder (Platform) │ ← 与系统交互(Android/iOS)
└──────────────────────────┘
Dart 语法
Dart 是一门 强类型、面向对象、支持函数式特性 的语言。
我们从几个核心特性来讲
① 类型系统(Static + Inference)
Dart 是 静态类型语言,也支持 类型推断(type inference) 。
静态类型意味着 Dart 会在编译期检查类型错误,而类型推断可以让你在声明变量时省略类型,Dart 会根据赋值自动推断类型。
① var、final、const
var name = 'Ziven'; // 类型推断为 String
final city = 'Tokyo'; // 运行时不可变,只赋值一次,赋值一次之后不可以改变
const country = 'Japan'; // 编译时常量
② late 延迟初始化
- 当变量不能在声明时立即赋值,但确定后不会为 null,可以使用
late - 常用于
final或非空变量的延迟初始化
late String description;
description = 'This is Dart'; // 延迟赋值 ✅
print(description);
- 如果访问前没有赋值,会抛出运行时异常
late String text;
print(text); // ❌ 错误:LateInitializationError
③ dynamic 动态类型
dynamic表示变量可以在运行时改变类型- 关闭类型检查,灵活但不安全
dynamic data = 1;
data = 'Hello'; // ✅
data = true; // ✅
④ Object
Object是 Dart 所有类的基类- 与
dynamic不同:编译器会进行类型检查,调用方法前需要类型转换
Object obj = 'Ziven';
if (obj is String) {
print(obj.length); // ✅ 类型安全
}
- 可空类型:
Object?
Object? obj2 = null;
⑤ 类型声明并初始化为 null(可空类型)
- 变量默认不可为 null,需要用
?声明可空类型
String? nickname; // 可以为 null
int? score; // 可以为 null
nickname = '小Z';
nickname = null; // ✅
⑥ typedef(函数类型别名)
- 用于给函数类型定义一个别名,方便声明变量或参数类型
typedef IntOperation = int Function(int a, int b);
IntOperation add = (x, y) => x + y;
print(add(3, 4)); // 7
⑦ 变量作用域
Dart 中变量的作用域决定了变量可以被访问的范围,主要包括 全局作用域、局部作用域、类作用域。
- 顶级变量(全局作用域) :声明在类或函数外部,全局可用,整个文件甚至整个库都可以访问。
final appName = 'MyApp'; // 顶级变量,全局可访问
void printAppName() {
print(appName); // ✅ 可访问
}
- 局部变量(局部作用域) :声明在函数、方法或代码块中,只在该函数或代码块内部有效。
void main() {
var localVar = 100; // 局部变量,只在 main 函数内可访问
print(localVar); // ✅
// print(otherVar); // ❌ 错误,otherVar 不在作用域内
}
-
类作用域:
- 实例变量:属于类的对象,每个对象独立存在
- 静态变量:属于类本身,类级别共享,整个程序生命周期可访问
- 私有变量(Private Variable) :以
_开头,仅在当前库内可访问,库外不可直接访问
class Person {
String name; // 实例变量,对象独立
static int count = 0; // 静态变量,类级别共享
String _secret = 'I am private'; // 私有实例变量,只能在库内访问
Person(this.name) {
count++;
}
void greet() {
print('Hi, I am $name');
print('秘密信息: $_secret'); // 库内可以访问私有变量
}
// 公有 getter 提供访问私有变量
String get secret => _secret;
// 公有 setter 提供修改私有变量
set secret(String value) => _secret = value;
}
void main() {
var p1 = Person('Ziven');
var p2 = Person('Tom');
p1.greet(); // Hi, I am Ziven
p2.greet(); // Hi, I am Tom
print(Person.count); // 2,静态变量全局共享
// print(p1._secret); // ❌ 错误:库外不可访问私有变量
print(p1.secret); // ✅ 通过 getter 访问
p1.secret = 'New secret'; // ✅ 通过 setter 修改
print(p1.secret); // New secret
}
- 小结:
| 类型 | 作用域 | 生命周期 | 访问方式 |
|---|---|---|---|
| 顶级变量 | 文件 / 库全局 | 程序生命周期 | 直接访问或通过 import/export |
| 局部变量 | 函数或代码块内 | 执行函数时创建,结束销毁 | 只能在局部范围访问 |
| 类实例变量 | 类实例内 | 对象存在期间 | 通过对象访问 |
| 类静态变量 | 类级别 | 程序生命周期 | 通过类名访问 |
⑧ 常用组合示例
late final String token; // 延迟初始化且不可修改
dynamic flexible = 123; // 动态类型
Object general = 'Dart'; // 所有对象类型
String? nullableName; // 可空类型
const pi = 3.14159; // 编译时常量
总结一下,Dart 的变量声明方法丰富,每种都有适用场景:
| 关键字 | 特性 | 用途 |
|---|---|---|
var | 可变,类型由编译器推断 | 普通变量 |
final | 运行时赋值一次 | 不可变变量 |
const | 编译期常量 | 常量表达式 |
late | 延迟初始化 | 延迟赋值的非空变量 |
dynamic | 动态类型,关闭类型检查 | 灵活变量 |
Object/Object? | 所有对象的基类,可空或非空 | 类型安全 |
String?/int? | 可空类型 | 支持空安全 |
typedef | 函数类型别名 | 定义函数变量类型 |
与 JavaScript 最大不同:JS 是动态类型,很多类型错误只有运行时才发现;Dart 在编译时就会捕获类型错误。
② 空安全(Null Safety)
Dart 默认 变量不可为 null,除非显式声明为可空类型(类型后加 ?)。
String name = 'Ziven'; // 不能为 null
String? nickname; // 可为 null
nickname = '小Z'; // ✅
nickname = null; // ✅
访问可空对象时,需要做判空处理:
print(nickname?.length ?? 0); // 可空访问,如果 nickname 为 null,则返回 0
空安全(Null Safety)能在编译期避免空指针异常(NullPointerException),大幅减少运行时崩溃风险。
③ 异步机制(Future / async / await)
Dart 是 单线程事件驱动模型(类似 JavaScript),运行时通过 事件循环(Event Loop) 来管理任务执行。
异步任务执行流程
-
顺序执行栈:普通任务按顺序执行。
-
遇到耗时任务(异步任务,例如网络请求、文件读取):
- 将任务挂起
- 将对应回调注册到 微任务队列(microtask) 或 事件队列(event queue)
-
异步任务完成后:将回调放入执行栈,继续执行,避免阻塞主线程。
总结:Dart 的事件循环机制保证 UI 或主线程不会被阻塞,异步操作在后台完成,再把结果交回主线程处理。
Future<void> fetchData() async {
print('开始请求...');
final data = await getDataFromServer(); // 模拟异步
print('请求结束: $data');
}
⚙️Dart 特性与 JS 对比
Dart 的定位在某种程度上像“为 Flutter 优化过的 JS”。
但在语法、运行机制、性能上有明显不同👇
| 对比项 | Dart | JavaScript |
|---|---|---|
| 类型系统 | 静态类型(编译期检查) | 动态类型(运行期检查) |
| 空安全 | 有(Null Safety) | 无(undefined/null 问题多) |
| 运行机制 | JIT + AOT,可编译为机器码 | 解释执行(JIT 优化) |
| 异步机制 | Future + async/await(语法级支持) | Promise + async/await |
🌍 深入解释几个重点差异
① 编译模式差异
Dart 支持两种编译方式:
| 模式 | 说明 |
|---|---|
| JIT (Just-In-Time) | 热重载开发模式,用于快速调试 |
| AOT (Ahead-Of-Time) | 编译成机器码,用于发布版本(性能高) |
所以比起react native和uni-app来说由于这个编译的特性,flutter的应用的启动更快。
③ 类型系统的本质差异
JS 是弱类型动态语言:
let x = "1" + 2; // "12"
Dart 是强类型静态语言:
var x = "1" + 2; // ❌ 编译错误
这让 Dart 的错误可以在编译期暴露,提升了可靠性。
🔍 小结
| 维度 | Dart 优势 |
|---|---|
| 安全性 | 类型 + 空安全防止低级错误 |
| 性能 | 可直接编译为机器码,性能接近原生 |
| 并发 | Isolate 模型更易控 |
| Flutter | 与 UI 层深度集成,跨平台统一开发体验 |
Flutter 的布局核心
Widget 的概念
-
Widget 是 Flutter UI 的基本构建块,相当于前端的组件或 HTML 元素。
-
Flutter 中一切都是 Widget:布局(Row/Column)、容器(Container)、文本(Text)、图片(Image)、按钮(ElevatedButton)等等。
-
声明式 UI:
- Widget 本身不可变,只描述界面应该“长什么样”(不可变对象容易缓存和复用)。
- 具体的状态、数据变化由 State 控制。
- 每次
setState(),Flutter 会用新的 Widget 描述界面,但复用旧的 State。
常用Widget以及布局约束
布局约束的本质上就是子组件的宽高收到父组件的约束,比如说父亲组件的宽200,那么子组件的宽度范围就在0~200。
超出约束的话就会出现布局报错。
| Widget 类型 | 宽高默认行为 | 举例 |
|---|---|---|
| 容器型(Container, SizedBox) | 遵守父约束,但可强制设置自身尺寸 | Container(width:100) |
| 布局型(Row, Column, Stack) | 先看主轴规则、再看子元素情况 | Row占满宽度但高取决于子 |
| 可扩展型(Expanded/Flexible) | 会占据父组件剩余空间(主轴方向) | Expanded(child: ...) |
| 内容型(Text, Icon, Image) | 尺寸取决于内容本身 | Text('Hi') 宽高由文字决定 |
| 滚动型(ListView, SingleChildScrollView) | 在可滚动方向上可以超出父容器 | ListView 默认会尽量扩展 |
如何使用Widget描述一个组件呢?
Widget组件Demo
比如说我们想开发一个模块卡片的组件,他的样式如下,这就是一个
// 模块卡片
Widget _buildCard(String title, int color, IconData icon, String routeName) {
return GestureDetector(
onTap: () {
print('routername $routeName');
Get.toNamed(routeName);
},
child: Container(
width: 160,
height: 140,
margin: const EdgeInsets.only(right: 12),
decoration: BoxDecoration(
color: Color(color),
borderRadius: BorderRadius.circular(20),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 30, color: Colors.grey[700]),
const SizedBox(height: 8),
Text(
title,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey[800],
fontSize: 14,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
这样子我们就已经写好了一个按钮的样式,接下来就是把这个按钮渲染到页面中。渲染到页面上需要继承 StatelessWidget,StatefulWidget这两个类,然后我们对build方法重写,将想要渲染的widget返回就可以了,将我们的组件像搭积木一样,搭进去,把组件放到想放到的位置就行了
。
状态管理
在 Flutter 中,我们通过继承 StatelessWidget 或 StatefulWidget 来创建页面或组件,并在其中重写 build 方法,将各个 Widget 组合起来渲染到界面上。
这两者的主要区别在于是否需要维护可变状态:
对于不会随时间或交互变化的静态组件,使用 StatelessWidget;
对于需要根据用户操作或数据变化动态更新界面的组件,使用 StatefulWidget。
原生的状态管理
因为Widget是不可变的,因为本身widget就是对于界面的静态描述,Flutter 每次刷新 UI 时(例如 setState() 调用),会重新执行 build() 方法:
build()返回一棵新的 Widget 树。
这时候框架会自动比较:
- 旧的 Widget 树
- 新的 Widget 树
然后找到差异,最小化更新底层的 RenderObject。
这种 diff 策略要求:
Widget 必须是纯描述、无副作用、不可变的。
import 'package:flutter/material.dart';
class TodoPage extends StatefulWidget {
@override
State<TodoPage> createState() => _TodoPageState();
}
class _TodoPageState extends State<TodoPage> {
List<String> todos = [];
String inputText = ""; // 保存输入框内容
void addTodo() {
if (inputText.isEmpty) return;
setState(() {
todos.add(inputText);
inputText = ""; // 添加后清空输入
});
}
void removeTodo(int index) {
setState(() {
todos.removeAt(index);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Todo List')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(
children: [
// TextField 不用 controller,用 onChanged 保存输入
Expanded(
child: TextField(
onChanged: (value) {
setState(() {
inputText = value;
});
},
onSubmitted: (_) => addTodo(), // 按回车也能添加
decoration: const InputDecoration(
hintText: "添加新任务",
border: OutlineInputBorder(),
),
controller: null, // 不使用 controller
// 显示当前输入
// 如果不想显示输入值也可以省略
// textAlignVertical: TextAlignVertical.center,
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: addTodo,
child: const Text("添加"),
),
],
),
const SizedBox(height: 16),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (_, index) {
return ListTile(
title: Text(todos[index]),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => removeTodo(index),
),
);
},
),
)
],
),
),
);
}
}
使用GetX进行状态管理
在 Flutter 中,Widget 是不可变的,每次状态变化都要通过重建 Widget 来反映 UI 变化。
当组件层级变深、状态要跨页面共享时,setState 就变得复杂笨重。
GetX 这样的状态管理框架可以做到:
- 自动管理 UI 与数据的绑定
- 跨组件、跨页面共享状态
- 简化代码,不需要手动写
setStat
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// ----------------- Todo 数据模型 -----------------
class Todo {
String title;
bool done;
Todo({required this.title, this.done = false});
}
// ----------------- 控制器 -----------------
class TodoController extends GetxController {
var todos = <Todo>[].obs;
void addTodo(String title) {
if (title.trim().isNotEmpty) {
todos.add(Todo(title: title.trim()));
}
}
void toggleDone(int index) {
todos[index].done = !todos[index].done;
todos.refresh();
}
void deleteTodo(int index) {
todos.removeAt(index);
}
int get completed => todos.where((t) => t.done).length;
int get pending => todos.length - completed;
}
// ----------------- 页面 -----------------
class TodoListPage extends StatelessWidget {
TodoListPage({super.key});
final TodoController controller = Get.put(TodoController());
final TextEditingController _textController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF7F9FB),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// 标题
const Text(
"My Todo List",
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Color(0xFF004C6D),
),
),
const SizedBox(height: 16),
// 添加任务输入框
Row(
children: [
Expanded(
child: TextField(
controller: _textController,
decoration: InputDecoration(
hintText: "添加新任务",
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
controller.addTodo(_textController.text);
_textController.clear();
},
style: ElevatedButton.styleFrom(
minimumSize: const Size(0, 55), // 高度 50
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text("添加"),
)
],
),
const SizedBox(height: 24),
// Todo列表
Expanded(
child: Obx(() {
if (controller.todos.isEmpty) {
return const Center(
child: Text("暂无任务,快去添加吧!",
style: TextStyle(color: Colors.grey)),
);
}
return ListView.separated(
itemCount: controller.todos.length,
separatorBuilder: (_, __) => const SizedBox(height: 8),
itemBuilder: (context, index) {
final todo = controller.todos[index];
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: const Offset(0, 2),
)
],
),
child: ListTile(
leading: Checkbox(
value: todo.done,
onChanged: (_) => controller.toggleDone(index),
activeColor: const Color(0xFF004C6D),
),
title: Text(
todo.title,
style: TextStyle(
fontSize: 16,
decoration: todo.done
? TextDecoration.lineThrough
: TextDecoration.none,
color: todo.done ? Colors.grey : Colors.black87,
),
),
trailing: IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () => controller.deleteTodo(index),
color: Colors.redAccent,
),
),
);
},
);
}),
),
// 已完成/未完成统计
Obx(() => Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"未完成: ${controller.pending}",
style: const TextStyle(fontSize: 16),
),
const SizedBox(width: 24),
Text(
"已完成: ${controller.completed}",
style: const TextStyle(fontSize: 16, color: Colors.green),
),
],
),
)),
],
),
),
),
);
}
}
全局状态管理
这个时候只需要在对应的widegt中
final LoginController loginController = Get.find();
Demo
路由管理
getX 同样能够进行路由管理,路由管理就是对 app 的所有的页面进行一个管理,包含路由跳转的行为,路由守卫的拦截。
路由表的注册
// lib/routes/app_pages.dart
import 'package:get/get.dart';
import 'package:op_flutter/pages/State/StateWhat/state_what.dart';
import 'package:op_flutter/pages/chatRoom/chart_room.dart';
import 'package:op_flutter/pages/home/home_page.dart';
import 'package:op_flutter/pages/requestDemo/request_demo.dart';
import 'package:op_flutter/pages/routeDemo/route_demo.dart';
import 'package:op_flutter/pages/routeDemo/route_guard.dart';
import '../pages/login/login_page.dart';
/// 所有路由路径定义
class AppRoutes {
static const login = '/login';
static const home = '/home';
static const demo = '/stateDemo';
// 状态管理模块
static const stateWhat = '/state/what';
static const stateHow = '/state/how';
// 路由管理模块
static const routerWhat = '/router/what';
static const routerHow = '/router/how';
static const routerGuard = '/guard';
// 网络请求模块
static const networkWhat = '/network/what';
static const sdkAbility = '/sdk';
}
/// 所有路由页面配置
class AppPages {
static final routes = [
GetPage(
name: AppRoutes.login, // 路由路径
page: () => LoginPage(), // 对应页面
),
GetPage(
name: AppRoutes.home, // 路由路径
page: () => HomePage(), // 对应页面
),
GetPage(name: AppRoutes.stateWhat, page: () => StateWhat()),
GetPage(name: AppRoutes.routerWhat, page: () => RouteShowcasePage()),
GetPage(name: AppRoutes.routerGuard, page: () => TodoSummaryPage(), middlewares: [TodoGuard()]),
GetPage(name: AppRoutes.networkWhat, page: () => RequestDemoPage()),
GetPage(name: AppRoutes.sdkAbility, page: () => ChatRoomPage())
];
}
路由表注入
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Login Demo',
theme: ThemeData(
scaffoldBackgroundColor: Colors.white, // 全局白色背景
primarySwatch: Colors.blue, // 可选,全局主题色
),
initialRoute: AppRoutes.login, // 默认启动页
getPages: AppPages.routes, // 路由表
);
}
}
那么现在就可以控制路由的跳转了。 跳转的方法和 vue-router 用法类似。
跳转方法
方法 路由栈行为 是否可传参数 异步返回
Get.to() push ✅ ✅
Get.off() 替换当前 ✅ ✅
Get.offAll() 清空栈 ✅ ✅
Get.toNamed() push ✅ ✅
Get.offNamed() 替换当前 ✅ ✅
Get.offAllNamed() 清空栈 ✅ ✅
Get.back() pop ✅ ✅
如何携带参数进行跳转
方式 使用方法 特点
arguments Get.to(() => Page(), arguments: {...}) 传任意对象,灵活
路径参数 Get.toNamed('/page/:id') URL 风格,方便 Deep Link / Web
GetX 支持两种主要传参方式:
- 直接传对象参数(arguments)
- 通过路径参数(parameters)
跳转传参
✅ 方式 1:使用 arguments
📦 适合传递对象、列表、Map 等复杂数据。
跳转时传参:
// 跳转并传递参数
Get.to(DetailPage(), arguments: {'id': 1, 'title': '任务详情'});
或使用命名路由:
Get.toNamed('/detail', arguments: {'id': 1, 'title': '任务详情'});
接收参数:
class DetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 获取传入的参数
final args = Get.arguments;
final id = args['id'];
final title = args['title'];
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(child: Text('当前任务ID:$id')),
);
}
}
✅ 方式 2:使用 parameters(URL 参数)
📎 适合传递简单字符串(GetX 会将它解析为 Map<String, String>)
跳转时传参:
Get.toNamed('/detail?id=123&title=任务详情');
或:
Get.toNamed('/detail', parameters: {'id': '123', 'title': '任务详情'});
接收参数:
class DetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final id = Get.parameters['id'];
final title = Get.parameters['title'];
return Scaffold(
appBar: AppBar(title: Text(title ?? '无标题')),
body: Center(child: Text('任务ID: $id')),
);
}
}
✅ 方式 3:带返回值的跳转(异步返回)
📬 用于跳转后返回结果,例如编辑页返回更新结果。
👉 发起跳转:
void goToEditPage() async {
final result = await Get.to(() => EditPage(), arguments: {'text': '初始内容'});
print('从编辑页返回: $result');
}
👉 在目标页返回结果:
ElevatedButton(
onPressed: () {
Get.back(result: '编辑完成');
},
child: const Text('返回结果'),
)
✅ 方式 4:在 GetPage 定义中接收参数
如果你使用命名路由配置:
GetPage(
name: '/detail',
page: () => DetailPage(),
)
那么无论是:
Get.toNamed('/detail', arguments: {...});
还是:
Get.toNamed('/detail?id=1');
都可以在 DetailPage 中通过:
Get.arguments // 对象参数
Get.parameters // URL 参数
来获取。
路由守卫怎么使用
/// 路由守卫:当 Todo 列表为空时禁止进入目标页
class TodoGuard extends GetMiddleware {
@override
RouteSettings? redirect(String? route) {
final todoCtrl = Get.find<TodoController>();
if (todoCtrl.todos.isEmpty) {
// 显示提示
Future.microtask(() {
Get.snackbar(
'访问受限',
'请先添加至少一个 Todo 项!',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.redAccent.withOpacity(0.8),
colorText: Colors.white,
margin: const EdgeInsets.all(12),
);
});
return const RouteSettings(name: AppRoutes.routerWhat); // 返回当前页
}
return null; // 允许访问
}
}
Demo
网络请求
网络请求类封装
import 'package:dio/dio.dart';
class DioManager {
static final DioManager _instance = DioManager._internal();
factory DioManager() => _instance;
late Dio dio;
DioManager._internal() {
BaseOptions options = BaseOptions(
baseUrl: 'https://jsonplaceholder.typicode.com/', // 全局 baseUrl
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
headers: {
'Content-Type': 'application/json',
},
);
dio = Dio(options);
// 添加拦截器
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
print('📤 请求: ${options.method} ${options.path}');
print('请求参数: ${options.data}');
return handler.next(options);
},
onResponse: (response, handler) {
print('📥 响应: ${response.data}');
return handler.next(response);
},
onError: (DioError e, handler) {
print('❌ 错误: ${e.message}');
return handler.next(e);
},
),
);
}
/// GET 请求
Future<Response> get(String path,
{Map<String, dynamic>? queryParameters, CancelToken? cancelToken}) async {
try {
Response response = await dio.get(
path,
queryParameters: queryParameters,
cancelToken: cancelToken,
);
return response;
} on DioError catch (e) {
throw _handleError(e);
}
}
/// POST 请求
Future<Response> post(String path,
{dynamic data, Map<String, dynamic>? queryParameters, CancelToken? cancelToken}) async {
try {
Response response = await dio.post(
path,
data: data,
queryParameters: queryParameters,
cancelToken: cancelToken,
);
return response;
} on DioError catch (e) {
throw _handleError(e);
}
}
/// 错误处理
Exception _handleError(DioError e) {
switch (e.type) {
case DioErrorType.connectionTimeout:
return Exception('连接超时');
case DioErrorType.receiveTimeout:
return Exception('接收超时');
case DioErrorType.badResponse:
return Exception('服务器错误: ${e.response?.statusCode}');
case DioErrorType.cancel:
return Exception('请求取消');
default:
return Exception('未知错误: ${e.message}');
}
}
}
Demo
扩展能力
Flutter 本质上只是一个 UI 框架,运行在宿主平台之上,Flutter 本身是无法提供一些系统能力,比如使用蓝牙、相机、GPS等,因此要在 Flutter 中调用这些能力就必须和原生平台进行通信。目前Flutter 已经支持 iOS、Android、Web、macOS、Windows、Linux等众多平台,要调用特定平台 API 就需要写插件。插件是一种特殊的包,和纯 dart 包主要区别是插件中除了dart代码,还包括特定平台的代码,比如 image_picker 插件可以在 iOS 和 Android 设备上访问相册和摄像头。
🧩 一、直接调用原生的api
Flutter 本身只提供了一部分跨平台功能(UI、网络、存储等),
但很多功能是 Flutter 无法直接实现的,比如:
- 蓝牙通信
- NFC、相机、麦克风
- 打印机(你上面的例子)
- 系统通知、定位、传感器
- 第三方 SDK(支付宝、微信、谷歌登录等)
这些能力只有 Android(Java/Kotlin) 或 iOS(Objective-C/Swift) 才能访问。
所以 —— Flutter 提供了一套「桥接机制」,
让 Dart 可以“打电话”给原生代码去执行这些功能。
package com.example.my_app
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import com.pax.dal.IPrinter
import com.pax.neptunelite.api.NeptuneLiteUser
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.my_app/printer"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
setupPrinterChannel(flutterEngine)
}
/** 注册 Printer 通道 */
private fun setupPrinterChannel(flutterEngine: FlutterEngine) {
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
when (call.method) {
"printStr" -> handlePrint(call.arguments as? Map<String, Any>?, result)
else -> result.notImplemented()
}
}
}
/** 处理打印逻辑 */
private fun handlePrint(arguments: Map<String, Any>?, result: MethodChannel.Result) {
val text = arguments?.get("text") as? String ?: ""
val param = arguments?.get("param") as? String
try {
val printer = getPrinter()
printer.init()
printer.printStr(text, param)
val status = printer.start()
result.success("✅ 打印成功,状态: $status")
} catch (e: Exception) {
result.error("PRINT_ERROR", "❌ 打印失败: ${e.message}", null)
}
}
/** 获取 IPrinter 实例 */
private fun getPrinter(): IPrinter {
val neptuneLiteUser = NeptuneLiteUser.getInstance()
val dal = neptuneLiteUser.getDal(this)
?: throw IllegalStateException("无法获取 DAL 实例")
return dal.printer ?: throw IllegalStateException("无法获取打印机实例")
}
}
🔁 MethodChannel 通信原理(底层机制)
这部分非常关键👇
1️⃣ Flutter 与原生之间有一条“消息通道”
Dart <——> Platform Channel <——> Android/iOS 原生
消息是通过 binary message(字节流) 传输的,
Flutter Engine 底层通过 PlatformDispatcher 处理。
2️⃣ 调用过程分为两边:
▶ Dart 侧(调用方)
static const platform = MethodChannel('com.example.my_app/printer');
final result = await platform.invokeMethod('printStr', {'text': 'Hello'});
👉 Flutter 会通过 “binaryMessenger” 发送一条消息,
内容是:
{
"method": "printStr",
"arguments": {"text": "Hello"}
}
▶ 原生侧(接收方)
Android(Kotlin)注册监听:
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.my_app/printer")
.setMethodCallHandler { call, result ->
if (call.method == "printStr") {
val text = call.argument<String>("text")
printer.printText(text)
result.success("OK")
}
}
iOS(Swift)注册监听:
let channel = FlutterMethodChannel(name: "com.example.my_app/printer", binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler { (call, result) in
if call.method == "printStr" {
let text = (call.arguments as? [String: Any])?["text"]
printer.print(text)
result("OK")
}
}
🧠 底层原理总结图
┌──────────────────────┐
│ Flutter (Dart) │
│ MethodChannel.invokeMethod() │
└────────────┬────────────┘
│ 通过 BinaryMessenger 发送二进制消息
┌────────────┴────────────┐
│ Flutter Engine │
│ (C++ 层桥接中转机制) │
└────────────┬────────────┘
│
┌────────────┴────────────┐
│ Android/iOS 原生平台层 │
│ setMethodCallHandler() │
└─────────────────────────┘