本文首发于技术博客 · 作者:LanMei · 2026年6月
一、Flutter 简介:为什么选择 Flutter
Flutter 是 Google 开源的新一代跨平台 UI 框架,使用 Dart 语言开发,一套代码同时编译出 Android、iOS、Web、桌面端(Windows / macOS / Linux)的原生应用。它不依赖浏览器或 WebView,而是直接调用 Skia 图形引擎 进行自绘制,因此性能和体验都接近原生。
Flutter vs React Native
| 维度 | Flutter | React Native |
|---|---|---|
| 渲染方式 | Skia 自绘,不依赖平台控件 | 桥接原生控件 |
| 语言 | Dart(编译型、AOT 支持) | JavaScript(解释型) |
| 性能 | 接近原生,60fps 流畅 | 桥接通信有损耗 |
| 组件库 | Material Design / Cupertino 内置 | 需要第三方或自定义 |
| Hot Reload | 支持,毫秒级生效 | 支持,但部分场景需重编译 |
| 入门曲线 | 需学习 Dart,但体系一致 | 前端开发者上手快 |
一句话总结:如果你追求一致的高品质 UI 体验和原生级性能,Flutter 是当前跨平台开发的最佳选择之一。
二、Dart 语言基础
2.1 类型系统
Dart 是 强类型语言,但支持类型推断。
// 显式类型
String name = 'Flutter';
int age = 7;
double version = 3.27;
bool isStable = true;
// 类型推断(var / final / const)
var framework = 'Flutter'; // 推断为 String
final platform = 'Android'; // 运行时常量
const pi = 3.14159; // 编译期常量
// 集合类型
List<String> items = ['苹果', '香蕉', '橘子'];
Map<String, int> scores = {'张三': 95, '李四': 88};
Set<int> uniqueIds = {1, 2, 3};
2.2 空安全(Null Safety)
Dart 的 空安全 在编译期杜绝空指针异常,是语言的核心理念。
// ? 表示可为空
String? nullableName;
nullableName = null; // ✅ 允许
// ! 表示非空断言
void printLength(String? text) {
print(text!.length); // 运行时断言 —— 请确保不为 null
}
// ?? 提供默认值
String displayName = nullableName ?? '默认用户';
// ?. 安全调用
int? len = nullableName?.length; // 为 null 时不调用,返回 null
// late 延迟初始化
late String lazyValue = _computeExpensive();
2.3 异步编程
Dart 使用 Future 和 async / await 处理异步,与 JavaScript 非常相似。
// 模拟网络请求
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return '数据加载完成';
}
// 调用
void load() async {
print('加载中...');
String result = await fetchData();
print(result);
}
// 并发 —— 同时发起多个请求
Future<void> loadAll() async {
var future1 = fetchData();
var future2 = fetchData();
var results = await Future.wait([future1, future2]);
}
Dart 还支持
Stream(事件流)和Isolate(多线程),本文不做展开。
三、Widget 体系
在 Flutter 中,一切皆 Widget。无论是结构、样式还是布局,都由 Widget 组合而成。
3.1 StatelessWidget(无状态组件)
当组件的展示内容完全由外部传入的参数决定、内部没有可变状态时,使用 StatelessWidget。
class GreetingTile extends StatelessWidget {
final String userName;
const GreetingTile({super.key, required this.userName});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
child: Text('你好,$userName!👋'),
);
}
}
3.2 StatefulWidget(有状态组件)
当组件需要维护可变状态(如输入框内容、计数器),用 StatefulWidget + State。
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _increment,
child: Text('点击次数:$_count'),
);
}
}
关键区别:
StatelessWidget的build只执行一次(或父组件重建时);StatefulWidget通过setState()触发build重新执行,从而更新 UI。
四、常用布局组件
4.1 Container(容器)
最常用的装饰/约束容器,支持 padding、margin、背景色、圆角、边框等。
Container(
width: 200,
height: 100,
margin: EdgeInsets.all(8),
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(color: Colors.black26, blurRadius: 4),
],
),
child: Text('Hello', style: TextStyle(color: Colors.white)),
)
4.2 Row / Column(线性布局)
Row 水平排列子组件,Column 垂直排列。
Column(
mainAxisAlignment: MainAxisAlignment.center, // 主轴对齐
crossAxisAlignment: CrossAxisAlignment.start, // 交叉轴对齐
children: [
Text('标题', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Row(
children: [
Icon(Icons.star, color: Colors.amber),
SizedBox(width: 4),
Text('4.8 分'),
],
),
],
)
4.3 Stack(层叠布局)
子组件可以叠加放置,常用 Positioned 定位。
Stack(
children: [
Container(
width: 300,
height: 200,
color: Colors.grey[200],
),
Positioned(
top: 10,
right: 10,
child: CircleAvatar(
radius: 24,
backgroundImage: NetworkImage('https://example.com/avatar.png'),
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
height: 40,
color: Colors.black45,
child: Center(child: Text('底部标签', style: TextStyle(color: Colors.white))),
),
),
],
)
4.4 ListView(列表)
滚动列表 —— 最常用的组件之一。
// 简单列表
ListView(
children: [
ListTile(title: Text('条目 1')),
ListTile(title: Text('条目 2')),
ListTile(title: Text('条目 3')),
],
)
// 动态长列表(按需构建,高性能)
ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(child: Text('$index')),
title: Text('第 $index 项'),
);
},
)
4.5 GridView(网格)
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
itemCount: 20,
itemBuilder: (context, index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length],
child: Center(child: Text('$index')),
);
},
)
五、页面导航与路由管理
5.1 基本导航(Navigator 1.0 风格)
// 跳转到新页面
Navigator.push(
context,
MaterialPageRoute(builder: (_) => DetailPage(id: 42)),
);
// 返回上一页并传值
Navigator.pop(context, '返回的数据');
// 接收返回值
final result = await Navigator.push<String>(
context,
MaterialPageRoute(builder: (_) => DetailPage()),
);
print(result); // '返回的数据'
5.2 命名路由
// 在 MaterialApp 中注册
MaterialApp(
initialRoute: '/',
routes: {
'/': (_) => HomePage(),
'/detail': (_) => DetailPage(),
},
)
// 跳转
Navigator.pushNamed(context, '/detail', arguments: {'id': 42});
// 在 DetailPage 中接收
final args = ModalRoute.of(context)!.settings.arguments as Map;
5.3 Navigator 2.0 与 Router API
Flutter 3.7+ 推荐使用 Router + GoRouter 或 Beamer 进行声明式路由管理。以 go_router 为例:
// 需要 pub add go_router
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(path: '/', builder: (_, __) => HomePage()),
GoRoute(
path: '/detail/:id',
builder: (_, state) => DetailPage(id: state.pathParameters['id']!),
),
],
);
// 使用
MaterialApp.router(
routerConfig: router,
)
// 跳转
context.go('/detail/42');
Navigator 2.0更灵活,支持深度链接、页面栈精细控制,适合中大型项目。
六、状态管理入门
6.1 setState —— 最基础的方式
setState 是 StatefulWidget 自带的更新机制,适合管理组件内部的简单状态。
class TodoInput extends StatefulWidget {
const TodoInput({super.key});
@override
State<TodoInput> createState() => _TodoInputState();
}
class _TodoInputState extends State<TodoInput> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: TextField(controller: _controller),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
final text = _controller.text.trim();
if (text.isNotEmpty) {
setState(() {
// 实际项目中应回调给父组件
});
_controller.clear();
}
},
),
],
);
}
}
6.2 状态提升(Lifting State Up)
当多个组件共享同一状态时,将状态上移到它们的共同父组件,通过回调下传。
class TodoApp extends StatefulWidget {
@override
State<TodoApp> createState() => _TodoAppState();
}
class _TodoAppState extends State<TodoApp> {
final List<String> _todos = [];
void _addTodo(String text) {
setState(() => _todos.add(text));
}
void _removeTodo(int index) {
setState(() => _todos.removeAt(index));
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TodoInput(onAdd: _addTodo), // 下传回调
TodoList(items: _todos, onDelete: _removeTodo), // 下传数据和回调
],
);
}
}
项目规模变大后,建议使用 Provider、Riverpod、Bloc 等状态管理方案,本文不做展开。
七、网络请求
7.1 使用 http 库
// 在 pubspec.yaml 添加:
// dependencies:
// http: ^1.2.0
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<List<dynamic>> fetchTodos() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/todos'),
);
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception('请求失败:${response.statusCode}');
}
}
7.2 使用 dio 库(推荐)
dio 功能更强大,支持拦截器、超时、文件上传、请求取消等。
// pub add dio
import 'package:dio/dio.dart';
final dio = Dio(BaseOptions(
baseUrl: 'https://jsonplaceholder.typicode.com',
connectTimeout: Duration(seconds: 10),
receiveTimeout: Duration(seconds: 10),
));
Future<List<dynamic>> fetchTodos() async {
final response = await dio.get('/todos');
return response.data;
}
// 带拦截器的使用示例
void setupLogger() {
dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
));
}
八、完整示例:待办事项 App
下面我们将以上学到的知识整合起来,构建一个完整的待办事项应用。
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '待办事项',
theme: ThemeData(
primarySwatch: Colors.indigo,
useMaterial3: true,
),
home: const TodoHomePage(),
);
}
}
// ---------- 主页面 ----------
class TodoHomePage extends StatefulWidget {
const TodoHomePage({super.key});
@override
State<TodoHomePage> createState() => _TodoHomePageState();
}
class _TodoHomePageState extends State<TodoHomePage> {
final _textController = TextEditingController();
final List<Map<String, dynamic>> _todos = [];
void _addTodo() {
final text = _textController.text.trim();
if (text.isEmpty) return;
setState(() {
_todos.add({'title': text, 'done': false});
});
_textController.clear();
}
void _toggleTodo(int index) {
setState(() {
_todos[index]['done'] = !_todos[index]['done'];
});
}
void _deleteTodo(int index) {
setState(() {
_todos.removeAt(index);
});
}
int get _completedCount => _todos.where((t) => t['done'] == true).length;
@override
void dispose() {
_textController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('待办事项 ($_completedCount/${_todos.length})'),
),
body: Column(
children: [
// 输入区域
Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: TextField(
controller: _textController,
decoration: InputDecoration(
hintText: '输入新待办...',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _addTodo(),
),
),
SizedBox(width: 12),
FilledButton(
onPressed: _addTodo,
child: Text('添加'),
),
],
),
),
// 列表区域
Expanded(
child: _todos.isEmpty
? Center(
child: Text(
'暂无待办 🎉',
style: TextStyle(fontSize: 18, color: Colors.grey),
),
)
: ListView.builder(
itemCount: _todos.length,
itemBuilder: (context, index) {
final todo = _todos[index];
return Card(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: Checkbox(
value: todo['done'],
onChanged: (_) => _toggleTodo(index),
),
title: Text(
todo['title'],
style: TextStyle(
decoration: todo['done']
? TextDecoration.lineThrough
: null,
),
),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () => _deleteTodo(index),
),
),
);
},
),
),
],
),
);
}
}
运行效果
| 功能 | 说明 |
|---|---|
| 添加待办 | 输入文字后点击"添加"或按回车 |
| 标记完成 | 点击复选框,文字显示删除线 |
| 删除待办 | 点击右侧红色删除图标 |
| 计数统计 | AppBar 显示「已完成数 / 总数」 |
| 空状态 | 无待办时展示友好的提示文案 |
九、总结与下一步
本文从 Flutter 框架定位、Dart 语言基础、Widget 体系、布局组件、导航路由、状态管理到网络请求,最后用一个完整的待办事项 App 串联所有知识点。
学习路线建议
- 巩固基础 —— 多写
StatelessWidget和StatefulWidget,熟悉布局组件搭配 - 理解状态管理 —— 从
setState到Provider再到Riverpod / Bloc - 深入 Widget 生命周期 —— 掌握
initState、didUpdateWidget、dispose - 实战项目 —— 尝试做一个天气 App 或记账本 App
- 性能优化 —— 学习
const构造函数、RepaintBoundary、列表性能优化 - 平台集成 —— 使用
platform channels调用原生功能
Flutter 生态正在飞速成长,无论你是移动端开发者还是前端开发者,现在都是学习 Flutter 的最佳时机。
如果这篇文章对你有帮助,欢迎点赞收藏,后续还会带来 Flutter 进阶系列文章!
评论