Flutter

技术栈
前端框架
mobilecross-platformdartgoogleui-framework

概览

Flutter 技术栈概览

Flutter 是 Google 于 2017 年发布的跨平台 UI 框架,使用 Dart 语言,通过自绘 Skia/Impeller 引擎渲染界面,实现 iOS、Android、Web、Desktop 一致体验。不同于 React Native 桥接原生控件,Flutter 直接控制像素,消除了平台差异带来的不一致。

解决什么问题

  • 多端开发成本高 → 一套代码跑 iOS + Android + Web + Desktop
  • 原生控件不一致 → 自绘引擎保证像素级一致
  • 热重载开发效率 → 亚秒级 Stateful Hot Reload
  • 复杂动画/自定义 UI → 内置动画系统 + CustomPainter

关键特性

  • Dart 语言:AOT 编译保证发布性能,JIT 编译支持热重载
  • Widget 体系:一切皆 Widget,组合式构建 UI
  • 声明式 UIbuild() 返回 Widget 树,状态驱动
  • Skia/Impeller 渲染:不依赖 OEM 控件,自绘每一帧
  • 热重载(Hot Reload):修改代码后亚秒级刷新,状态保留
  • Platform Channels:与原生代码双向通信
  • 丰富的动画框架:Tween、Hero、AnimationController
  • Material + Cupertino:双设计语言开箱即用

安装

环境准备

  • OS:macOS(可编译 iOS+Android)、Windows/Linux(仅 Android)
  • 磁盘空间:至少 15GB(SDK + 工具链 + 模拟器)
  • Git:用于拉取 Flutter SDK

安装命令

macOS / Linux

# 1. 下载 Flutter SDK
git clone https://github.com/flutter/flutter.git -b stable ~/flutter

# 2. 添加到 PATH(写入 .zshrc 或 .bashrc)
export PATH="$HOME/flutter/bin:$PATH"

# 3. 自动检查缺失依赖
flutter doctor

# 4. 接受 Android 许可(若报错)
flutter doctor --android-licenses

Windows

# 推荐方式:choco 或 winget
winget install Google.Flutter

# 或手动:下载 zip 解压到 C:\flutter,添加 bin 到 PATH

验证安装

flutter --version
flutter doctor -v    # 详细诊断

flutter doctor 会检查:Flutter SDK、Android SDK / Xcode、Chrome、IDE 插件等。

创建第一个项目

flutter create my_app
cd my_app
flutter run             # 自动检测手机/模拟器/Chrome

常见安装问题

Q1: flutter doctor 报 Android SDK 缺失

安装 Android Studio → SDK Manager → 安装 Android SDK Platform + SDK Build-Tools。

Q2: macOS 报 cocoapods not installed

sudo gem install cocoapods

Q3: Windows 报 cmdline-tools component is missing

Android Studio → Settings → Appearance → System Settings → Android SDK → SDK Tools → 勾选 Android SDK Command-line Tools。

Q4: 国内网络慢

设置镜像:export PUB_HOSTED_URL=https://pub.flutter-io.cn + export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

示例

Flutter 例程:经典计数器——理解 StatefulWidget

目标

通过官方经典的计数器例子,理解 Flutter 核心概念:Widget 树、StatefulWidget、setState、MaterialApp。

完整代码

// lib/main.dart
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: 'Flutter Demo',
      theme: ThemeData(
        colorSchemeSeed: Colors.blue,
        useMaterial3: true,
      ),
      home: const MyHomePage(title: '计数器 Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;

  const MyHomePage({super.key, required this.title});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  void _decrementCounter() {
    setState(() {
      if (_counter > 0) _counter--;
    });
  }

  void _resetCounter() {
    setState(() {
      _counter = 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        centerTitle: true,
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _resetCounter,
            tooltip: '重置',
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              '你已经点了这么多次:',
              style: TextStyle(fontSize: 18),
            ),
            const SizedBox(height: 12),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.displayLarge?.copyWith(
                    fontWeight: FontWeight.bold,
                    color: _counter > 10 ? Colors.red : null,
                  ),
            ),
            const SizedBox(height: 24),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                FloatingActionButton(
                  onPressed: _decrementCounter,
                  child: const Icon(Icons.remove),
                ),
                const SizedBox(width: 16),
                FloatingActionButton(
                  onPressed: _incrementCounter,
                  child: const Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

运行步骤

flutter create flutter_counter
# 将上面代码覆盖 lib/main.dart
flutter run

预期表现

  • 点击 + 按钮计数器加一
  • 点击 - 按钮计数器减一(最小为 0)
  • 点击 AppBar 刷新图标重置计数器
  • 计数器超过 10 时数字变为红色

核心概念

概念 说明
StatelessWidget 无状态组件,属性不变
StatefulWidget 有状态组件,setState() 触发重建
setState() 通知框架状态已变,需要重新 build
MaterialApp Material Design 根组件
Scaffold 页面脚手架(含 AppBar、body、FAB)

教程

Flutter 跨平台开发入门教程

第一章:Flutter 架构解析

Flutter 应用分为三层:

┌─────────────┐
│  Dart 代码   │  ← Widget / State / 业务逻辑
├─────────────┤
│  Engine     │  ← C++:Skia/Impeller 渲染、Dart VM
├─────────────┤
│  Embedder   │  ← 平台胶水层:Surface、纹理、输入事件
└─────────────┘

关键区别:Flutter 不调用 OEM 控件,而是直接用 Skia/Impeller 画布渲染每个像素。这带来跨平台像素级一致性,但也意味着包体中包含整个渲染引擎。

第二章:Widget 即一切

Flutter 世界只有一个核心概念:Widget

  • 按钮是 Widget
  • 布局(Row/Column/Stack)是 Widget
  • 主题是 Widget
  • 手势检测是 Widget

组合优于继承

// 一个自定义搜索框 = 组合多个基础 Widget
Row(
  children: [
    Icon(Icons.search),
    SizedBox(width: 8),
    Text('搜索...'),
    Spacer(),
    IconButton(icon: Icon(Icons.clear), onPressed: () {}),
  ],
)

第三章:State 管理三阶段

阶段一:setState(入门)

简单直接,适合表单、按钮状态等局部场景。缺点:状态分散,难以跨组件共享。

阶段二:InheritedWidget / Provider(进阶)

官方推荐的中型项目方案,依赖注入 + ChangeNotifier。

阶段三:Bloc / Riverpod / Redux(大型项目)

响应式、可测试、与 UI 解耦的状态管理。

第四章:常用 Widget 速查

类别 Widget
布局 Row, Column, Stack, Expanded, Flexible
容器 Container, Padding, Center, SizedBox, DecoratedBox
列表 ListView, GridView, SliverList
文本 Text, RichText, TextField
按钮 ElevatedButton, TextButton, IconButton, FloatingActionButton
导航 Navigator, BottomNavigationBar, TabBar
图片 Image.network, Image.asset, FadeInImage

第五章:网络请求与 JSON

import 'dart:convert';
import 'package:http/http.dart' as http;

Future<List<User>> fetchUsers() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/users'),
  );
  if (response.statusCode == 200) {
    final List<dynamic> jsonList = jsonDecode(response.body);
    return jsonList.map((j) => User.fromJson(j)).toList();
  }
  throw Exception('请求失败');
}

思考题

  1. Flutter 与 React Native 在渲染机制上的核心区别是什么?各有什么优劣?
  2. StatefulWidget 的 createState() 为何在多次重建中只调用一次?
  3. Widget、Element、RenderObject 三棵树的关系是什么?
  4. Flutter 如何处理不同屏幕尺寸的适配?

参考资料

暂无参考文献