CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Flutter 开发入门/课时/Widget 体系
RawBlame
课时目录12 节

Flutter 开发入门

  1. 01Flutter 环境
  2. 02Dart 语言
  3. 03Widget 体系
  4. 04架构原则
  5. 05状态管理
  6. 06动画
  7. 07网络与本地存储
  8. 08Firebase 集成
  9. 09测试
  10. 10CI/CD 与发布
  11. 11分析与监控
  12. 12综合练习

Widget 体系

Material/Cupertino 与资源。

30 分钟

Widget 体系

这一课你将能做到

  1. 区分 StatelessWidget 和 StatefulWidget,知道什么时候用哪个
  2. 熟练组合 Text / Image / Container / Row / Column / ListView 等基础 Widget 搭出任意 UI
  3. 理解 Key 的作用,能解决「列表动画错乱」「状态错位」等经典问题

为什么要学

Flutter 里一切都是 Widget——按钮是 Widget、内边距是 Widget、整个 App 也是 Widget。Widget 写不对,UI 一定搭不出来。

人话:Widget 就是乐高积木。小积木(Text、Icon)拼出大积木(Card、AppBar),大积木再拼出整个房子(Scaffold)。

先建立图像

MaterialApp
 └── Scaffold
      ├── AppBar(title: Text('首页'))
      ├── body: Column
      │    ├── Image.network(...)
      │    ├── Row
      │    │    ├── Icon(Icons.star)
      │    │    └── Text('评分 4.9')
      │    └── ElevatedButton(...)
      └── FloatingActionButton(...)

每个节点都是一个 Widget,Widget 嵌套 Widget。

跟着做

步骤 1:最小可运行骨架

新建 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: 'Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Widget 体系')),
      body: const Center(child: Text('Hello Widget')),
    );
  }
}

flutter run 即可看到一个带标题栏和居中文字的页面。

步骤 2:StatelessWidget vs StatefulWidget

StatelessWidget:只依赖构造参数,build 出来的内容不变。

class Greeting extends StatelessWidget {
  final String name;
  const Greeting({super.key, required this.name});

  @override
  Widget build(BuildContext context) => Text('你好, $name');
}

StatefulWidget:自己有可变状态(counter、表单输入、动画值)。

class Counter extends StatefulWidget {
  const Counter({super.key});
  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  void _inc() => setState(() => _count++);   // 改状态必须走 setState

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _inc,
      child: Text('点了 $_count 次'),
    );
  }
}

经验:默认 Stateless,真的需要内部状态再升级成 Stateful。

步骤 3:常用基础 Widget

// 文本
Text('大标题', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold));

// 图片
Image.network('https://picsum.photos/200');
Image.asset('assets/logo.png');    // 需在 pubspec.yaml 注册

// 容器(盒模型:padding/margin/color/圆角/装饰)
Container(
  margin: const EdgeInsets.all(16),
  padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
  decoration: BoxDecoration(
    color: Colors.blue.shade50,
    borderRadius: BorderRadius.circular(12),
    boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 4)],
  ),
  child: const Text('卡片'),
);

// 图标 + 按钮
Icon(Icons.favorite, color: Colors.red);
ElevatedButton(onPressed: () {}, child: const Text('确认'));
TextButton(onPressed: () {}, child: const Text('取消'));

步骤 4:布局三剑客 Row / Column / Expanded

class ProfileRow extends StatelessWidget {
  const ProfileRow({super.key});
  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        const CircleAvatar(radius: 28, child: Text('A')),
        const SizedBox(width: 12),
        Expanded(                                  // 占满剩余宽度
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: const [
              Text('Ada', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              Text('Flutter 工程师', style: TextStyle(color: Colors.grey)),
            ],
          ),
        ),
        const Icon(Icons.chevron_right),
      ],
    );
  }
}

关键概念:

  • Row 主轴水平,Column 主轴垂直;mainAxisAlignment 控制主轴,crossAxisAlignment 控制交叉轴。
  • Expanded / Flexible:分配剩余空间,否则 Row 内容超出会黄黑条溢出。

步骤 5:ListView 长列表

class TodoList extends StatelessWidget {
  final List<String> items;
  const TodoList({super.key, required this.items});

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (ctx, i) => ListTile(
        key: ValueKey(items[i]),                  // 稳定 Key
        leading: const Icon(Icons.check_circle_outline),
        title: Text(items[i]),
      ),
    );
  }
}

规则:列表项超过一屏就用 ListView.builder(懒加载),不要用 ListView(children: [...]) 一次性渲染。

步骤 6:Key 解决「状态错乱」

反例:没有 Key 的可重排序 StatefulWidget 列表:

class BadList extends StatefulWidget {
  const BadList({super.key});
  @override
  State<BadList> createState() => _BadListState();
}

class _BadListState extends State<BadList> {
  final _items = ['A', 'B', 'C'];

  void _shuffle() => setState(_items.shuffle);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(onPressed: _shuffle, child: const Text('打乱')),
        for (var e in _items)
          StatefulColorTile(key: ValueKey(e), label: e),  // 关键
      ],
    );
  }
}

class StatefulColorTile extends StatefulWidget {
  final String label;
  const StatefulColorTile({super.key, required this.label});
  @override
  State<StatefulColorTile> createState() => _StatefulColorTileState();
}

class _StatefulColorTileState extends State<StatefulColorTile> {
  late final Color _color =
      Colors.primaries[widget.label.hashCode % Colors.primaries.length];

  @override
  Widget build(BuildContext context) =>
      Container(color: _color, padding: const EdgeInsets.all(16), child: Text(widget.label));
}

把 key: ValueKey(e) 删掉再点「打乱」,会发现颜色不跟字母走 —— 这就是 Key 缺失导致的状态错位。

讲清楚要点

  1. Widget 是配置,不可变:字段必须 final。要变就建一个新 Widget 替换旧的,Flutter 做 diff。
  2. build() 必须「纯净」:无副作用、无网络请求、快。网络请求放 initState / FutureBuilder / Bloc。
  3. State 三件套:initState(一次性初始化)、build(每次重建)、dispose(释放资源,比如 controller)。
  4. Key 决定 Element 是否复用:列表删除/排序/动画,给 StatefulWidget 一个稳定的 ValueKey/ObjectKey。
  5. const 越多越好:能 const 的构造都 const,避免无谓 rebuild。

你可能会懵的地方

  • Padding 居然是个 Widget? Flutter 没有「属性」的概念,所有视觉/布局都是 Widget。Padding(padding:..., child: ...) 是个真实存在的节点。

  • 为什么 Image 加载不出来? Image.asset 必须在 pubspec.yaml 里声明路径:flutter: assets: - assets/。改完要完全重启(不是热重载)。

  • Column 里再嵌 ListView 报错? 两个都无限高。给 ListView 套 Expanded 或 SizedBox(height: ...)。

  • setState 之后 UI 没刷新? 检查是不是在 build 里直接改字段,或在 dispose 之后调用;也可能是改的字段没参与 build。

  • Material 和 Cupertino 怎么选? MaterialApp 是默认推荐,跨平台一致;要做 iOS 原生风格才用 CupertinoApp。生产项目基本用 Material。

错法对照

错法问题正解
在 StatelessWidget 里写字段并直接改UI 不更新用 StatefulWidget + setState
setState 里跑耗时操作阻塞 UIsetState 只改数据,逻辑放外面
Column 直接放 ListView高度无限冲突Expanded(child: ListView(...))
给所有 Widget 都加 GlobalKey性能差且难维护局部用 ValueKey/ObjectKey,GlobalKey 仅在跨树共享状态时用
在 build 里发网络请求每次 rebuild 都发一次initState 发一次,或 FutureBuilder 包 Future
用 MediaQuery.of(context).size 写死布局不响应屏幕变化用 LayoutBuilder / Expanded

卡点急救

  1. 黄黑溢出条纹(RenderFlex overflowed):子组件超宽,套 Expanded 或 Flexible;或加滚动 SingleChildScrollView。
  2. 图片 asset 404:检查 pubspec.yaml 是否声明,注意缩进;改完要 flutter clean && flutter run。
  3. setState called during build:把调用挪到按钮回调/WidgetsBinding.instance.addPostFrameCallback。
  4. 键盘弹出导致 bottom overflow:Scaffold(resizeToAvoidBottomInset: false) 或包 SingleChildScrollView。
  5. Stateful 列表删除后状态错位:每项加 key: ValueKey(item.id)。

面试常问

  • StatelessWidget 和 StatefulWidget 区别? 前者配置不可变,无内部状态;后者有 State 对象,可跨 rebuild 保留数据,setState 触发重建。

  • build() 会被调用多少次? 任意次、随时可能被调用。所以 build 必须纯净无副作用,依赖于此才能高效重建。

  • Key 的作用? 在 Widget 树更新时帮助 Flutter 判断「旧 Element 是否可以复用」。相同位置相同类型 + 相同 Key → 复用;不同 Key → 销毁重建。

  • GlobalKey 用在哪? 跨 Widget 树访问 State(如 FormState.validate()),或在树的不同位置移动同一个 Widget 保留状态。代价高,能不用就不用。

自测三问

  1. 为什么 Flutter 没有 view.setPadding(16) 这种调用?它是怎么处理 padding 的?
  2. 写一段代码:一个计数器按钮,点击数字 +1。
  3. 给一个可重排序的 StatefulWidget 列表加 Key,应该选哪种 Key?为什么?

小结一句话

Widget 就是不可变的 UI 配置,Stateless 用来展示、Stateful 用来持状态,合理组合 + 正确使用 Key 是 Flutter UI 的全部基石。

延伸

  • Flutter Widget 目录:<https://docs.flutter.dev/ui/widgets>
  • Widget of the Week 短视频(官方):搜索 "Flutter Widget of the Week"
  • 下一课:架构原则,理解 Widget/Element/RenderObject 三棵树,为性能优化打底。
加载本节练习…
← 上一课Dart 语言下一课 →架构原则