Widget 基础
在 Flutter 中,一切皆 Widget。Widget 是 Flutter 应用的基本构建块,理解 Widget 是掌握 Flutter 开发的关键。
什么是 Widget
Widget 是 Flutter 中描述用户界面的不可变元素。它可以是一个按钮、一段文本、一个布局结构,甚至是整个应用。
Widget 的特点
- 不可变性:Widget 一旦创建就不能修改,只能重新创建
- 轻量级:Widget 只是配置信息,非常轻量
- 组合性:通过组合小 Widget 构建复杂界面
Widget 与 Element
- Widget:描述 UI 应该长什么样
- Element:Widget 的实例化对象,管理渲染树
StatelessWidget
StatelessWidget 是没有状态的 Widget,一旦创建就不会改变。
基本结构
class MyWidget extends StatelessWidget {
final String title;
const MyWidget({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(title);
}
}
示例:自定义卡片
class InfoCard extends StatelessWidget {
final String title;
final String subtitle;
final IconData icon;
const InfoCard({
Key? key,
required this.title,
required this.subtitle,
required this.icon,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
margin: EdgeInsets.all(8),
child: Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Icon(icon, size: 40, color: Colors.blue),
SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
subtitle,
style: TextStyle(color: Colors.grey),
),
],
),
],
),
),
);
}
}
// 使用
InfoCard(
title: 'Flutter',
subtitle: '跨平台开发框架',
icon: Icons.flutter_dash,
)
StatefulWidget
StatefulWidget 是有状态的 Widget,可以响应用户交互或数据变化而更新。
基本结构
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('计数器:$_counter'),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('增加'),
),
],
);
}
}
setState 的工作原理
当调用 setState() 时:
- 标记 Widget 为需要重建
- Flutter 框架重新调用
build()方法 - 比较新旧 Widget 树,只更新变化的部分
生命周期
class LifecycleWidget extends StatefulWidget {
const LifecycleWidget({Key? key}) : super(key: key);
@override
_LifecycleWidgetState createState() => _LifecycleWidgetState();
}
class _LifecycleWidgetState extends State<LifecycleWidget> {
@override
void initState() {
super.initState();
print('initState: Widget 初始化');
// 初始化状态、订阅流、添加监听器
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('didChangeDependencies: 依赖改变');
// 当依赖的 InheritedWidget 改变时调用
}
@override
void didUpdateWidget(covariant LifecycleWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print('didUpdateWidget: Widget 更新');
// 父 Widget 重建时调用
}
@override
Widget build(BuildContext context) {
print('build: 构建 UI');
return Container();
}
@override
void dispose() {
print('dispose: Widget 销毁');
// 清理资源、取消订阅、移除监听器
super.dispose();
}
}
基础 Widget
文本 Widget
// 基本文本
Text('Hello, Flutter!')
// 带样式的文本
Text(
'Hello, Flutter!',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
letterSpacing: 2,
),
)
// 富文本
RichText(
text: TextSpan(
style: TextStyle(color: Colors.black, fontSize: 16),
children: [
TextSpan(text: 'Hello '),
TextSpan(
text: 'Flutter',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
TextSpan(text: '!'),
],
),
)
按钮 Widget
// ElevatedButton(凸起按钮)
ElevatedButton(
onPressed: () {
print('点击了凸起按钮');
},
child: Text('凸起按钮'),
)
// TextButton(文本按钮)
TextButton(
onPressed: () {
print('点击了文本按钮');
},
child: Text('文本按钮'),
)
// OutlinedButton(轮廓按钮)
OutlinedButton(
onPressed: () {
print('点击了轮廓按钮');
},
child: Text('轮廓按钮'),
)
// IconButton(图标按钮)
IconButton(
icon: Icon(Icons.favorite),
onPressed: () {
print('点击了图标按钮');
},
)
// 自定义按钮样式
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
onPressed: () {},
child: Text('自定义按钮'),
)
图片 Widget
// 网络图片
Image.network(
'https://example.com/image.jpg',
width: 200,
height: 200,
fit: BoxFit.cover,
)
// 本地图片(需要在 pubspec.yaml 中声明)
Image.asset(
'assets/images/logo.png',
width: 100,
height: 100,
)
// 文件图片
Image.file(
File('/path/to/image.jpg'),
)
// 图标
Icon(
Icons.star,
size: 48,
color: Colors.yellow,
)
容器 Widget
// Container
Container(
width: 200,
height: 200,
padding: EdgeInsets.all(16),
margin: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Text('容器内容'),
)
// Padding
Padding(
padding: EdgeInsets.all(16),
child: Text('带内边距的文本'),
)
// Center
Center(
child: Text('居中文本'),
)
// SizedBox
SizedBox(
width: 100,
height: 50,
child: ElevatedButton(
onPressed: () {},
child: Text('固定大小'),
),
)
输入 Widget
TextField
// 基本文本输入
TextField(
decoration: InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
border: OutlineInputBorder(),
),
onChanged: (value) {
print('输入内容:$value');
},
)
// 带图标的输入框
TextField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.email),
labelText: '邮箱',
border: OutlineInputBorder(),
),
)
// 密码输入框
TextField(
obscureText: true,
decoration: InputDecoration(
prefixIcon: Icon(Icons.lock),
labelText: '密码',
border: OutlineInputBorder(),
),
)
// 使用 TextEditingController
class MyTextField extends StatefulWidget {
@override
_MyTextFieldState createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _controller,
decoration: InputDecoration(labelText: '输入'),
),
ElevatedButton(
onPressed: () {
print('输入值:${_controller.text}');
},
child: Text('获取输入'),
),
],
);
}
}
列表 Widget
ListView
// 基本列表
ListView(
children: [
ListTile(title: Text('项目 1')),
ListTile(title: Text('项目 2')),
ListTile(title: Text('项目 3')),
],
)
// 构建器模式(适合大量数据)
ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(
title: Text('项目 $index'),
);
},
)
// 分隔线列表
ListView.separated(
itemCount: 100,
separatorBuilder: (context, index) => Divider(),
itemBuilder: (context, index) {
return ListTile(
title: Text('项目 $index'),
);
},
)
GridView
// 网格布局
GridView.count(
crossAxisCount: 2, // 每行 2 个
children: [
Container(color: Colors.red),
Container(color: Colors.green),
Container(color: Colors.blue),
Container(color: Colors.yellow),
],
)
// 构建器模式
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: 30,
itemBuilder: (context, index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length],
child: Center(child: Text('$index')),
);
},
)
对话框和提示
AlertDialog
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('提示'),
content: Text('确定要删除吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text('确定'),
),
],
);
},
).then((confirmed) {
if (confirmed == true) {
print('用户确认删除');
}
});
SnackBar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('操作成功!'),
action: SnackBarAction(
label: '撤销',
onPressed: () {
print('撤销操作');
},
),
),
);
BottomSheet
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
height: 200,
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('底部弹出菜单', style: TextStyle(fontSize: 18)),
ListTile(
leading: Icon(Icons.photo),
title: Text('从相册选择'),
onTap: () {
Navigator.pop(context);
print('从相册选择');
},
),
ListTile(
leading: Icon(Icons.camera),
title: Text('拍照'),
onTap: () {
Navigator.pop(context);
print('拍照');
},
),
],
),
);
},
);
小结
本章我们学习了:
- Widget 的概念:一切皆 Widget,不可变,轻量级
- StatelessWidget:无状态 Widget,适合静态内容
- StatefulWidget:有状态 Widget,适合交互内容
- 生命周期:initState、build、dispose 等
- 基础 Widget:Text、Button、Image、Container
- 输入 Widget:TextField 及其配置
- 列表 Widget:ListView、GridView
- 对话框和提示:AlertDialog、SnackBar、BottomSheet
练习
- 创建一个自定义的登录表单 Widget
- 实现一个图片列表,支持下拉刷新
- 创建一个自定义的 AlertDialog,包含输入框
- 实现一个底部导航栏应用
下一步
下一章我们将学习 布局系统,掌握如何使用各种布局 Widget 组织界面。