本文首发于技术博客 · 作者: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 使用 Futureasync / 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'),
    );
  }
}

关键区别StatelessWidgetbuild 只执行一次(或父组件重建时);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 + GoRouterBeamer 进行声明式路由管理。以 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 —— 最基础的方式

setStateStatefulWidget 自带的更新机制,适合管理组件内部的简单状态。

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 串联所有知识点。

学习路线建议

  1. 巩固基础 —— 多写 StatelessWidgetStatefulWidget,熟悉布局组件搭配
  2. 理解状态管理 —— 从 setStateProvider 再到 Riverpod / Bloc
  3. 深入 Widget 生命周期 —— 掌握 initStatedidUpdateWidgetdispose
  4. 实战项目 —— 尝试做一个天气 App 或记账本 App
  5. 性能优化 —— 学习 const 构造函数、RepaintBoundary、列表性能优化
  6. 平台集成 —— 使用 platform channels 调用原生功能

Flutter 生态正在飞速成长,无论你是移动端开发者还是前端开发者,现在都是学习 Flutter 的最佳时机。


如果这篇文章对你有帮助,欢迎点赞收藏,后续还会带来 Flutter 进阶系列文章!