第5章 - Widget 基础
嗨,朋友!我是长安。
上一章我们创建了第一个 Flutter 应用,接触了一些基本组件。这一章,我们来系统地学习 Flutter 中最重要的概念——Widget。
🤔 什么是 Widget?
在 Flutter 中,Widget 就是构建 UI 的基本单元。
你可以把 Widget 想象成乐高积木:
- 每一块积木都是一个 Widget
- 小积木可以组合成大积木
- 大积木可以组合成更大的作品
- 最终搭建出完整的界面
Flutter UI = Widget + Widget + Widget + ...
核心理念
在 Flutter 中,一切皆 Widget!文字是 Widget,按钮是 Widget,图片是 Widget,甚至布局、间距、动画都是 Widget。
🎭 Widget 的两种类型
1. StatelessWidget(无状态组件)
特点:创建后不会改变,没有可变的状态
适用场景:纯展示性的内容,如文字、图标、静态图片
class MyTitle extends StatelessWidget {
final String text;
const MyTitle({super.key, required this.text});
Widget build(BuildContext context) {
return Text(
text,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
);
}
}
// 使用
MyTitle(text: '我是标题')
2. StatefulWidget(有状态组件)
特点:可以动态改变内容,有可变的状态
适用场景:需要交互或数据变化的内容,如计数器、表单、动画
class Counter extends StatefulWidget {
const Counter({super.key});
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0; // 状态数据
void _increment() {
setState(() { // 调用 setState 更新 UI
_count++;
});
}
Widget build(BuildContext context) {
return Column(
children: [
Text('计数:$_count', style: const TextStyle(fontSize: 24)),
ElevatedButton(
onPressed: _increment,
child: const Text('加一'),
),
],
);
}
}
重要
修改 StatefulWidget 的状态时,必须使用 setState() 方法,否则 UI 不会更新!
📦 常用基础 Widget
1. Text - 文本
// 基础用法
Text('Hello Flutter')
// 带样式
Text(
'带样式的文本',
style: TextStyle(
fontSize: 20, // 字体大小
fontWeight: FontWeight.bold, // 粗体
color: Colors.blue, // 颜色
letterSpacing: 2, // 字间距
decoration: TextDecoration.underline, // 下划线
),
)
// 多行文本
Text(
'这是一段很长很长的文本,会自动换行显示',
maxLines: 2, // 最多显示行数
overflow: TextOverflow.ellipsis, // 超出显示省略号
textAlign: TextAlign.center, // 居中对齐
)
2. Icon - 图标
// 基础图标
Icon(Icons.favorite)
// 带样式
Icon(
Icons.star,
size: 40,
color: Colors.amber,
)
// Flutter 内置了大量 Material 图标
// 查看所有图标:https://fonts.google.com/icons
常用图标:
| 图标 | 代码 |
|---|---|
| ❤️ | Icons.favorite |
| ⭐ | Icons.star |
| 🏠 | Icons.home |
| ⚙️ | Icons.settings |
| 🔍 | Icons.search |
| ➕ | Icons.add |
| ✏️ | Icons.edit |
| 🗑️ | Icons.delete |
3. Image - 图片
// 网络图片
Image.network(
'https://example.com/image.png',
width: 200,
height: 200,
fit: BoxFit.cover, // 填充方式
)
// 本地图片(需要先在 pubspec.yaml 中声明)
Image.asset('assets/images/logo.png')
// 占位图和错误图
Image.network(
'https://example.com/image.png',
loadingBuilder: (context, child, progress) {
if (progress == null) return child;
return const CircularProgressIndicator();
},
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.error);
},
)
4. Button - 按钮
// 凸起按钮(主要操作)
ElevatedButton(
onPressed: () {
print('点击了');
},
child: const Text('ElevatedButton'),
)
// 文字按钮(次要操作)
TextButton(
onPressed: () {},
child: const Text('TextButton'),
)
// 边框按钮
OutlinedButton(
onPressed: () {},
child: const Text('OutlinedButton'),
)
// 图标按钮
IconButton(
onPressed: () {},
icon: const Icon(Icons.thumb_up),
)
// 浮动操作按钮
FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
)
// 带图标的按钮
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.send),
label: const Text('发送'),
)
5. TextField - 输入框
// 基础输入框
TextField(
decoration: const InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
prefixIcon: Icon(Icons.person),
),
)
// 密码输入框
TextField(
obscureText: true, // 隐藏输入内容
decoration: const InputDecoration(
labelText: '密码',
prefixIcon: Icon(Icons.lock),
),
)
// 获取输入内容
class MyForm extends StatefulWidget {
State<MyForm> createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _controller = TextEditingController();
void dispose() {
_controller.dispose(); // 记得释放
super.dispose();
}
Widget build(BuildContext context) {
return Column(
children: [
TextField(controller: _controller),
ElevatedButton(
onPressed: () {
print('输入内容:${_controller.text}');
},
child: const Text('提交'),
),
],
);
}
}
6. Container - 容器
Container 是一个多功能的容器组件,可以设置尺寸、边距、背景、边框等。
Container(
width: 200,
height: 100,
margin: const EdgeInsets.all(10), // 外边距
padding: const EdgeInsets.all(20), // 内边距
decoration: BoxDecoration(
color: Colors.blue, // 背景色
borderRadius: BorderRadius.circular(10), // 圆角
boxShadow: [ // 阴影
BoxShadow(
color: Colors.black26,
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: const Text(
'Container 示例',
style: TextStyle(color: Colors.white),
),
)
7. Card - 卡片
Card(
elevation: 4, // 阴影高度
margin: const EdgeInsets.all(10),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const Text('卡片标题', style: TextStyle(fontSize: 18)),
const SizedBox(height: 10),
const Text('卡片内容描述...'),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () {},
child: const Text('操作'),
),
],
),
),
)
8. ListTile - 列表项
ListTile(
leading: const CircleAvatar(
child: Icon(Icons.person),
),
title: const Text('长安'),
subtitle: const Text('Flutter 开发者'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {
print('点击了列表项');
},
)
🎨 样式技巧
EdgeInsets - 边距
// 四边相同
EdgeInsets.all(16)
// 水平/垂直
EdgeInsets.symmetric(horizontal: 20, vertical: 10)
// 单独设置
EdgeInsets.only(left: 10, top: 20)
// 分别设置
EdgeInsets.fromLTRB(10, 20, 10, 20) // 左、上、右、下
BoxDecoration - 装饰
BoxDecoration(
color: Colors.white, // 背景色
gradient: LinearGradient( // 渐变背景
colors: [Colors.blue, Colors.purple],
),
borderRadius: BorderRadius.circular(12), // 圆角
border: Border.all(color: Colors.grey), // 边框
boxShadow: [ // 阴影
BoxShadow(
color: Colors.black12,
blurRadius: 8,
offset: Offset(0, 4),
),
],
image: DecorationImage( // 背景图
image: NetworkImage('url'),
fit: BoxFit.cover,
),
)
📝 小结
这一章我们学习了:
- ✅ Widget 的概念:Flutter UI 的基本构建单元
- ✅ StatelessWidget vs StatefulWidget 的区别
- ✅ 常用基础 Widget:Text、Icon、Image、Button、TextField、Container、Card、ListTile
- ✅ 样式相关:EdgeInsets、BoxDecoration
💪 练习题
- 创建一个 StatefulWidget,实现一个点赞功能(点击爱心图标,数字加一,图标变红)
- 使用 Card 和 ListTile 创建一个简单的联系人列表(至少 3 个联系人)
- 创建一个带有输入框和按钮的表单,点击按钮时弹出输入的内容
点击查看答案
练习1:点赞功能
class LikeButton extends StatefulWidget {
const LikeButton({super.key});
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
bool _isLiked = false;
int _likeCount = 0;
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {
setState(() {
_isLiked = !_isLiked;
_likeCount += _isLiked ? 1 : -1;
});
},
icon: Icon(
_isLiked ? Icons.favorite : Icons.favorite_border,
color: _isLiked ? Colors.red : Colors.grey,
),
),
Text('$_likeCount'),
],
);
}
}
练习2:联系人列表
ListView(
children: const [
Card(
child: ListTile(
leading: CircleAvatar(child: Text('张')),
title: Text('张三'),
subtitle: Text('13800138001'),
trailing: Icon(Icons.phone),
),
),
Card(
child: ListTile(
leading: CircleAvatar(child: Text('李')),
title: Text('李四'),
subtitle: Text('13800138002'),
trailing: Icon(Icons.phone),
),
),
Card(
child: ListTile(
leading: CircleAvatar(child: Text('王')),
title: Text('王五'),
subtitle: Text('13800138003'),
trailing: Icon(Icons.phone),
),
),
],
)
🚀 下一步
掌握了基础 Widget 后,下一章我们来学习 布局系统,让这些组件整齐地排列起来!
由 编程指南 提供
