📋 学习前提
- 已完成第4课:布局组件深度解析
- 熟悉Row、Column、Container等布局组件
- 理解Widget的基本概念和生命周期
🎯 课程目标
通过本课程,你将能够:
- 掌握Flutter核心UI组件的使用方法
- 熟练使用文本、图片、按钮等基础组件
- 理解表单组件的交互和验证机制
- 构建美观实用的用户界面
- 掌握组件组合和自定义的技巧
🚀 Flutter组件生态系统
Flutter提供了丰富的组件库,主要分为两大类:
Material Design组件
- 基础组件:Text、Image、Icon、Button等
- 布局组件:Container、Row、Column、Stack等
- Material组件:AppBar、Drawer、Card、Dialog等
- 表单组件:TextField、Checkbox、Radio、Switch等
Cupertino组件(iOS风格)
- iOS风格组件:CupertinoButton、CupertinoTextField等
- 平台适配:为iOS用户提供原生体验
💡 重要概念:组件组合
Flutter的核心思想是通过组合简单组件来构建复杂界面。每个组件都是独立的,可以自由组合和重用。
📝 Text(文本组件)
Text的基本用法
Text是显示文本内容的核心组件:
import 'package:flutter/material.dart';
class TextExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 基本文本
const Text('普通文本'),
// 带样式的文本
const Text(
'带样式文本',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
decoration: TextDecoration.underline,
),
),
// 多行文本
const Text(
'这是一个很长的文本内容,会自动换行显示。Flutter的文本组件支持丰富的样式和排版功能。',
style: TextStyle(fontSize: 16),
maxLines: 2,
overflow: TextOverflow.ellipsis, // 超出显示省略号
),
// 富文本(RichText)
Text.rich(
TextSpan(
children: [
const TextSpan(
text: '红色',
style: TextStyle(color: Colors.red, fontSize: 20),
),
const TextSpan(text: '和'),
const TextSpan(
text: '蓝色',
style: TextStyle(color: Colors.blue, fontSize: 20),
),
const TextSpan(text: '的文本'),
WidgetSpan(
child: Icon(Icons.star, color: Colors.amber, size: 20),
),
],
),
),
],
);
}
}
TextStyle常用属性
TextStyle(
color: Colors.black, // 文字颜色
fontSize: 16.0, // 字体大小
fontWeight: FontWeight.bold, // 字体粗细
fontStyle: FontStyle.italic, // 字体样式(斜体)
letterSpacing: 1.0, // 字符间距
wordSpacing: 2.0, // 单词间距
height: 1.5, // 行高
backgroundColor: Colors.yellow, // 背景颜色
decoration: TextDecoration.underline, // 装饰线
decorationColor: Colors.red, // 装饰线颜色
decorationStyle: TextDecorationStyle.dashed, // 装饰线样式
shadows: [ // 文字阴影
Shadow(
color: Colors.grey,
blurRadius: 2,
offset: Offset(2, 2),
),
],
)
💡 最佳实践:文本主题
使用Theme.of(context).textTheme来获取主题中的文本样式,保持应用风格的一致性。
🖼️ Image(图片组件)
图片加载方式
Flutter支持多种图片加载方式:
Column(
children: [
// 1. 加载本地图片
Image.asset(
'assets/images/avatar.png',
width: 100,
height: 100,
fit: BoxFit.cover,
),
// 2. 加载网络图片
Image.network(
'https://example.com/image.jpg',
width: 200,
height: 150,
fit: BoxFit.contain,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
);
},
),
// 3. 加载内存中的图片
Image.memory(
Uint8List.fromList([...]), // 图片字节数据
width: 100,
height: 100,
),
// 4. 加载文件图片
Image.file(
File('/path/to/image.jpg'),
width: 100,
height: 100,
),
],
)
图片适配模式(BoxFit)
- BoxFit.fill:拉伸填满,可能变形
- BoxFit.contain:保持比例,完整显示
- BoxFit.cover:保持比例,覆盖整个区域
- BoxFit.fitWidth:适应宽度
- BoxFit.fitHeight:适应高度
- BoxFit.scaleDown:只在需要时缩小
图片装饰和效果
// 圆形头像
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage('https://example.com/avatar.jpg'),
fit: BoxFit.cover,
),
border: Border.all(color: Colors.white, width: 2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 8,
offset: Offset(0, 4),
),
],
),
)
// 带占位符和错误处理的图片
FadeInImage.assetNetwork(
placeholder: 'assets/placeholder.jpg', // 占位图
image: 'https://example.com/image.jpg', // 网络图
width: 200,
height: 150,
fit: BoxFit.cover,
imageErrorBuilder: (context, error, stackTrace) {
return Icon(Icons.error, color: Colors.red, size: 50);
},
)
🔘 Button(按钮组件)
按钮类型和使用场景
Column(
children: [
// ElevatedButton - 凸起按钮
ElevatedButton(
onPressed: () {
print('按钮被点击');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 4,
),
child: Text('主要按钮'),
),
SizedBox(height: 10),
// TextButton - 文本按钮
TextButton(
onPressed: () {
print('文本按钮被点击');
},
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
child: Text('次要按钮'),
),
SizedBox(height: 10),
// OutlinedButton - 轮廓按钮
OutlinedButton(
onPressed: () {
print('轮廓按钮被点击');
},
style: OutlinedButton.styleFrom(
foregroundColor: Colors.blue,
side: BorderSide(color: Colors.blue, width: 1),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text('轮廓按钮'),
),
SizedBox(height: 10),
// IconButton - 图标按钮
IconButton(
onPressed: () {
print('图标按钮被点击');
},
icon: Icon(Icons.favorite),
color: Colors.red,
iconSize: 30,
),
SizedBox(height: 10),
// 带图标的按钮
ElevatedButton.icon(
onPressed: () {},
icon: Icon(Icons.send),
label: Text('发送'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
),
),
],
)
按钮状态管理
class ButtonStateExample extends StatefulWidget {
@override
_ButtonStateExampleState createState() => _ButtonStateExampleState();
}
class _ButtonStateExampleState extends State {
bool _isLoading = false;
void _simulateApiCall() async {
setState(() {
_isLoading = true;
});
// 模拟API调用
await Future.delayed(Duration(seconds: 2));
setState(() {
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _isLoading ? null : _simulateApiCall, // 禁用状态
child: _isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(Colors.white),
),
)
: Text('提交'),
);
}
}
📋 TextField(输入框组件)
TextField的基本用法
class TextFieldExample extends StatefulWidget {
@override
_TextFieldExampleState createState() => _TextFieldExampleState();
}
class _TextFieldExampleState extends State {
final TextEditingController _controller = TextEditingController();
String _inputText = '';
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
// 基本输入框
TextField(
controller: _controller,
decoration: InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
onChanged: (value) {
setState(() {
_inputText = value;
});
},
),
SizedBox(height: 20),
// 密码输入框
TextField(
obscureText: true, // 隐藏文本
decoration: InputDecoration(
labelText: '密码',
hintText: '请输入密码',
prefixIcon: Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(Icons.visibility),
onPressed: () {},
),
border: OutlineInputBorder(),
),
),
SizedBox(height: 20),
// 多行文本输入
TextField(
maxLines: 3,
decoration: InputDecoration(
labelText: '个人简介',
hintText: '请输入个人简介...',
border: OutlineInputBorder(),
),
),
SizedBox(height: 20),
Text('输入的内容: $_inputText'),
],
);
}
}
输入验证和格式化
TextField(
decoration: InputDecoration(
labelText: '邮箱',
errorText: _emailError,
border: OutlineInputBorder(),
),
keyboardType: TextInputType.emailAddress,
onChanged: (value) {
// 邮箱验证
bool isValid = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value);
setState(() {
_emailError = isValid ? null : '请输入有效的邮箱地址';
});
},
)
// 数字输入框
TextField(
decoration: InputDecoration(
labelText: '年龄',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly, // 只允许数字
],
)
⭐ Icon和Card组件
Icon(图标组件)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(Icons.star, color: Colors.amber, size: 30),
Icon(Icons.favorite, color: Colors.red, size: 30),
Icon(Icons.thumb_up, color: Colors.blue, size: 30),
Icon(Icons.share, color: Colors.green, size: 30),
Icon(Icons.delete, color: Colors.grey, size: 30),
],
)
// 自定义图标
Icon(
Icons.abc, // Material Icons
color: Colors.purple,
size: 40,
)
// 使用图片作为图标
ImageIcon(
AssetImage('assets/icons/custom.png'),
color: Colors.orange,
size: 30,
)
Card(卡片组件)
Card(
elevation: 4, // 阴影深度
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.account_circle, size: 40, color: Colors.blue),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('张三', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Text('前端工程师', style: TextStyle(color: Colors.grey)),
],
),
),
Icon(Icons.more_vert, color: Colors.grey),
],
),
SizedBox(height: 12),
Text('这是一个用户信息卡片,展示了Flutter Card组件的使用方式。'),
SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {},
child: Text('取消'),
),
SizedBox(width: 8),
ElevatedButton(
onPressed: () {},
child: Text('确认'),
),
],
),
],
),
),
)
📚 实践练习
练习1:创建用户注册表单
使用各种表单组件创建完整的注册界面:
- 用户名输入框(带验证)
- 邮箱输入框(格式验证)
- 密码输入框(显示/隐藏功能)
- 确认密码输入框(一致性验证)
- 提交按钮(加载状态)
练习2:实现商品卡片组件
使用Card和布局组件创建商品展示卡片:
- 商品图片展示
- 商品标题和描述
- 价格和折扣信息
- 评分和购买按钮
- 添加收藏和分享功能
练习3:构建设置页面
使用各种交互组件创建设置界面:
- 开关控件(通知设置)
- 单选按钮(主题选择)
- 下拉选择框(语言设置)
- 滑块控件(音量调节)
- 按钮组(操作确认)
🔍 常见问题解答
Q: 如何自定义按钮的样式?
A: 使用ButtonStyle或对应的styleFrom方法,可以设置颜色、形状、内边距等属性。
Q: TextField如何获取输入的值?
A: 使用TextEditingController或onChanged回调来获取输入值。
Q: 图片加载失败怎么办?
A: 使用errorBuilder属性或FadeInImage组件来处理加载失败的情况。
Q: 如何实现表单验证?
A: 结合TextFormField和Form组件,使用validator属性进行验证。
📖 总结
在本课程中,我们深入学习了Flutter的核心UI组件:
- 掌握了Text组件的丰富样式和排版功能
- 熟练使用Image组件的各种加载方式和适配模式
- 理解了不同类型Button的使用场景和样式定制
- 学会了TextField的输入处理和验证机制
- 掌握了Icon和Card等常用组件的实战应用
这些组件是构建Flutter应用的基础,熟练掌握它们将帮助你快速开发出功能完善、界面美观的应用程序。