在 Flutter 开发中,Widget 是最核心、最基础的概念,几乎所有的界面元素都是由 Widget 构成的。本节课我们将深入理解 Widget 的本质、特性及其在 Flutter 应用中的工作方式,为后续的界面开发打下坚实基础。
一、Widget 是什么?
Widget(组件)是 Flutter 框架中构建用户界面的基本单位,可以理解为屏幕上所有可见元素的抽象描述。从简单的文本、按钮,到复杂的布局、页面,甚至是应用的主题样式,在 Flutter 中都被表示为 Widget。
Widget 不仅仅是视觉元素的描述,它还包含了:
- 界面元素的结构信息
- 布局约束和规则
- 绘制信息
- 交互行为
- 状态管理方式
与传统 UI 组件的区别
Flutter 的 Widget 与传统原生开发或其他跨平台框架的 UI 组件有本质区别:
-
描述性 vs 实例化
- 传统组件:通常是直接实例化的对象,持有实际的渲染状态
- Flutter Widget:是对 UI 的不可变描述,更像是一份 "蓝图" 或 "配置文件"
-
轻量级 vs 重量级
- 传统组件:创建和销毁成本高,包含大量底层渲染逻辑
- Flutter Widget:非常轻量,创建成本低,频繁重建不会显著影响性能
-
组合方式
- 传统组件:通常通过继承扩展功能
- Flutter Widget:几乎完全通过组合而非继承来构建复杂 UI
-
渲染方式
- 传统组件:依赖平台原生控件渲染
- Flutter Widget:由 Flutter 引擎直接渲染,不依赖平台控件
二、不可变 Widget 特性与重建机制
不可变特性(Immutability)
在 Flutter 中,几乎所有 Widget 都是不可变的(immutable),这意味着一旦创建,Widget 的属性(成员变量)就不能再被修改。所有 Widget 的属性都应该被声明为 final
:
dart
import 'package:flutter/material.dart';
class MyWidget extends Widget {
final String title;
final int count;
// 构造函数必须使用 const 或普通构造函数,不允许有修改属性的方法
const MyWidget({super.key, required this.title, required this.count});
@override
Element createElement() {
// TODO: implement createElement
throw UnimplementedError();
}
// ...
}
不可变特性的优势:
- 简化状态管理:Widget 本身不存储可变状态
- 提高性能:Flutter 可以快速比较 Widget 的变化
- 线程安全:不可变对象可以安全地在多线程间传递
- 易于测试和调试:状态变化可追踪
重建机制
由于 Widget 是不可变的,当需要更新 UI 时,Flutter 采用的是重建 Widget 树的方式,而不是修改现有 Widget。
这个过程类似于:
- 当状态变化时,创建新的 Widget 实例(包含新的属性值)
- Flutter 框架对比新旧 Widget 树(这个过程称为 "diffing")
- 只更新实际发生变化的部分到渲染树
热重载(Hot Reload)的原理就基于此:修改代码后,Flutter 会重建 Widget 树,但保留应用状态,从而实现快速预览效果。
StatelessWidget 与 StatefulWidget
根据是否需要管理状态,Widget 分为两大类:
- StatelessWidget(无状态组件)
- 不包含可变状态
- 一旦创建,其外观就完全由构造函数的参数决定
- 适用于静态 UI 元素,如标题、图标等
- 示例:
Text
、Icon
、Container
dart
import 'package:flutter/material.dart';
class MyStatelessWidget extends StatelessWidget {
final String message;
const MyStatelessWidget({super.key, required this.message});
@override
Widget build(BuildContext context) {
return Text(message);
}
}
- StatefulWidget(有状态组件)
- 包含可变状态(State)
- 状态变化会触发 UI 重建
- 适用于需要交互或数据动态变化的场景
- 示例:
TextField
、Checkbox
、ListView
dart
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(onPressed: _increment, child: const Text('Increment')),
],
);
}
}
setState()
方法是状态更新的关键,它会通知 Flutter 框架状态已改变,需要重建 Widget。
三、基础 Widget 介绍
Flutter 提供了丰富的基础 Widget,下面介绍几个最常用的:
1. Container
Container
是一个多功能容器 Widget,类似于 HTML 中的 div
,可以包含一个子 Widget,并为其添加装饰、边距、padding 等。
dart
Container(
width: 200, // 宽度
height: 200, // 高度
margin: const EdgeInsets.all(20), // 外边距
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), // 内边距
decoration: BoxDecoration(
color: Colors.blue, // 背景色
borderRadius: BorderRadius.circular(10), // 圆角
boxShadow: const [ // 阴影
BoxShadow(
color: Colors.grey,
blurRadius: 5,
offset: Offset(2, 2),
),
],
),
child: const Text( // 子Widget
'Hello Container',
style: TextStyle(color: Colors.white, fontSize: 18),
),
alignment: Alignment.center, // 子Widget对齐方式
)
2. Text
Text
用于显示文本,可以通过 TextStyle
配置字体、大小、颜色等样式。
dart
const Text(
'Hello Flutter',
style: TextStyle(
fontSize: 24, // 字体大小
color: Colors.black87, // 颜色
fontWeight: FontWeight.bold, // 字重
fontStyle: FontStyle.italic, // 字体样式
decoration: TextDecoration.underline, // 文本装饰
decorationColor: Colors.red, // 装饰线颜色
letterSpacing: 2, // 字间距
),
textAlign: TextAlign.center, // 对齐方式
maxLines: 2, // 最大行数
overflow: TextOverflow.ellipsis, // 溢出处理
)
3. Image
Image
用于显示图片,支持多种图片来源:
dart
// 从网络加载
Image.network(
'https://picsum.photos/200/300',
width: 200,
height: 300,
fit: BoxFit.cover, // 图片适配方式
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return const Center(child: CircularProgressIndicator());
},
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.error);
},
)
// 从本地资源加载(需要在pubspec.yaml中配置)
Image.asset(
'assets/images/logo.png',
width: 100,
height: 100,
)
// 从文件加载
Image.file(File('/path/to/image.jpg'))
// 从内存加载
Image.memory(Uint8List bytes)
使用本地资源图片需要在 pubspec.yaml
中配置:
yaml
flutter:
assets:
- assets/images/logo.png
- assets/icons/
4. Icon
Icon
用于显示图标,Flutter 内置了 Material Design 图标库,也支持自定义图标。
dart
const Icon(
Icons.favorite, // 图标名称
color: Colors.red, // 颜色
size: 32, // 大小
)
// 使用图标按钮
IconButton(
icon: const Icon(Icons.share),
onPressed: () {
// 点击事件处理
},
tooltip: 'Share', // 长按提示
)
可以通过 pubspec.yaml
添加自定义字体图标:
yaml
flutter:
fonts:
- family: MyIcons
fonts:
- asset: fonts/my_icons.ttf
四、Widget 树结构与嵌套规则
Flutter 应用的界面是由 Widget 嵌套形成的树形结构,称为 "Widget 树"。理解 Widget 树的结构和嵌套规则是掌握 Flutter 布局的关键。
Widget 树结构
一个典型的 Flutter 应用 Widget 树结构如下:
plaintext
MaterialApp
├── Scaffold
│ ├── AppBar
│ ├── Body
│ │ ├── Column
│ │ │ ├── Text
│ │ │ ├── Container
│ │ │ │ └── Image
│ │ │ └── ElevatedButton
│ │ └── ListView
│ │ ├── ListTile
│ │ └── ListTile
│ └── FloatingActionButton
└── Theme
- 根节点通常是
MaterialApp
或CupertinoApp
(提供基础应用框架) - 中间节点通常是布局类 Widget(如
Column
、Row
、Stack
) - 叶子节点通常是展示类 Widget(如
Text
、Image
、Icon
)
嵌套规则
Flutter 采用 "组合优于继承" 的设计理念,通过 Widget 嵌套实现复杂 UI。嵌套时需要遵循以下规则:
- 单一子节点 Widget :只能包含一个子 Widget,如
Container
、Padding
、Center
dart
Container(
child: Text('Single child'), // 只能有一个child
)
- 多子节点 Widget :可以包含多个子 Widget,通常通过
children
属性接收一个 Widget 列表
dart
Column(
children: [ // 多个子Widget放在列表中
Text('First child'),
Text('Second child'),
ElevatedButton(onPressed: () {}, child: Text('Button')),
],
)
-
布局约束传递:父 Widget 会向子 Widget 传递布局约束(最大 / 最小宽高),子 Widget 在约束范围内决定自己的大小
-
上下文(Context)传递 :每个 Widget 都有一个
context
,代表其在 Widget 树中的位置,可用于导航、获取主题等
常见布局 Widget
掌握以下布局 Widget 可以满足大多数界面需求:
- Row:水平排列子 Widget
- Column:垂直排列子 Widget
- Stack:层叠排列子 Widget
- ListView:可滚动的列表
- GridView:网格布局
- Expanded:在 Row/Column 中占满剩余空间
- Padding:添加内边距
- Center:居中对齐子 Widget
- Align:自定义对齐方式
示例:结合使用多种布局 Widget
dart
Column(
children: [
const Text('User Profile', style: TextStyle(fontSize: 20)),
const SizedBox(height: 16), // 间距
Row(
mainAxisAlignment: MainAxisAlignment.center, // 水平居中
children: [
Image.network(
'https://picsum.photos/100/100',
width: 100,
height: 100,
),
const SizedBox(width: 16), // 水平间距
const Column(
crossAxisAlignment: CrossAxisAlignment.start, // 左对齐
children: [
Text('John Doe'),
Text('john@example.com'),
],
),
],
),
const Expanded( // 占满剩余空间
child: Center(
child: Text('Profile details will appear here'),
),
),
],
)
五、Widget 生命周期
虽然 Widget 本身是不可变的,但 StatefulWidget 关联的 State 对象有明确的生命周期:
-
创建阶段
createState()
:创建 State 对象initState()
:初始化状态,只调用一次
-
构建阶段
build()
:构建 Widget 树,每次状态变化都会调用didUpdateWidget()
:当父 Widget 重建导致子 Widget 变化时调用
-
活跃阶段
setState()
:触发状态更新和重建didChangeDependencies()
:当依赖的 InheritedWidget 变化时调用
-
销毁阶段
deactivate()
:Widget 即将从树中移除时调用dispose()
:Widget 被永久移除时调用,用于清理资源
理解生命周期有助于正确管理资源和状态,避免内存泄漏。