2.2 Widget简介
📚 核心知识点
- Widget的概念和特点
- Flutter的三棵树结构
- StatelessWidget 和 StatefulWidget
- BuildContext的作用
- Flutter组件库介绍
💡 核心概念
1. Widget是什么?
Widget是Flutter中对用户界面的描述,是不可变的(immutable)。
关键理解:
- Widget != 真正的UI元素
- Widget只是配置信息(像建筑图纸)
- 真正的渲染由RenderObject完成
scala
// Widget只是描述
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('Hello'); // ← 这只是描述,不是真正的文本元素
}
}
2. Flutter的三棵树
graph LR
A[Widget树
配置信息] --> B[Element树
生命周期] B --> C[RenderObject树
实际渲染] style A fill:#E1F5FE,stroke:#01579B style B fill:#F3E5F5,stroke:#4A148C style C fill:#E8F5E9,stroke:#1B5E20
配置信息] --> B[Element树
生命周期] B --> C[RenderObject树
实际渲染] style A fill:#E1F5FE,stroke:#01579B style B fill:#F3E5F5,stroke:#4A148C style C fill:#E8F5E9,stroke:#1B5E20
| 树 | 作用 | 特点 |
|---|---|---|
| Widget树 | UI的配置信息 | 不可变,频繁重建 |
| Element树 | Widget的实例化,管理生命周期 | 相对稳定,持有Widget和RenderObject |
| RenderObject树 | 实际的布局和绘制 | 负责测量、布局、绘制 |
工作流程:
- 创建Widget树(配置)
- Flutter根据Widget树创建Element树
- Element树创建和管理RenderObject树
- RenderObject树进行实际渲染
3. StatelessWidget vs StatefulWidget
StatelessWidget - 无状态组件
scala
class WelcomeText extends StatelessWidget {
final String name;
const WelcomeText({required this.name});
@override
Widget build(BuildContext context) {
return Text('欢迎, $name');
}
}
特点:
- ✅ 无内部状态
- ✅ 创建后不可变
- ✅ 只有build()方法
- ✅ 性能更好(无状态管理开销)
使用场景:
- 静态文本
- 图标
- 固定布局
- 不需要交互的UI
StatefulWidget - 有状态组件
scala
class Counter extends StatefulWidget {
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () => setState(() => count++),
child: Text('增加'),
),
],
);
}
}
特点:
- ✅ 有内部状态
- ✅ 可以通过setState()更新
- ✅ 有完整的生命周期
- ✅ Widget和State分离
使用场景:
- 表单输入
- 动画
- 用户交互
- 数据会变化的UI
4. BuildContext的作用
BuildContext代表Widget在树中的位置
graph TD
A[MaterialApp] --> B[Scaffold]
B --> C[Column]
C --> D[Text]
C --> E[Button]
D -.BuildContext.-> C
E -.BuildContext.-> C
C -.BuildContext.-> B
style A fill:#E1F5FE
style B fill:#F3E5F5
style C fill:#E8F5E9
style D fill:#FFF3E0
style E fill:#FFF3E0
用途:
scss
// 1. 获取主题
final theme = Theme.of(context);
// 2. 获取屏幕信息
final size = MediaQuery.of(context).size;
// 3. 导航
Navigator.of(context).push(...);
// 4. 显示SnackBar
ScaffoldMessenger.of(context).showSnackBar(...);
// 5. 查找父级Widget
final scaffold = Scaffold.of(context);
5. 获取State对象
有时候我们需要在Widget树中获取某个StatefulWidget的State对象,有两种方法:
方法1:通过Context查找
scss
// 查找最近的ScaffoldState
ScaffoldState? scaffoldState = context.findAncestorStateOfType<ScaffoldState>();
if (scaffoldState != null) {
// 使用State对象
scaffoldState.openDrawer();
}
特点:
- ✅ 自动向上查找Widget树
- ✅ 不需要提前准备
- ❌ 只能查找祖先Widget的State
- ❌ 可能返回null
方法2:通过GlobalKey
scala
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class ParentWidget extends StatelessWidget {
// 创建GlobalKey
final GlobalKey<_MyWidgetState> _key = GlobalKey<_MyWidgetState>();
@override
Widget build(BuildContext context) {
return Column(
children: [
MyWidget(key: _key), // ← 传递GlobalKey
ElevatedButton(
onPressed: () {
// 通过GlobalKey获取State
_key.currentState?.someMethod();
},
child: Text('调用子组件方法'),
),
],
);
}
}
特点:
- ✅ 可以跨层级访问
- ✅ 类型安全
- ✅ 可以在任何地方使用
- ❌ 需要提前创建GlobalKey
- ❌ 使用不当可能影响性能
使用场景对比:
| 场景 | 推荐方法 |
|---|---|
| 获取父级State | Context查找 |
| 操作子组件 | GlobalKey |
| 表单验证 | GlobalKey |
| 临时使用 | Context查找 |
6. Widget接口
每个Widget都需要实现的核心方法:
csharp
abstract class Widget {
/// 创建Element
Element createElement();
/// 用于优化,判断是否需要重建
Key? get key;
}
StatelessWidget实现:
scala
abstract class StatelessWidget extends Widget {
Widget build(BuildContext context);
@override
StatelessElement createElement() => StatelessElement(this);
}
StatefulWidget实现:
scala
abstract class StatefulWidget extends Widget {
State createState();
@override
StatefulElement createElement() => StatefulElement(this);
}
📚 Flutter组件库
1. 基础组件库
arduino
import 'package:flutter/widgets.dart';
常用组件:
Text- 文本显示Row/Column- 线性布局Stack- 层叠布局Container- 容器
2. Material组件库(Android风格)
arduino
import 'package:flutter/material.dart';
常用组件:
MaterialApp- 应用根组件Scaffold- 页面脚手架AppBar- 顶部栏FloatingActionButton- 悬浮按钮Card- 卡片Dialog- 对话框
3. Cupertino组件库(iOS风格)
arduino
import 'package:flutter/cupertino.dart';
常用组件:
CupertinoApp- 应用根组件CupertinoPageScaffold- 页面脚手架CupertinoNavigationBar- 导航栏CupertinoButton- 按钮CupertinoAlertDialog- 警告对话框
示例:
less
class CupertinoDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
middle: Text("iOS风格"),
),
child: Center(
child: CupertinoButton(
color: CupertinoColors.activeBlue,
child: const Text("按钮"),
onPressed: () {},
),
),
);
}
}
🎯 核心总结
- Widget是不可变的UI描述,不是真正的UI元素
- 三棵树:Widget树(配置)→ Element树(管理)→ RenderObject树(渲染)
- StatelessWidget:无状态,简单高效
- StatefulWidget:有状态,通过setState()更新
- BuildContext:Widget在树中的位置,用于访问父级和服务
- 组件库:基础库、Material风格、Cupertino风格
📝 常见问题
Q1: 为什么Widget要设计成不可变的?
A:
- 性能优化:不可变对象可以安全共享和复用
- 简化对比:只需对比引用即可判断是否改变
- 函数式编程:符合声明式UI范式
Q2: Element是做什么的?
A: Element是Widget的实例化,主要作用:
- 持有Widget和RenderObject的引用
- 管理Widget的生命周期
- 在Widget树变化时执行diff算法
- 决定是否需要重建RenderObject
Q3: 什么时候用StatelessWidget,什么时候用StatefulWidget?
A:
scss
// 简单判断规则
有变化的数据 → StatefulWidget
没有变化的数据 → StatelessWidget
// 示例
Text('固定文本') → StatelessWidget
TextField() → StatefulWidget (输入会变化)
Icon(Icons.star) → StatelessWidget
Checkbox(value: checked) → StatefulWidget (选中状态会变化)
Q4: 可以在StatelessWidget中定义变量吗?
A: 可以,但这些变量是构造参数,创建后不可变:
scala
class UserCard extends StatelessWidget {
final String name; // ✅ 可以,但不可变
final int age;
const UserCard({required this.name, required this.age});
@override
Widget build(BuildContext context) {
// this.name = 'new'; // ❌ 错误!不能修改
return Text('$name, $age岁');
}
}
Q5: Material和Cupertino可以混用吗?
A: 可以!它们只是不同的UI风格:
less
MaterialApp(
home: Scaffold(
body: CupertinoButton( // ✅ Material中使用Cupertino组件
child: Text('iOS风格按钮'),
onPressed: () {},
),
),
)
🎓 跟着做练习
练习1:创建一个静态个人名片 ⭐
目标: 使用StatelessWidget创建一个显示个人信息的卡片
完整代码:
less
class ProfileCard extends StatelessWidget {
final String name;
final String title;
final String email;
const ProfileCard({
required this.name,
required this.title,
required this.email,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(name, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
Text(title, style: TextStyle(color: Colors.grey)),
SizedBox(height: 8),
Row(
children: [
Icon(Icons.email, size: 16),
SizedBox(width: 4),
Text(email),
],
),
],
),
),
);
}
}
// 使用
ProfileCard(
name: '张三',
title: 'Flutter开发工程师',
email: 'zhangsan@example.com',
)
练习2:创建一个喜欢按钮 ⭐⭐
目标: 使用StatefulWidget创建一个可以点击切换状态的喜欢按钮
完整代码:
scala
class LikeButton extends StatefulWidget {
@override
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
bool _isLiked = false;
int _likeCount = 0;
void _toggleLike() {
setState(() {
_isLiked = !_isLiked;
_likeCount += _isLiked ? 1 : -1;
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(
_isLiked ? Icons.favorite : Icons.favorite_border,
color: _isLiked ? Colors.red : Colors.grey,
),
onPressed: _toggleLike,
),
Text('$_likeCount'),
],
);
}
}
练习3:使用Context获取主题色 ⭐⭐
目标: 创建一个组件,根据当前主题色显示不同样式
完整代码:
less
class ThemedCard extends StatelessWidget {
final String title;
final String content;
const ThemedCard({required this.title, required this.content});
@override
Widget build(BuildContext context) {
// 从Context获取主题
final theme = Theme.of(context);
final primaryColor = theme.colorScheme.primary;
return Card(
color: primaryColor.withValues(alpha: 0.1),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryColor, // ← 使用主题色
),
),
SizedBox(height: 8),
Text(content),
],
),
),
);
}
}
💡 练习建议
- 先运行示例:看看每个组件的效果
- 复制代码尝试:在示例基础上修改
- 理解原理:思考为什么这样设计
- 举一反三:尝试创建类似的组件