Appearance
Flutter 组件
Container
容器组件
属性名 | 类型 | 说明 | 示例 |
---|---|---|---|
alignment | AlignmentGeometry | 子元素对齐方式 | topCenter /topLeft /topRight center /centerLeft /centerRight bottomCenter /bottomLeft /bottomRight |
decoration | BoxDecoration | 容器装饰样式(背景/边框/阴影等) | dart<br>BoxDecoration(<br> color: Colors.blue,<br> border: Border.all(<br> color: Colors.red,<br> width: 2.0<br> ),<br> borderRadius: BorderRadius.circular(8),<br> boxShadow: [BoxShadow(<br> color: Colors.black54,<br> offset: Offset(2.0, 2.0),<br> blurRadius: 10.0<br> )],<br> gradient: LinearGradient( // 或 RadialGradient<br> colors: [Colors.red, Colors.orange]<br> )<br>)<br> |
margin | EdgeInsetsGeometry | 容器外间距(与外部组件的距离) | EdgeInsets.all(20.0) EdgeInsets.only(top: 10) |
padding | EdgeInsetsGeometry | 容器内边距(边缘与子元素的间距) | EdgeInsets.all(10.0) EdgeInsets.symmetric(horizontal: 15) |
transform | Matrix4 | 矩阵变换(旋转/缩放/平移等) | Matrix4.rotationZ(0.2) Matrix4.translationValues(10, 0, 0) |
height | double | 容器高度 | height: 100.0 |
width | double | 容器宽度 | width: 200.0 |
child | Widget | 子元素内容 | child: Text("Hello") child: Icon(Icons.star) |
关键说明:
alignment 使用
Alignment
类的静态常量控制子元素位置,如:dartalignment: Alignment.topCenter // 顶部居中
decoration 支持嵌套多种样式效果:
color
:背景色(与gradient
冲突)border
:边框样式borderRadius
:圆角半径boxShadow
:阴影效果gradient
:线性/径向渐变背景
margin & padding 使用
EdgeInsets
类定义间距,常用构造方法:dartEdgeInsets.all(20) // 全方向 EdgeInsets.only(left:10) // 指定方向 EdgeInsets.symmetric(vertical: 10) // 对称方向
transform 通过 4x4 矩阵实现 3D 变换,常用操作:
dartMatrix4.rotationZ(angle) // Z轴旋转 Matrix4.translationValues(x, y, z) // 平移
Text
组件属性详解
属性名 | 类型 | 说明 | 可选值/示例 |
---|---|---|---|
textAlign | TextAlign | 文本水平对齐方式 | TextAlign.center (居中)TextAlign.left (左对齐)TextAlign.right (右对齐)TextAlign.justify (两端对齐) |
textDirection | TextDirection | 文本阅读方向 | TextDirection.ltr (从左至右)TextDirection.rtl (从右至左) |
overflow | TextOverflow | 文本溢出处理方式 | TextOverflow.clip (裁剪)TextOverflow.fade (渐隐)TextOverflow.ellipsis (省略号) |
textScaleFactor | double | 字体缩放比例(基于系统字体大小) | 1.0 (默认大小)1.2 (放大 20%)0.8 (缩小 20%) |
maxLines | int | 最大显示行数 | 1 (单行显示)3 (最多 3 行)null (无限制) |
style | TextStyle | 文本样式设置(支持嵌套多种样式) | dart<br>TextStyle(<br> color: Colors.red,<br> fontSize: 16.0,<br> fontWeight: FontWeight.bold,<br> fontStyle: FontStyle.italic,<br> letterSpacing: 1.5,<br> decoration: TextDecoration.underline<br>) |
关键属性详解:
textAlign
控制文本在水平方向的对齐方式:dartText('Hello World', textAlign: TextAlign.center)
textDirection
影响对齐方向和溢出行为:dartText('مرحبا', textDirection: TextDirection.rtl) // 阿拉伯语从右向左
overflow 与 maxLines 配合使用
常见组合:dartText('长文本内容...', maxLines: 1, overflow: TextOverflow.ellipsis // 单行省略 )
textScaleFactor
响应系统字体设置:dartText('可缩放的文本', textScaleFactor: 1.2)
style 常用样式属性:
样式属性 类型/值 说明 color
Color 文字颜色 fontSize
double 字体大小 fontWeight
FontWeight.w100-w900 字体粗细 fontStyle
FontStyle.normal/italic 正常/斜体 letterSpacing
double 字符间距 decoration
TextDecoration.underline/lineTrough 下划线/删除线 fontFamily
String 自定义字体
💡 最佳实践:
- 多语言文本务必设置正确的
textDirection
- 长文本推荐组合使用
maxLines
和overflow
- 使用
textScaleFactor
保持 UI 可访问性- 通过
style
的copyWith()
复用文本样式:dartTextStyle baseStyle = TextStyle(fontSize: 16); Text('标题', style: baseStyle.copyWith(fontWeight: FontWeight.bold))
Image
组件属性详解
- Image.asset, 本地图片
- Image.network 远程图片
属性名 类型 说明 可选值/示例 alignment Alignment 图片在容器内的对齐方式 Alignment.topCenter
Alignment.bottomLeft
Alignment(-0.5, 0.5)
color
colorBlendModeColor
BlendMode背景色与混合模式(需配合使用) dart<br>color: Colors.green,<br>colorBlendMode: BlendMode.difference
fit BoxFit 图片填充方式 BoxFit.fill
BoxFit.contain
BoxFit.cover
BoxFit.fitWidth
BoxFit.fitHeight
BoxFit.scaleDown
repeat ImageRepeat 图片平铺方式 ImageRepeat.repeat
ImageRepeat.repeatX
ImageRepeat.repeatY
ImageRepeat.noRepeat
width double 图片宽度(常配合圆形裁剪使用) width: 120.0
height double 图片高度(常配合圆形裁剪使用) height: 120.0
关键属性详解:
1. color & colorBlendMode
dart
Image.asset('assets/icon.png',
color: Colors.green,
colorBlendMode: BlendMode.difference
)
- 常用混合模式:
BlendMode.difference
:反色效果BlendMode.multiply
:叠加效果BlendMode.saturation
:饱和度混合
2. fit(图片填充方式)
值 | 效果说明 | 视觉表现 |
---|---|---|
BoxFit.fill | 拉伸充满容器(可能变形) | ⬛ 全填充(变形) |
BoxFit.contain | 保持比例全显示(可能有留白) | ⬛ 完整显示(带留白) |
BoxFit.cover | 保持比例充满(可能裁剪) | ⬛ 充满(裁剪边缘) |
BoxFit.fitWidth | 宽度充满(高度自适应) | ↔️ 宽度撑满 |
BoxFit.fitHeight | 高度充满(宽度自适应) | ↕️ 高度撑满 |
BoxFit.scaleDown | 保持比例但不放大(contain 变体) | ⬛ 完整显示(不放大) |
3. repeat(平铺模式)
dart
Image.asset('pattern.png',
repeat: ImageRepeat.repeatX,
width: double.infinity
)
- 应用场景:
- 背景纹理平铺
- 创建连续图案
- 无缝拼接大图
4. width/height(尺寸控制)
dart
ClipOval(
child: Image.network('url',
width: 100,
height: 100,
fit: BoxFit.cover,
)
)
最佳实践:
混合模式选择:
- 使用
color
属性实现单色图标 - 通过
colorBlendMode
创建特殊滤镜效果
- 使用
响应式图片处理:
dartLayoutBuilder( builder: (context, constraints) { return Image.asset('bg.jpg', width: constraints.maxWidth, fit: BoxFit.cover ); } )
圆形头像实现:
dartClipOval( child: Image.asset('avatar.jpg', width: 80, height: 80, fit: BoxFit.cover, // 关键:确保图片居中裁剪 ), )
平铺背景优化:
dartContainer( decoration: BoxDecoration( image: DecorationImage( image: AssetImage('tile.png'), repeat: ImageRepeat.repeat, scale: 0.5 // 控制图案密度 ) ) )
💡 性能提示:
- 网络图片使用
cacheHeight
/cacheWidth
减少内存占用- 需要圆角效果优先考虑
ClipRRect
而非borderRadius
- 平铺小图替代大图可显著降低内存消耗
Flutter 实现圆形图片的 5 种方法
方法 1:使用 ClipOval
(最常用)
dart
ClipOval(
child: Image.network(
'https://example.com/avatar.jpg',
width: 100,
height: 100,
fit: BoxFit.cover, // 关键:确保图片填充裁剪区域
),
)
方法 2:使用 CircleAvatar
(头像专用)
dart
CircleAvatar(
radius: 60, // 控制圆形大小
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
child: Text('AB'), // 可添加备用文本
)
方法 3:使用 Container
+ BoxDecoration
dart
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
// borderRadius: BorderRadius.circular(75),
image: DecorationImage(
image: NetworkImage('https://example.com/avatar.jpg'),
fit: BoxFit.cover,
),
),
)
方法 4:使用 ClipRRect
+ 圆角(可调整圆角程度)
dart
ClipRRect(
borderRadius: BorderRadius.circular(100), // 设置为宽高的一半
child: Image.asset(
'assets/profile.jpg',
width: 100,
height: 100,
fit: BoxFit.cover,
),
)
方法 5:使用 PhysicalModel
(带物理效果)
dart
PhysicalModel(
color: Colors.transparent,
shape: BoxShape.circle,
elevation: 5, // 添加阴影深度
child: Image.network(
'https://example.com/avatar.jpg',
width: 100,
height: 100,
fit: BoxFit.cover,
),
)
最佳实践建议:
尺寸控制:
- 宽高必须相等才能得到正圆
- 使用
LayoutBuilder
实现响应式圆形:
dartLayoutBuilder( builder: (context, constraints) { double size = constraints.maxWidth; return ClipOval( child: Image.asset('avatar.jpg', width: size, height: size, fit: BoxFit.cover, ), ); } )
占位与错误处理:
dartClipOval( child: CachedNetworkImage( imageUrl: 'https://example.com/avatar.jpg', width: 100, height: 100, fit: BoxFit.cover, placeholder: (context, url) => CircularProgressIndicator(), errorWidget: (context, url, error) => Icon(Icons.error), ), )
添加边框:
dartContainer( decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all(color: Colors.blue, width: 3), ), child: ClipOval( child: Image.asset(...), ), )
带点击效果的圆形图片:
dartInkWell( onTap: () {}, // 点击事件 borderRadius: BorderRadius.circular(50), child: ClipOval( child: Image.asset(...), ), )
💡 性能优化:
- 网络图片使用
cached_network_image
包缓存- 本地图片使用
Image.asset
并指定精确尺寸- 对于小图标,优先使用
Icon
而不是图片资源
官方图标组件
- Material Design 所有图标可以在其官网查看:https://material.io/tools/icons/
dart
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: const [
Icon(Icons.search, color: Colors.red, size: 40),
SizedBox(height: 10),
Icon(Icons.home),
SizedBox(height: 10),
Icon(Icons.category),
SizedBox(height: 10),
Icon(Icons.shop),
SizedBox(height: 10),
],
)
);
}
}
集成阿里巴巴图标库自定义字体图标指南
完整实现步骤
dart
// 步骤1:定义图标映射类
class Iconfont {
static const String _family = 'iconfont'; // 与pubspec.yaml中定义一致
Iconfont._();
// 使用Unicode码点定义图标
static const IconData home = IconData(0xe600, fontFamily: _family);
static const IconData search = IconData(0xe601, fontFamily: _family);
static const IconData cart = IconData(0xe602, fontFamily: _family);
static const IconData user = IconData(0xe603, fontFamily: _family);
static const IconData settings = IconData(0xe604, fontFamily: _family);
}
class IconfontDemo extends StatelessWidget {
const IconfontDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('阿里巴巴图标库示例')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 步骤2:使用自定义图标
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildIcon(Iconfont.home, '首页', Colors.blue),
_buildIcon(Iconfont.search, '搜索', Colors.red),
_buildIcon(Iconfont.cart, '购物车', Colors.orange),
],
),
const SizedBox(height: 40),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildIcon(Iconfont.user, '用户', Colors.green),
_buildIcon(Iconfont.settings, '设置', Colors.purple),
],
),
const SizedBox(height: 40),
// 使用Text显示图标
Text(
String.fromCharCode(Iconfont.home.codePoint),
style: TextStyle(
fontFamily: Iconfont._family,
fontSize: 40,
color: Colors.blue,
),
),
],
),
),
);
}
Widget _buildIcon(IconData icon, String label, Color color) {
return Column(
children: [
Icon(icon, size: 36, color: color),
const SizedBox(height: 8),
Text(label, style: TextStyle(color: color)),
],
);
}
}
配置步骤详解
步骤 1:获取图标文件
- 访问阿里巴巴图标库
- 创建项目并添加所需图标
- 下载图标包(选择
Font class
方式) - 解压后得到:
iconfont.ttf
(字体文件)iconfont.css
(包含 Unicode 编码)
步骤 2:配置 Flutter 项目
- 在项目根目录创建
assets/fonts
文件夹 - 将
iconfont.ttf
复制到此文件夹 - 修改
pubspec.yaml
添加字体配置:
yaml
flutter:
uses-material-design: true
assets:
- assets/images/ # 如果有其他资源
fonts:
- family: iconfont # 自定义字体名称
fonts:
- asset: assets/fonts/iconfont.ttf
高级用法:自动生成映射类
安装代码生成工具:
bashflutter pub global activate iconfont_builder
使用命令生成 Dart 类:
bashflutter pub global run iconfont_builder --from path/to/iconfont.css --to lib/iconfont.dart
常见问题解决
图标显示为方块 □
- 检查字体文件路径是否正确
- 确保
pubspec.yaml
缩进正确 - 执行
flutter clean
后重新运行
图标颜色不生效
- 确认图标不是多色图标(多色图标需要使用 SVG)
- 检查是否在父组件中覆盖了图标颜色
图标大小异常
- 使用
size
参数明确指定尺寸 - 避免在父容器中使用固定宽高限制
- 使用
图标偏移问题
dartIcon( Iconfont.cart, size: 24, textDirection: TextDirection.ltr, // 解决方向问题 )
最佳实践建议
图标管理
- 创建单独的
iconfont.dart
文件管理所有图标 - 使用脚本自动更新图标映射类
- 为图标添加详细注释说明用途
- 创建单独的
性能优化
- 使用
const
修饰图标组件 - 避免在动画中频繁改变图标
- 复杂图标优先考虑 SVG 格式
- 使用
多主题适配
dartIcon( Iconfont.home, color: Theme.of(context).iconTheme.color, )
图标命名规范
dartclass AppIcons { static const IconData home = IconData(0xe600, fontFamily: 'iconfont'); static const IconData search = IconData(0xe601, fontFamily: 'iconfont'); // 按功能模块分组 }
提示:对于多色图标或需要动态变化的图标,建议使用
flutter_svg
包加载 SVG 格式图标
ListView 列表组件
列表类型及核心属性
列表类型 | 实现方式 | 特点描述 |
---|---|---|
垂直列表 | ListView | 默认垂直滚动,适合单列数据展示 |
垂直图文列表 | ListView + ListTile | 图文混合布局,适合商品列表、新闻列表等复杂内容 |
水平列表 | ListView(scrollDirection: Axis.horizontal) | 横向滚动,适合轮播图、分类标签等 |
动态列表 | ListView.builder | 按需构建列表项,性能最优,适合大数据集 |
核心属性详解
属性名 | 类型 | 说明 |
---|---|---|
scrollDirection | Axis | 滚动方向:Axis.vertical (垂直,默认),Axis.horizontal (水平) |
padding | EdgeInsetsGeometry | 列表内边距,如:EdgeInsets.all(10) |
reverse | bool | 是否反向显示列表(从下往上/从右往左) |
children | List<Widget> | 直接子组件列表(静态列表使用) |
itemCount | int | 动态列表项数量(仅builder 使用) |
itemBuilder | IndexedWidgetBuilder | 动态列表项构建函数((context, index) => Widget ) |
1. 基础垂直列表
dart
ListView(
padding: const EdgeInsets.all(16),
children: const [
ListTile(title: Text('选项1')),
ListTile(title: Text('选项2')),
ListTile(title: Text('选项3')),
ListTile(title: Text('选项4')),
],
)
2. 垂直图文列表
dart
ListView(
children: [
ListTile(
leading: Image.network('https://example.com/img1.jpg', width: 50),
title: const Text('商品标题1'),
subtitle: const Text('商品描述信息'),
trailing: const Icon(Icons.arrow_forward),
),
ListTile(
leading: Image.network('https://example.com/img2.jpg', width: 50),
title: const Text('商品标题2'),
subtitle: const Text('商品描述信息'),
trailing: const Icon(Icons.arrow_forward),
),
// 更多列表项...
],
)
3. 水平列表
dart
SizedBox(
height: 120, // 必须指定高度
child: ListView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 12),
children: [
_buildCategory('美食', Icons.restaurant),
_buildCategory('购物', Icons.shopping_cart),
_buildCategory('旅行', Icons.flight),
_buildCategory('娱乐', Icons.movie),
_buildCategory('学习', Icons.school),
],
),
)
Widget _buildCategory(String name, IconData icon) {
return Container(
width: 100,
margin: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 40),
const SizedBox(height: 8),
Text(name),
],
),
);
}
4. 动态列表(性能优化)
dart
final List<String> items = List.generate(100, (i) => '列表项 ${i + 1}');
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text(items[index]),
subtitle: Text('索引: $index'),
trailing: Icon(
index % 3 == 0 ? Icons.star : Icons.star_border,
color: Colors.amber,
),
);
},
)
高级列表技巧
1. 分隔线控制
dart
ListView.separated(
itemCount: 50,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) => ListTile(title: Text('项目 $index')),
)
2. 滚动控制器
dart
final ScrollController _controller = ScrollController();
@override
void initState() {
super.initState();
_controller.addListener(() {
print('滚动位置: ${_controller.offset}');
});
}
ListView.builder(
controller: _controller,
// ...
)
3. 无限滚动加载
dart
ListView.builder(
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == items.length) {
return _buildLoadingIndicator();
}
return ListTile(title: Text(items[index]));
},
)
Widget _buildLoadingIndicator() {
return const Padding(
padding: EdgeInsets.all(16),
child: Center(child: CircularProgressIndicator()),
);
}
4. 吸顶头部
dart
CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
title: Text('吸顶标题'),
background: Image.network('https://example.com/header.jpg', fit: BoxFit.cover),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('内容 $index')),
childCount: 50,
),
),
],
)
性能优化建议
静态列表:
dartListView( children: const [ // 使用const ItemWidget(), // 组件也使用const ItemWidget(), ], )
动态列表黄金法则:
- 始终使用
ListView.builder
- 设置精确的
itemCount
- 避免在
itemBuilder
中创建新函数
- 始终使用
懒加载图片:
dartListView.builder( itemBuilder: (context, index) => CachedNetworkImage( imageUrl: images[index], placeholder: (_, __) => LoadingWidget(), ), )
列表项保持单一职责:
dart// ❌ 避免 itemBuilder: (c, i) => Column( children: [ if (i == 0) Header(), Item(data[i]) ] ) // ✅ 正确 Column( children: [ Header(), Expanded(child: ListView.builder(...)) ] )
使用
RepaintBoundary
:dartListView.builder( itemBuilder: (c, i) => RepaintBoundary( child: ComplexItemWidget(data[i]) ) )
常见问题解决方案
嵌套滚动冲突:
dartListView( physics: const ClampingScrollPhysics(), // 解决嵌套滚动 // ... )
列表高度错误:
dart// 在Column中包裹ListView Expanded( child: ListView(...) )
滚动位置保持:
dartclass _MyListState extends State<MyList> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; @override Widget build(context) { super.build(context); return ListView(...); } }
动态更新优化:
dart// 使用Key强制重建特定项 ListView.builder( itemBuilder: (c, i) => ItemWidget( key: ValueKey(items[i].id), // 唯一Key data: items[i], ) )
GridView 网格组件
核心属性总览
属性名 | 类型 | 说明 |
---|---|---|
scrollDirection | Axis | 滚动方向:Axis.vertical (垂直,默认),Axis.horizontal (水平) |
padding | EdgeInsetsGeometry | 内边距 |
reverse | bool | 是否反向排序(默认 false) |
crossAxisSpacing | double | 水平子 Widget 之间的间距 |
mainAxisSpacing | double | 垂直子 Widget 之间的间距 |
crossAxisCount | int | 在GridView.count 中使用,指定一行中的网格数量 |
maxCrossAxisExtent | double | 在GridView.extent 中使用,指定横轴子元素的最大长度 |
childAspectRatio | double | 子 Widget 宽高比例(宽度/高度) |
children | List<Widget> | 子组件列表(用于静态网格) |
gridDelegate | SliverGridDelegate | 布局控制器(用于动态网格),有两种类型:SliverGridDelegateWithFixedCrossAxisCount SliverGridDelegateWithMaxCrossAxisExtent |
三种创建方式详解
1. GridView.count - 固定数量网格
dart
GridView.count(
crossAxisCount: 3, // 每行3个元素
crossAxisSpacing: 8, // 水平间距
mainAxisSpacing: 12, // 垂直间距
childAspectRatio: 0.8, // 宽高比(宽/高)
padding: const EdgeInsets.all(16),
children: List.generate(12, (index) {
return Container(
color: Colors.blue[100 * (index % 9)],
child: Center(child: Text('Item $index')),
);
}),
)
2. GridView.extent - 最大宽度网格
dart
GridView.extent(
maxCrossAxisExtent: 150, // 每个网格最大宽度150
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1, // 正方形网格
children: List.generate(20, (index) {
return Card(
elevation: 3,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.star, size: 40, color: Colors.amber),
Text('商品 $index')
],
),
);
}),
)
3. GridView.builder - 动态网格(推荐)
dart
final List<Product> products = [
Product(name: '手机', icon: Icons.phone_iphone),
Product(name: '电脑', icon: Icons.computer),
// ...更多数据
];
GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4, // 每行4个
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 0.9,
),
itemCount: products.length,
itemBuilder: (context, index) {
return ProductItem(product: products[index]);
},
)
高级网格布局技巧
1. 响应式网格布局
dart
LayoutBuilder(
builder: (context, constraints) {
int crossAxisCount;
if (constraints.maxWidth > 600) {
crossAxisCount = 4; // 大屏幕4列
} else if (constraints.maxWidth > 400) {
crossAxisCount = 3; // 中等屏幕3列
} else {
crossAxisCount = 2; // 小屏幕2列
}
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
// ...
),
// ...
);
},
)
2. 网格与列表混合布局
dart
CustomScrollView(
slivers: [
SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 0.8,
),
delegate: SliverChildBuilderDelegate(
(context, index) => ProductCard(products[index]),
childCount: products.length,
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('推荐商品 $index')),
childCount: 10,
),
),
],
)
3. 网格头部吸顶效果
dart
CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
title: Text('商品分类'),
background: Image.network('header.jpg', fit: BoxFit.cover),
),
),
SliverGrid(
// ...网格配置
),
],
)
4. 网格项动画效果
dart
GridView.builder(
gridDelegate: /*...*/,
itemBuilder: (context, index) {
return AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: /*...*/,
);
},
)
网格布局最佳实践
1. 性能优化方案
dart
GridView.builder( // 始终使用builder
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: items.length,
itemBuilder: (context, index) {
return ProductCard( // 提取为独立组件
key: ValueKey(items[index].id), // 唯一键值
product: items[index],
);
},
)
2. 网格卡片组件示例
dart
class ProductCard extends StatelessWidget {
final Product product;
const ProductCard({super.key, required this.product});
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
child: Image.network(product.imageUrl, fit: BoxFit.cover),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(product.name, style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 4),
Text('¥${product.price}', style: const TextStyle(color: Colors.red)),
],
),
),
],
),
);
}
}
3. 网格布局常见问题解决
问题:网格高度不正确
dart
// 解决方案:使用Expanded包裹
Scaffold(
body: Column(
children: [
Expanded( // 关键
child: GridView.count(
crossAxisCount: 3,
// ...
),
),
],
),
)
问题:滚动冲突
dart
GridView.builder(
physics: const ClampingScrollPhysics(), // 解决嵌套滚动
// ...
)
问题:网格项大小不一致
dart
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
mainAxisExtent: 180, // 固定高度
// ...
)
网格布局使用场景示例
电商商品网格
dart
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.75,
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
itemCount: products.length,
itemBuilder: (context, index) {
return ProductGridItem(product: products[index]);
},
)
照片墙布局
dart
GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
),
itemCount: images.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => _openFullScreen(index),
child: Hero(
tag: 'image-$index',
child: Image.network(images[index], fit: BoxFit.cover),
),
);
},
)
设置选项网格
dart
GridView.count(
crossAxisCount: 4,
padding: const EdgeInsets.all(24),
children: [
_buildSettingItem(Icons.wifi, '网络'),
_buildSettingItem(Icons.bluetooth, '蓝牙'),
_buildSettingItem(Icons.battery_full, '电池'),
_buildSettingItem(Icons.storage, '存储'),
// ...更多设置项
],
)
Widget _buildSettingItem(IconData icon, String label) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 32),
const SizedBox(height: 8),
Text(label),
],
);
}
性能提示:
- 对于大型网格使用
GridView.builder
+SliverGridDelegate
- 使用
const
构造函数优化静态网格项- 为网格项添加
RepaintBoundary
减少重绘- 使用
cacheExtent
预加载网格区域提高滚动流畅度
Padding 组件
属性名 | 类型 | 说明 | 示例 |
---|---|---|---|
padding | EdgeInsetsGeometry | 设置内边距(核心属性) | EdgeInsets.all(16) EdgeInsets.only(top: 20) EdgeInsets.symmetric(horizontal: 10) |
child | Widget | 需要添加内边距的子组件 | child: Text('Hello') child: Icon(Icons.star) |
1. 统一内边距
dart
Padding(
padding: const EdgeInsets.all(20), // 所有方向20像素内边距
child: Container(
color: Colors.blue,
child: Text('统一内边距'),
),
)
2. 非对称内边距
dart
Padding(
padding: const EdgeInsets.only(
top: 30,
left: 15,
right: 15
), // 仅顶部/左侧/右侧有内边距
child: Card(
child: ListTile(
title: Text('非对称内边距示例'),
subtitle: Text('底部无内边距'),
),
),
)
3. 对称内边距
dart
Padding(
padding: const EdgeInsets.symmetric(
vertical: 10, // 垂直方向10px
horizontal: 20 // 水平方向20px
),
child: ElevatedButton(
onPressed: () {},
child: Text('对称内边距按钮'),
),
)
4. 组合内边距
dart
Padding(
padding: const EdgeInsets.fromLTRB(15, 10, 15, 5), // 左15, 上10, 右15, 下5
child: Text('自定义组合内边距'),
)
线性布局(Row 和 Column)
属性名 | Row(水平布局) | Column(垂直布局) | 说明 |
---|---|---|---|
主轴方向 | 水平(从左到右) | 垂直(从上到下) | 布局的主轴方向 |
主轴对齐 | MainAxisAlignment | MainAxisAlignment | 控制子组件在主轴上的排列方式 |
交叉轴对齐 | CrossAxisAlignment | CrossAxisAlignment | 控制子组件在交叉轴上的排列方式 |
主轴尺寸 | MainAxisSize | MainAxisSize | 主轴方向占用的空间(max 或 min) |
文本方向 | TextDirection | - | 控制水平方向子组件的排列顺序 |
垂直方向 | - | VerticalDirection | 控制垂直方向子组件的排列顺序 |
子组件列表 | children | children | 包含的子组件 |
1. 主轴对齐(MainAxisAlignment)
dart
enum MainAxisAlignment {
start, // 从起始位置开始排列
end, // 从结束位置开始排列
center, // 居中排列
spaceBetween, // 两端对齐,子组件之间间距相等
spaceAround, // 每个子组件两侧间距相等
spaceEvenly, // 所有间距相等(包括两端)
}
2. 交叉轴对齐(CrossAxisAlignment)
dart
enum CrossAxisAlignment {
start, // 沿交叉轴起始位置对齐
end, // 沿交叉轴结束位置对齐
center, // 居中对齐(默认)
stretch, // 拉伸子组件填满交叉轴
baseline, // 按文本基线对齐
}
3. 主轴尺寸(MainAxisSize)
dart
enum MainAxisSize {
min, // 占用最小空间
max, // 占用最大可用空间(默认)
}
基础用法示例
1. 基本水平布局(Row)
dart
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.star, color: Colors.amber),
Text('评分:4.8', style: TextStyle(fontSize: 16)),
ElevatedButton(
onPressed: () {},
child: Text('购买'),
),
],
)
2. 基本垂直布局(Column)
dart
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('标题', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text('副标题', style: TextStyle(fontSize: 16, color: Colors.grey)),
SizedBox(height: 16),
Text('详细内容...' * 5),
],
)
高级布局技巧
1. 灵活空间分配(Expanded)
dart
Row(
children: [
Expanded(
flex: 2, // 占2份
child: Container(height: 50, color: Colors.red),
),
Expanded(
flex: 3, // 占3份
child: Container(height: 50, color: Colors.blue),
),
],
)
2. 相对空间分配(Flexible)
dart
Row(
children: [
Flexible(
fit: FlexFit.loose, // 宽松分配
child: Container(width: 150, color: Colors.green),
),
Flexible(
fit: FlexFit.tight, // 紧密分配
child: Container(color: Colors.yellow),
),
],
)
3. 基线对齐(跨轴)
dart
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
'24',
style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
),
Text(
'°C',
style: TextStyle(fontSize: 20),
),
],
)
4. 嵌套布局
dart
Column(
children: [
// 顶部导航栏
Row(
children: [
IconButton(icon: Icon(Icons.menu),
Expanded(child: TextField()),
IconButton(icon: Icon(Icons.search)),
],
),
// 内容区域
Expanded(
child: ListView(/*...*/),
),
// 底部导航
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(icon: Icon(Icons.home)),
IconButton(icon: Icon(Icons.shopping_cart)),
IconButton(icon: Icon(Icons.person)),
],
),
],
)
最佳实践与解决方案
1. 处理溢出问题
dart
Row(
children: [
Expanded( // 使用Expanded防止溢出
child: Text('这是一个非常长的文本内容' * 5),
),
IconButton(icon: Icon(Icons.more_vert)),
],
)
2. 响应式布局
dart
LayoutBuilder(
builder: (context, constraints) {
bool isWide = constraints.maxWidth > 600;
return isWide
? Row(children: [NavigationRail(), ContentArea()]) // 宽屏布局
: Column(children: [AppBar(), ContentArea()]); // 窄屏布局
},
)
3. 动态列表构建
dart
Column(
children: List.generate(10, (index) =>
ListTile(
leading: CircleAvatar(child: Text('${index+1}')),
title: Text('项目 $index'),
),
),
)
4. 复杂对齐场景
dart
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 左侧头像
CircleAvatar(radius: 30),
// 右侧内容(使用Column)
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text('用户名', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(width: 8),
Text('@userhandle', style: TextStyle(color: Colors.grey)),
],
),
SizedBox(height: 4),
Text('内容文本...'),
],
),
),
],
)
性能优化指南
使用 const 构造函数:
dartconst Row( children: [ Icon(Icons.star), Text('固定内容'), ], )
提取子组件:
dart// 提取为独立组件 class RatingBar extends StatelessWidget { const RatingBar({super.key}); @override Widget build(BuildContext context) { return Row(/*...*/); } }
避免深层嵌套:
dart// ❌ 不推荐(嵌套过深) Row(children: [Column(children: [Row(children: [...]])]) // ✅ 推荐(提取子组件) CustomComplexWidget()
使用 IntrinsicHeight(谨慎使用):
dartIntrinsicHeight( child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 等高子组件 ], ), )
常见布局模式
1. 居中图标+文字按钮
dart
ElevatedButton(
onPressed: () {},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.download),
SizedBox(width: 8),
Text('下载文件'),
],
),
)
2. 标签+数值展示
dart
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('温度', style: TextStyle(color: Colors.grey)),
Text('24°C', style: TextStyle(fontSize: 20)),
],
)
3. 带图标的输入框
dart
Row(
children: [
Icon(Icons.search, color: Colors.grey),
SizedBox(width: 8),
Expanded(
child: TextField(
decoration: InputDecoration(
hintText: '搜索...',
border: InputBorder.none,
),
),
),
],
)
4. 卡片底部操作栏
dart
Card(
child: Column(
children: [
Image.network('image.jpg'),
Padding(
padding: const EdgeInsets.all(16),
child: Text('描述内容'),
),
const Divider(height: 1),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(onPressed: () {}, child: Text('取消')),
TextButton(onPressed: () {}, child: Text('确定')),
],
),
],
),
)
提示:
- 使用
Spacer()
在布局中创建灵活空间- 结合
Wrap
组件实现自动换行- 在需要滚动时使用
SingleChildScrollView
包裹 Row/Column- 使用
LayoutBuilder
获取父容器约束实现响应式布局
层叠布局(Stack, Align, Positioned)
组件 | 主要功能 | 核心特点 |
---|---|---|
Stack | 层叠布局容器,允许子组件重叠显示 | 支持相对定位和绝对定位 |
Align | 控制子组件在父容器中的对齐位置 | 支持精确的分数定位(FractionalOffset) |
Positioned | 在 Stack 中精确控制子组件的位置 | 可设置上下左右四个方向的偏移值 |
Stack 属性
属性名 | 类型 | 说明 |
---|---|---|
alignment | AlignmentDirectional | 默认子组件对齐方式(默认左上角) |
textDirection | TextDirection | 文本方向(影响对齐方向) |
fit | StackFit | 未定位子组件的尺寸调整方式(loose/expand/passthrough) |
clipBehavior | Clip | 内容溢出处理方式(默认为 Clip.hardEdge) |
children | List<Widget> | 子组件列表 |
Align 属性
属性名 | 类型 | 说明 |
---|---|---|
alignment | Alignment | 对齐方式(如 Alignment.center) |
widthFactor | double | 宽度因子(相对于子组件宽度) |
heightFactor | double | 高度因子(相对于子组件高度) |
child | Widget | 子组件 |
Positioned 属性
属性名 | 类型 | 说明 |
---|---|---|
left | double | 距离 Stack 左边的距离 |
top | double | 距离 Stack 顶部的距离 |
right | double | 距离 Stack 右边的距离 |
bottom | double | 距离 Stack 底部的距离 |
width | double | 指定宽度(非必须) |
height | double | 指定高度(非必须) |
child | Widget | 子组件 |
基础用法示例
1. 基础层叠布局
dart
Stack(
children: [
// 底层背景
Container(color: Colors.blue, height: 200, width: double.infinity),
// 居中对齐的文本
const Align(
alignment: Alignment.center,
child: Text('居中文本', style: TextStyle(color: Colors.white, fontSize: 24)),
// 右上角图标
const Positioned(
top: 10,
right: 10,
child: Icon(Icons.star, color: Colors.amber, size: 30),
),
// 左下角按钮
Positioned(
bottom: 20,
left: 20,
child: ElevatedButton(
onPressed: () {},
child: const Text('点击'),
),
),
],
)
2. 相对定位(FractionalOffset)
dart
Align(
alignment: const FractionalOffset(0.7, 0.3), // 70%宽度,30%高度处
child: Container(
width: 50,
height: 50,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
)
3. 部分定位
dart
Positioned(
left: 20,
right: 20, // 左右定位,宽度自适应
bottom: 40,
child: Container(
height: 50,
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(10),
),
child: const Center(child: Text('底部横幅')),
),
)
高级应用技巧
1. 响应式层叠布局
dart
LayoutBuilder(
builder: (context, constraints) {
return Stack(
children: [
// 背景图片
Image.network('background.jpg',
width: constraints.maxWidth,
height: constraints.maxHeight,
fit: BoxFit.cover),
// 根据屏幕尺寸调整位置
Positioned(
top: constraints.maxHeight * 0.1,
left: constraints.maxWidth * 0.15,
child: const Text('响应式标题', style: TextStyle(fontSize: 24)),
),
],
);
},
)
2. 动画浮动按钮
dart
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
bottom: _expanded ? 100 : 20,
right: _expanded ? 100 : 20,
child: FloatingActionButton(
onPressed: () => setState(() => _expanded = !_expanded),
child: const Icon(Icons.add),
),
)
3. 自定义对话框
dart
Stack(
children: [
// 半透明遮罩
Positioned.fill(
child: Container(color: Colors.black54),
),
// 居中对话框
Center(
child: Container(
width: 300,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('确认删除?', style: TextStyle(fontSize: 18)),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
TextButton(onPressed: () {}, child: const Text('取消')),
TextButton(onPressed: () {}, child: const Text('确定')),
],
)
],
),
),
),
],
)
4. 图片水印效果
dart
Stack(
children: [
// 主图片
Image.network('photo.jpg'),
// 水印文本(重复排列)
Positioned.fill(
child: Column(
children: List.generate(5, (row) => Expanded(
child: Row(
children: List.generate(4, (col) => Expanded(
child: Center(
child: Transform.rotate(
angle: -0.2,
child: const Text('机密',
style: TextStyle(
color: Colors.white30,
fontSize: 24,
fontWeight: FontWeight.bold
),
),
),
),
)),
),
)),
),
),
],
)
最佳实践与解决方案
1. 处理溢出问题
dart
Stack(
clipBehavior: Clip.none, // 允许内容溢出
children: [
Container(color: Colors.blue, width: 100, height: 100),
Positioned(
top: 80, // 部分溢出容器
left: 80,
child: Container(width: 50, height: 50, color: Colors.red),
),
],
)
2. 等比例缩放元素
dart
Align(
alignment: Alignment.center,
widthFactor: 0.8, // 容器宽度为子组件的80%
heightFactor: 0.6, // 容器高度为子组件的60%
child: Container(
color: Colors.green,
child: const Center(child: Text('等比例缩放')),
),
3. 复杂定位系统
dart
Stack(
children: [
// 使用Align定位
const Align(
alignment: Alignment(0.8, -0.8), // 右上区域
child: Icon(Icons.star, size: 40),
),
// 使用Positioned定位
Positioned(
top: MediaQuery.of(context).size.height * 0.4,
left: MediaQuery.of(context).size.width * 0.2,
child: const Icon(Icons.circle, size: 40),
),
// 使用FractionalTranslation
FractionalTranslation(
translation: const Offset(0.5, 0.5), // 中心点
child: const Icon(Icons.add, size: 60),
),
],
)
4. 性能优化
dart
// 使用const避免重绘
const Stack(
children: [
Positioned(top: 10, child: Icon(Icons.star)),
Align(alignment: Alignment.center, child: Text('固定内容')),
],
)
// 提取独立组件
class Badge extends StatelessWidget {
const Badge({super.key});
@override
Widget build(BuildContext context) {
return const Positioned(
top: 5,
right: 5,
child: CircleAvatar(radius: 10, backgroundColor: Colors.red),
);
}
}
常见使用场景
1. 带徽章的头像
dart
Stack(
clipBehavior: Clip.none, // 允许徽章溢出
children: [
const CircleAvatar(radius: 30, backgroundImage: NetworkImage('avatar.jpg')),
Positioned(
top: -5,
right: -5,
child: Container(
padding: const EdgeInsets.all(2),
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: const Text('3', style: TextStyle(color: Colors.white)),
),
),
],
)
2. 卡片重叠效果
dart
Stack(
children: [
Positioned( // 底层卡片
top: 20,
left: 20,
right: 20,
child: Card(
elevation: 4,
child: Container(height: 150),
),
),
Positioned( // 中层卡片
top: 40,
left: 40,
right: 40,
child: Card(
elevation: 8,
child: Container(height: 150),
),
),
Positioned( // 顶层卡片
top: 60,
left: 60,
right: 60,
child: Card(
elevation: 12,
child: Container(height: 150),
),
),
],
)
3. 视频播放器控制层
dart
Stack(
children: [
// 视频内容
VideoPlayer(controller: _controller),
// 控制层(半透明背景)
Positioned.fill(
child: Container(
color: Colors.black45,
child: Column(
children: [
// 顶部标题栏
AppBar(title: const Text('视频标题'), backgroundColor: Colors.transparent),
// 中间播放按钮
Expanded(
child: Center(
child: IconButton(
icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow),
iconSize: 50,
color: Colors.white,
onPressed: _togglePlay,
),
),
),
// 底部进度条
VideoProgressIndicator(_controller, allowScrubbing: true),
],
),
),
),
],
)
4. 地图标记点
dart
Stack(
children: [
// 地图组件
GoogleMap(/*...*/),
// 标记点
Positioned(
left: 100,
top: 200,
child: Column(
children: [
const Icon(Icons.location_pin, color: Colors.red, size: 40),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
child: const Text('位置A'),
),
],
),
),
],
)
提示:
- 优先使用
Positioned
进行精确控制,使用Align
进行相对定位- 使用
LayoutBuilder
实现响应式层叠布局- 对于复杂布局,组合使用
Stack
+Transform
+Opacity
- 使用
Clip.none
允许内容溢出容器边界- 避免在
Stack
中嵌套过多子组件(超过 5 个考虑重构)
Card 组件全面指南
Card 是 Flutter 中用于创建卡片式布局的核心组件,具有圆角、阴影和立体效果,非常适合展示内容区块。以下是 Card 组件的详细解析:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
margin | EdgeInsetsGeometry | null | 卡片外部的边距 |
child | Widget | 必填 | 卡片内容区域 |
elevation | double | 1.0 | 阴影深度(值越大阴影越明显) |
color | Color | Theme.cardColor | 卡片背景颜色 |
shadowColor | Color | Theme.shadowColor | 阴影颜色 |
shape | ShapeBorder | RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.0)) | 卡片形状(可自定义圆角) |
clipBehavior | Clip | Clip.none | 内容裁剪方式(none/antiAlias/antiAliasWithSaveLayer/hardEdge) |
borderOnForeground | bool | true | 是否在子组件前绘制边框 |
surfaceTintColor | Color | null | 材料表面色调颜色(Material 3) |
基础用法示例
1. 基础卡片
dart
Card(
child: ListTile(
leading: Icon(Icons.person),
title: Text('用户姓名'),
subtitle: Text('用户描述信息'),
trailing: Icon(Icons.arrow_forward),
),
)
2. 自定义卡片
dart
Card(
elevation: 8, // 更强的阴影效果
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), // 更大的圆角
),
color: Colors.blue[50], // 自定义背景色
margin: EdgeInsets.all(16), // 外边距
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('卡片标题', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text('这里是卡片的内容区域,可以放置各种组件...'),
SizedBox(height: 16),
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: () {},
child: Text('操作按钮'),
),
),
],
),
),
)
3. 图文卡片
dart
Card(
clipBehavior: Clip.antiAlias, // 平滑裁剪
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Image.network(
'https://example.com/image.jpg',
height: 150,
fit: BoxFit.cover,
),
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('产品名称', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text('¥299', style: TextStyle(color: Colors.red, fontSize: 20)),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: List.generate(5, (i) =>
Icon(Icons.star, size: 16, color: i < 4 ? Colors.amber : Colors.grey)
),
),
Text('128条评价', style: TextStyle(color: Colors.grey)),
],
),
],
),
),
],
),
)
高级用法与技巧
1. 响应式卡片设计
dart
LayoutBuilder(
builder: (context, constraints) {
bool isWide = constraints.maxWidth > 600;
return Card(
margin: EdgeInsets.symmetric(
vertical: 16,
horizontal: isWide ? 40 : 16,
),
child: Flex(
direction: isWide ? Axis.horizontal : Axis.vertical,
children: [
if (isWide) Expanded(
flex: 2,
child: Image.asset('product.jpg', fit: BoxFit.cover),
),
Expanded(
flex: 3,
child: Padding(
padding: EdgeInsets.all(20),
child: /* 内容区域 */
),
),
],
),
);
},
)
2. 动画卡片
dart
AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: Card(
elevation: _isHovered ? 12 : 4, // 悬停时提升阴影
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(_isHovered ? 20 : 10),
),
child: InkWell(
onTap: () {},
onHover: (hover) => setState(() => _isHovered = hover),
child: /* 卡片内容 */
),
),
)
3. 卡片堆叠效果
dart
Stack(
children: [
Positioned(
top: 20,
left: 20,
right: 20,
child: Card(
elevation: 2,
color: Colors.grey[200],
child: Container(height: 120),
),
),
Positioned(
top: 40,
left: 40,
right: 40,
child: Card(
elevation: 6,
color: Colors.grey[300],
child: Container(height: 120),
),
),
Card(
elevation: 12,
margin: EdgeInsets.symmetric(horizontal: 60),
child: Container(
height: 120,
padding: EdgeInsets.all(16),
child: Center(child: Text('顶层卡片')),
),
),
],
)
4. 卡片式表单
dart
Card(
margin: EdgeInsets.all(24),
child: Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('登录账户', style: Theme.of(context).textTheme.titleLarge),
SizedBox(height: 24),
TextFormField(
decoration: InputDecoration(labelText: '用户名'),
validator: (value) => value!.isEmpty ? '请输入用户名' : null,
),
SizedBox(height: 16),
TextFormField(
obscureText: true,
decoration: InputDecoration(labelText: '密码'),
validator: (value) => value!.isEmpty ? '请输入密码' : null,
),
SizedBox(height: 24),
ElevatedButton(
onPressed: _submitForm,
child: Text('登录'),
),
],
),
),
),
)
最佳实践指南
1. 内容组织原则
dart
Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 1. 标题区域
Padding(
padding: EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Text('卡片标题', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
),
// 2. 分隔线(可选)
Divider(height: 1, thickness: 1),
// 3. 内容主体
Padding(
padding: EdgeInsets.all(16),
child: Text('这里是卡片的主要内容区域...'),
),
// 4. 操作区域
ButtonBar(
alignment: MainAxisAlignment.end,
children: [
TextButton(onPressed: () {}, child: Text('取消')),
ElevatedButton(onPressed: () {}, child: Text('确定')),
],
),
],
),
)
2. 性能优化
dart
// 使用const减少重建
const Card(
child: ListTile(
title: Text('固定内容'),
),
)
// 复杂卡片提取为独立组件
class ProductCard extends StatelessWidget {
const ProductCard({super.key});
@override
Widget build(BuildContext context) {
return Card(/* ... */);
}
}
3. 主题一致性
dart
MaterialApp(
theme: ThemeData(
cardTheme: CardTheme(
elevation: 6,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
),
),
home: Scaffold(/* ... */),
)
4. 无障碍支持
dart
Card(
semanticContainer: true, // 将卡片作为语义容器
child: MergeSemantics(
child: ListTile(
title: Text('无障碍卡片'),
subtitle: Text('屏幕阅读器会朗读整个卡片内容'),
onTap: () {},
),
),
)
常见问题解决方案
1. 阴影效果不明显
dart
Card(
elevation: 8, // 增加阴影深度
shadowColor: Colors.black.withOpacity(0.5), // 加深阴影颜色
// ...
)
2. 圆角不生效
dart
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), // 设置圆角半径
),
// ...
)
3. 内容超出边界
dart
Card(
clipBehavior: Clip.antiAlias, // 裁剪超出内容
child: Column(
children: [
Image.asset('large-image.jpg'), // 可能超出边界
// ...
],
),
)
4. 响应式边距
dart
Card(
margin: EdgeInsets.symmetric(
vertical: 16,
horizontal: MediaQuery.of(context).size.width > 600 ? 40 : 16,
),
// ...
)
实际应用场景
1. 设置项卡片
dart
Card(
child: Column(
children: [
SwitchListTile(
title: Text('通知设置'),
value: _notificationsEnabled,
onChanged: (v) => setState(() => _notificationsEnabled = v),
),
Divider(height: 1),
SwitchListTile(
title: Text('夜间模式'),
value: _darkMode,
onChanged: (v) => setState(() => _darkMode = v),
),
],
),
)
2. 数据展示卡片
dart
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('今日数据', style: TextStyle(fontSize: 18)),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildDataItem(Icons.visibility, '访问量', '1,248'),
_buildDataItem(Icons.shopping_cart, '订单', '86'),
_buildDataItem(Icons.attach_money, '收入', '¥5,280'),
],
),
],
),
),
)
Widget _buildDataItem(IconData icon, String label, String value) {
return Column(
children: [
Icon(icon, size: 30, color: Colors.blue),
SizedBox(height: 8),
Text(label, style: TextStyle(color: Colors.grey)),
SizedBox(height: 4),
Text(value, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
],
);
}
3. 用户资料卡片
dart
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage('avatar.jpg'),
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('张小明', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
SizedBox(height: 4),
Text('高级UI设计师', style: TextStyle(color: Colors.grey)),
SizedBox(height: 8),
Wrap(
spacing: 8,
children: [
Chip(label: Text('UI设计')),
Chip(label: Text('插画')),
Chip(label: Text('动效')),
],
),
],
),
),
],
),
),
)
4. 天气信息卡片
dart
Card(
color: Colors.blue[700],
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('武汉市', style: TextStyle(fontSize: 24, color: Colors.white)),
Text('2023-07-15', style: TextStyle(color: Colors.white70)),
],
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
children: [
Text('28°', style: TextStyle(fontSize: 48, color: Colors.white)),
Text('多云', style: TextStyle(color: Colors.white)),
],
),
WeatherIcon(weather: 'cloudy', size: 80),
],
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('湿度: 65%', style: TextStyle(color: Colors.white)),
Text('风速: 3km/h', style: TextStyle(color: Colors.white)),
Text('气压: 1013hPa', style: TextStyle(color: Colors.white)),
],
),
],
),
),
)
提示:
- 使用
elevation
控制卡片层次感,但避免过度使用(建议 1-12 范围)- 结合
ClipRRect
实现图片圆角效果更自然- 使用
InkWell
或InkResponse
为卡片添加水波纹效果- 在暗色模式下调整卡片颜色:
color: Theme.of(context).cardColor
- 使用
Card
嵌套Card
创建分层设计效果- 遵循 Material Design 指南,卡片间距至少 8dp,内容内边距至少 16dp
CircleAvatar 组件
CircleAvatar 是一个圆形头像组件,用于展示用户头像、图标或文字,支持图片、图标、文字等多种内容形式,是用户界面中展示个人信息的核心组件。
核心属性表
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
backgroundColor | Color | 主题默认色 | 圆形背景颜色(当没有背景图片时) |
backgroundImage | ImageProvider | null | 背景图片(如 NetworkImage、AssetImage) |
foregroundImage | ImageProvider | null | 前景图片(覆盖在背景之上) |
child | Widget | null | 子组件(通常是文字或图标) |
radius | double | 20 | 圆形半径(定义大小) |
minRadius | double | null | 最小半径 |
maxRadius | double | null | 最大半径 |
onBackgroundImageError | Function | null | 背景图片加载失败的回调函数 |
onForegroundImageError | Function | null | 前景图片加载失败的回调函数 |
基础用法示例
1. 文字头像
dart
CircleAvatar(
radius: 30,
backgroundColor: Colors.blue,
child: Text(
'AB',
style: TextStyle(fontSize: 20, color: Colors.white),
),
)
2. 图片头像
dart
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
)
3. 图标头像
dart
CircleAvatar(
radius: 30,
backgroundColor: Colors.amber,
child: Icon(Icons.person, color: Colors.white),
)
4. 带边框的头像
dart
CircleAvatar(
radius: 40,
backgroundColor: Colors.white, // 边框颜色
child: CircleAvatar(
radius: 38,
backgroundImage: AssetImage('assets/avatar.jpg'),
),
)
高级用法与技巧
1. 加载失败处理
dart
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
onBackgroundImageError: (exception, stackTrace) {
// 图片加载失败时显示默认头像
debugPrint('头像加载失败: $exception');
},
child: Text('加载失败', style: TextStyle(fontSize: 12)), // 备用显示
)
2. 动态头像状态
dart
Stack(
clipBehavior: Clip.none,
children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(user.avatarUrl),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
padding: EdgeInsets.all(3),
decoration: BoxDecoration(
color: user.isOnline ? Colors.green : Colors.grey,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
width: 12,
height: 12,
),
),
],
)
3. 头像组合
dart
Stack(
children: [
CircleAvatar(
radius: 25,
backgroundColor: Colors.grey[300],
child: Text('+5', style: TextStyle(color: Colors.black)),
),
Positioned(
left: 15,
child: CircleAvatar(
radius: 25,
backgroundColor: Colors.blue[300],
child: Text('+4'),
),
),
Positioned(
left: 30,
child: CircleAvatar(
radius: 25,
backgroundColor: Colors.green[300],
child: Text('+3'),
),
),
// 更多头像...
],
)
4. 头像动画效果
dart
AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _isExpanded ? 100 : 50,
height: _isExpanded ? 100 : 50,
child: CircleAvatar(
backgroundImage: NetworkImage('avatar.jpg'),
),
)
最佳实践指南
1. 统一头像尺寸
dart
// 创建统一尺寸的头像组件
class AppAvatar extends StatelessWidget {
final String? imageUrl;
final String? text;
const AppAvatar({this.imageUrl, this.text});
@override
Widget build(BuildContext context) {
return CircleAvatar(
radius: 24,
backgroundColor: Theme.of(context).primaryColor,
backgroundImage: imageUrl != null ? NetworkImage(imageUrl!) : null,
child: text != null
? Text(text!.substring(0, 2),
: Icon(Icons.person),
);
}
}
2. 性能优化
dart
// 使用const减少重建
const CircleAvatar(
radius: 30,
child: Icon(Icons.person),
)
// 本地资源使用AssetImage
CircleAvatar(
backgroundImage: AssetImage('assets/avatar.png'),
)
// 网络图片使用缓存
CachedNetworkImage(
imageUrl: 'https://example.com/avatar.jpg',
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
),
placeholder: (context, url) => CircleAvatar(
backgroundColor: Colors.grey[200],
child: CircularProgressIndicator(),
),
errorWidget: (context, url, error) => CircleAvatar(
backgroundColor: Colors.grey[200],
child: Icon(Icons.error),
),
)
3. 主题一致性
dart
MaterialApp(
theme: ThemeData(
avatarTheme: AvatarThemeData(
backgroundColor: Colors.blue[800],
radius: 22,
),
),
)
4. 无障碍支持
dart
CircleAvatar(
backgroundImage: NetworkImage('avatar.jpg'),
child: ExcludeSemantics( // 隐藏文本内容
child: Text(user.name.substring(0, 2)),
),
semanticLabel: '${user.name}的头像', // 屏幕阅读器会朗读此内容
)
实际应用场景
1. 用户列表项
dart
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(user.avatarUrl),
),
title: Text(user.name),
subtitle: Text(user.email),
trailing: Icon(Icons.arrow_forward),
)
2. 聊天界面
dart
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 20,
backgroundImage: NetworkImage(message.sender.avatar),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(message.sender.name, style: TextStyle(fontWeight: FontWeight.bold)),
Text(message.content),
],
),
),
],
)
3. 个人资料页
dart
Column(
children: [
CircleAvatar(
radius: 60,
backgroundImage: NetworkImage(profile.avatar),
),
SizedBox(height: 16),
Text(profile.name, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
Text(profile.title, style: TextStyle(color: Colors.grey)),
],
)
4. 评论组件
dart
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(radius: 20, backgroundImage: NetworkImage(comment.user.avatar)),
SizedBox(width: 12),
Text(comment.user.name, style: TextStyle(fontWeight: FontWeight.bold)),
],
),
SizedBox(height: 12),
Text(comment.content),
SizedBox(height: 12),
Text(comment.timeAgo, style: TextStyle(color: Colors.grey, fontSize: 12)),
],
),
),
)
5. 头像选择器
dart
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: avatarOptions.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => _selectAvatar(avatarOptions[index]),
child: CircleAvatar(
radius: 40,
backgroundColor: _selectedIndex == index
? Colors.blue.withOpacity(0.3)
: Colors.transparent,
child: CircleAvatar(
radius: 36,
backgroundImage: AssetImage(avatarOptions[index]),
),
),
);
},
)
常见问题解决方案
1. 图片变形问题
dart
CircleAvatar(
backgroundImage: NetworkImage('avatar.jpg'),
child: ClipOval( // 确保圆形裁剪
child: Image.network(
'avatar.jpg',
fit: BoxFit.cover,
width: 80, // 需要指定宽高
height: 80,
),
),
)
2. 加载状态处理
dart
CircleAvatar(
radius: 30,
child: FutureBuilder(
future: _loadAvatar(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Icon(Icons.error);
}
return Image.memory(snapshot.data!);
},
),
)
3. 头像大小自适应
dart
LayoutBuilder(
builder: (context, constraints) {
double avatarSize = constraints.maxWidth > 600 ? 80 : 50;
return CircleAvatar(
radius: avatarSize,
backgroundImage: NetworkImage('avatar.jpg'),
);
},
)
4. 文字自动缩放
dart
CircleAvatar(
radius: 40,
backgroundColor: Colors.blue,
child: FittedBox( // 文字自适应缩放
fit: BoxFit.scaleDown,
child: Text(
user.initials,
style: TextStyle(fontSize: 40), // 初始大字体
),
),
)
提示:
- 使用
CachedNetworkImage
优化网络头像加载性能- 对于未知用户,使用姓名首字母 + 随机背景色方案
- 使用
Hero
动画实现头像详情页的平滑过渡- 在暗色模式下调整背景色:
backgroundColor: Theme.of(context).colorScheme.surfaceVariant
- 使用
Badge
组件添加通知徽章- 组合
Tooltip
提供用户信息提示- 重要用户头像添加
BoxShadow
提升视觉层次
Button 按钮组件
Flutter 提供了丰富的按钮组件体系,满足不同场景下的交互需求。以下是完整的按钮组件分类与详解:
按钮类型对比表
按钮类型 | 视觉风格 | 适用场景 | 核心组件 |
---|---|---|---|
凸起按钮 | 有阴影和背景色 | 主要操作、强调操作 | ElevatedButton |
文本按钮 | 无背景、仅文字 | 次要操作、对话框操作 | TextButton |
描边按钮 | 带边框、无填充 | 中等重要性操作、取消操作 | OutlinedButton |
图标按钮 | 仅图标 | 工具栏、紧凑空间操作 | IconButton |
悬浮按钮 | 圆形悬浮按钮 | 主屏幕主要操作 | FloatingActionButton |
下拉按钮 | 带下拉菜单 | 选项选择 | DropdownButton |
弹出菜单按钮 | 三点菜单 | 更多操作菜单 | PopupMenuButton |
自定义按钮 | 完全自定义样式 | 品牌化设计、特殊交互 | InkWell/GestureDetector |
一、基础按钮组件
1. ElevatedButton (凸起按钮)
dart
ElevatedButton(
onPressed: () {
print('按钮被点击');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, // 背景色
foregroundColor: Colors.white, // 文字/图标颜色
elevation: 5, // 阴影深度
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), // 内边距
shape: RoundedRectangleBorder( // 形状
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('确认提交'),
)
2. TextButton (文本按钮)
dart
TextButton(
onPressed: () {},
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
padding: EdgeInsets.all(12),
),
child: const Text('取消操作'),
)
3. OutlinedButton (描边按钮)
dart
OutlinedButton(
onPressed: () {},
style: OutlinedButton.styleFrom(
side: BorderSide(color: Colors.blue, width: 2), // 边框样式
shape: StadiumBorder(), // 圆角形状
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
child: const Text('查看详情'),
)
4. IconButton (图标按钮)
dart
IconButton(
onPressed: () {},
icon: Icon(Icons.favorite),
iconSize: 30,
color: Colors.red,
tooltip: '收藏', // 长按提示
)
5. FloatingActionButton (悬浮按钮)
dart
FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
elevation: 6,
shape: CircleBorder(), // 圆形
child: Icon(Icons.add),
)
二、高级按钮组件
1. DropdownButton (下拉按钮)
dart
String? _selectedValue = '选项1';
DropdownButton<String>(
value: _selectedValue,
onChanged: (newValue) {
setState(() => _selectedValue = newValue);
},
items: <String>['选项1', '选项2', '选项3']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
)
2. PopupMenuButton (弹出菜单按钮)
dart
PopupMenuButton<String>(
icon: Icon(Icons.more_vert),
itemBuilder: (context) => [
PopupMenuItem(value: 'edit', child: Text('编辑')),
PopupMenuItem(value: 'delete', child: Text('删除')),
],
onSelected: (value) {
if (value == 'delete') _deleteItem();
},
)
3. ButtonBar (按钮组)
dart
ButtonBar(
alignment: MainAxisAlignment.end, // 对齐方式
children: [
TextButton(onPressed: () {}, child: Text('取消')),
ElevatedButton(onPressed: () {}, child: Text('确定')),
],
)
三、按钮状态管理
1. 禁用状态
dart
ElevatedButton(
onPressed: isLoading ? null : _submitForm, // null表示禁用
child: isLoading
? CircularProgressIndicator(color: Colors.white)
: Text('提交'),
)
2. 加载状态
dart
bool _isLoading = false;
ElevatedButton(
onPressed: _isLoading ? null : _fetchData,
child: _isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Text('加载数据'),
)
3. 按钮状态样式
dart
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return Colors.grey; // 禁用状态
}
if (states.contains(MaterialState.pressed)) {
return Colors.blue[700]!; // 按下状态
}
return Colors.blue; // 默认状态
},
),
),
// ...
)
四、自定义按钮
1. 使用 InkWell 创建自定义按钮
dart
InkWell(
onTap: () {},
borderRadius: BorderRadius.circular(8),
splashColor: Colors.blue[100], // 水波纹颜色
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue),
),
child: Text('自定义按钮'),
),
)
2. 渐变按钮
dart
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero, // 清除默认内边距
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () {},
child: Ink(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.purple],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 15),
child: Text('渐变按钮', style: TextStyle(color: Colors.white)),
),
)
3. 图标+文字按钮
dart
ElevatedButton.icon(
onPressed: () {},
icon: Icon(Icons.cloud_upload),
label: Text('上传文件'),
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
),
)
五、最佳实践指南
1. 按钮尺寸规范
dart
// 小尺寸按钮
TextButton(
style: TextButton.styleFrom(padding: EdgeInsets.all(8)),
child: Text('小按钮'),
)
// 中尺寸按钮(默认)
ElevatedButton(child: Text('中按钮'),)
// 大尺寸按钮
ElevatedButton(
style: ElevatedButton.styleFrom(padding: EdgeInsets.all(16)),
child: Text('大按钮'),
)
2. 响应式按钮设计
dart
LayoutBuilder(
builder: (context, constraints) {
bool isMobile = constraints.maxWidth < 600;
return isMobile
? FloatingActionButton(onPressed: () {}, child: Icon(Icons.add))
: ElevatedButton.icon(
onPressed: () {},
icon: Icon(Icons.add),
label: Text('添加新项目'),
);
},
)
3. 按钮主题统一
dart
MaterialApp(
theme: ThemeData(
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
),
),
),
)
4. 按钮无障碍优化
dart
ElevatedButton(
onPressed: () {},
child: Text('确认'),
autofocus: true, // 自动获取焦点
focusNode: _focusNode, // 焦点控制
onFocusChange: (hasFocus) {
if (hasFocus) _announceAccessibility('确认按钮已聚焦');
},
)
六、常见问题解决方案
1. 按钮点击无响应
dart
// 原因:onPressed为null
ElevatedButton(
onPressed: _isEnabled ? _handlePress : null, // 正确管理状态
// ...
)
2. 按钮样式不生效
dart
// 解决方案:使用styleFrom代替已弃用属性
ElevatedButton(
style: ElevatedButton.styleFrom( // 正确方式
backgroundColor: Colors.red,
),
// ...
)
3. 按钮尺寸异常
dart
// 解决方案:包裹约束容器
SizedBox(
width: double.infinity, // 撑满宽度
height: 50, // 固定高度
child: ElevatedButton(child: Text('登录')),
)
4. 按钮水波纹效果超出边界
dart
Material(
borderRadius: BorderRadius.circular(10), // 设置裁剪边界
child: InkWell(
borderRadius: BorderRadius.circular(10), // 与Material一致
onTap: () {},
child: Container(
padding: EdgeInsets.all(16),
child: Text('自定义按钮'),
),
),
)
七、实际应用场景
1. 表单提交按钮组
dart
Column(
children: [
// 表单字段...
ButtonBar(
alignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('取消'),
),
ElevatedButton(
onPressed: _submitForm,
child: Text('保存设置'),
),
],
),
],
)
2. 购物车操作按钮
dart
Row(
children: [
IconButton(
icon: Icon(Icons.favorite_border),
onPressed: _toggleFavorite,
),
SizedBox(width: 16),
Expanded(
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
),
icon: Icon(Icons.shopping_cart),
label: Text('加入购物车'),
onPressed: _addToCart,
),
),
],
)
3. 社交登录按钮组
dart
Column(
children: [
OutlinedButton.icon(
icon: Image.asset('assets/google.png', width: 24),
label: Text('使用Google登录'),
onPressed: _signInWithGoogle,
),
SizedBox(height: 12),
OutlinedButton.icon(
icon: Image.asset('assets/facebook.png', width: 24),
label: Text('使用Facebook登录'),
onPressed: _signInWithFacebook,
),
],
)
4. 多功能悬浮按钮
dart
FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
child: Icon(Icons.add),
heroTag: 'main_fab', // 唯一标识
)
// 次级悬浮按钮
ScaleTransition(
scale: CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
),
child: FloatingActionButton(
heroTag: 'fab1', // 不同heroTag
mini: true, // 小型按钮
onPressed: () {},
child: Icon(Icons.camera),
),
)
提示:
- 使用
ButtonStyle
替代已弃用的按钮样式属性- 对于重要操作,使用
ElevatedButton
提高视觉优先级- 在长列表中使用
NotificationListener
优化滚动时按钮性能- 使用
Focus
和FocusTraversalGroup
管理按钮焦点顺序- 通过
MaterialStateProperty
创建响应不同状态的按钮样式- 在暗色模式下使用
ThemeData
自动适配按钮颜色- 为图标按钮添加
tooltip
属性提升无障碍访问体验
Wrap 组件
Flutter 的 Wrap
组件是一种流式布局(Flow Layout)工具,用于在空间不足时自动换行排列子组件,特别适合标签云、动态列表等场景。以下是其核心特性和使用指南:
属性 | 说明 | 默认值 |
---|---|---|
direction | 主轴方向(Axis.horizontal 水平 / Axis.vertical 垂直) | Axis.horizontal |
alignment | 主轴对齐方式(如 WrapAlignment.start 、.center 、.spaceEvenly ) | WrapAlignment.start |
spacing | 主轴方向子组件的间距 | 0.0 |
runSpacing | 交叉轴方向行/列的间距(控制换行后的间隔) | 0.0 |
runAlignment | 交叉轴的行/列对齐方式(需父容器高度足够才生效) | WrapAlignment.start |
crossAxisAlignment | 子组件在交叉轴的对齐方式(如 WrapCrossAlignment.start ) | WrapCrossAlignment.start |
children | 子组件列表 | 必填 |
💡 关键特性:
当主轴空间不足时,Wrap
会自动换行(水平布局换行,垂直布局换列),而Row
/Column
会溢出报错。
🛠️ 二、基本用法示例
水平标签布局(自动换行)
dart
Wrap(
spacing: 10.0, // 水平间距
runSpacing: 15.0, // 行间距
alignment: WrapAlignment.spaceEvenly,
children: List.generate(10, (index) =>
Chip(
label: Text("标签${index + 1}"),
backgroundColor: Colors.blue[100],
),
),
)
垂直流式布局
dart
Wrap(
direction: Axis.vertical, // 垂直主轴
runSpacing: 20.0,
children: [
Icon(Icons.star),
Icon(Icons.favorite),
// 更多子组件...
],
)
⚙️ 三、高级技巧
1. 响应式布局
根据屏幕宽度动态切换方向:
dart
Wrap(
direction: MediaQuery.of(context).size.width > 600
? Axis.horizontal
: Axis.vertical,
children: [/*...*/],
)
2. 嵌套组合
嵌套 Wrap
实现复杂流式结构:
dart
Wrap(
children: [
Wrap( // 内层垂直Wrap
direction: Axis.vertical,
children: [Chip(label: Text("A")), Chip(label: Text("B"))],
),
Chip(label: Text("主内容区")),
],
)
3. 与状态管理结合
动态添加/删除标签:
dart
List<String> tags = ["Flutter", "Dart"];
Wrap(
children: tags.map((tag) =>
InputChip(
label: Text(tag),
onDeleted: () => setState(() => tags.remove(tag)),
),
).toList(),
)
⚠️ 四、常见问题解决
1. 换行后行对齐不生效
原因:runAlignment
需父容器有额外空间才能生效。
方案:为父容器设置足够高度:
dart
Container(
height: 300, // 明确高度
child: Wrap(runAlignment: WrapAlignment.end, children: [...]),
)
2. 子组件间距异常
- 使用
spacing
控制主轴间距,runSpacing
控制行间距。 - 避免子组件自身外边距(如
Padding
),优先用Wrap
的间距属性。
3. 性能优化
- 子组件数量过大时,用
Wrap
替代嵌套Row/Column
避免布局溢出。 - 复杂子组件提取为
const
或独立Widget
减少重建。
🎯 五、应用场景
1. 标签选择器
dart
Wrap(
spacing: 8.0,
children: ["喜剧", "动作", "科幻"].map((genre) =>
FilterChip(
label: Text(genre),
selected: _selectedGenre == genre,
onSelected: (v) => setState(() => _selectedGenre = genre),
),
).toList(),
)
2. 自适应按钮组
dart
Wrap(
runSpacing: 10.0,
children: [
ElevatedButton(onPressed: () {}, child: Text("保存")),
OutlinedButton(onPressed: () {}, child: Text("取消")),
// 更多按钮...
],
)
3. 瀑布流图片布局
dart
Wrap(
spacing: 5.0,
runSpacing: 5.0,
children: imageUrls.map((url) =>
Image.network(url, width: 100, height: 100, fit: BoxFit.cover),
).toList(),
)
🔍 六、对比其他布局
组件 | 特点 | 适用场景 |
---|---|---|
Wrap | 自动换行,简单易用 | 标签、动态内容流 |
Flow | 精确控制子组件位置,性能更高但代码复杂 | 自定义复杂流式布局 |
Row/Column | 单行/列布局,溢出报错 | 线性固定内容 |
💡 提示:
对于大多数场景,Wrap
的简洁性和自动换行特性已足够;需要像素级控制时(如重叠布局),再考虑Flow
。
💎 综合示例
dart
Wrap(
spacing: 12,
runSpacing: 16,
alignment: WrapAlignment.center,
children: [
_buildCategory("音乐", Icons.music_note),
_buildCategory("电影", Icons.movie),
// 添加更多分类...
],
)
Widget _buildCategory(String name, IconData icon) {
return Column(
children: [
Icon(icon, size: 36),
SizedBox(height: 8),
Text(name),
],
);
}
通过灵活组合 Wrap
的属性,可轻松实现各类自适应布局,尤其适合内容动态生成的场景(如搜索关键词、用户兴趣标签等)。
BottomNavigationBar
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
items | List<BottomNavigationBarItem> | 必填 | 导航项集合(至少需要 2 个) |
iconSize | double | 24.0 | 导航图标大小 |
currentIndex | int | 0 | 当前选中项的索引 |
onTap | ValueChanged<int> | - | 点击导航项的回调函数 |
fixedColor | Color | 主题色 | 选中项的颜色 |
type | BottomNavigationBarType | fixed | 导航栏类型:fixed 或 shifting |
backgroundColor | Color | 主题背景色 | 导航栏背景颜色 |
elevation | double | 8.0 | 导航栏阴影高度 |
selectedFontSize | double | 14.0 | 选中项字体大小 |
unselectedFontSize | double | 12.0 | 未选中项字体大小 |
selectedItemColor | Color | fixedColor | 选中项颜色 |
unselectedItemColor | Color | 主题未选中色 | 未选中项颜色 |
selectedLabelStyle | TextStyle | - | 选中项标签样式 |
unselectedLabelStyle | TextStyle | - | 未选中项标签样式 |
showSelectedLabels | bool | true | 是否显示选中项标签 |
showUnselectedLabels | bool | type == fixed | 是否显示未选中项标签 |
mouseCursor | MouseCursor | SystemMouseCursors.click | 鼠标悬停效果 |
enableFeedback | bool | true | 是否启用触觉反馈 |
landscapeLayout | BottomNavigationBarLandscapeLayout | spread | 横屏布局模式 |
两种导航类型对比
特性 | Fixed 类型 | Shifting 类型 |
---|---|---|
导航项宽度 | 固定相同宽度 | 动态变化宽度 |
文字标签 | 始终显示 | 仅选中项显示 |
背景颜色 | 统一背景色 | 选中项背景色变化 |
动画效果 | 简单 | 丰富动效 |
适用场景 | 项较多 (>3) | 项较少 (3-4) |
显示效果 | ![]() | ![]() |
完整使用示例
dart
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '底部导航示例',
theme: ThemeData(primarySwatch: Colors.blue),
home: const MainScreen(),
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _currentIndex = 0;
final List<Widget> _pages = [
const Center(child: Text('首页', style: TextStyle(fontSize: 24))),
const Center(child: Text('搜索', style: TextStyle(fontSize: 24))),
const Center(child: Text('收藏', style: TextStyle(fontSize: 24))),
const Center(child: Text('个人', style: TextStyle(fontSize: 24))),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('底部导航示例')),
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
type: BottomNavigationBarType.fixed,
fixedColor: Colors.blue,
backgroundColor: Colors.white,
elevation: 10,
selectedFontSize: 14,
unselectedFontSize: 12,
showSelectedLabels: true,
showUnselectedLabels: true,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home_outlined),
activeIcon: Icon(Icons.home),
label: '首页',
),
BottomNavigationBarItem(
icon: Icon(Icons.search_outlined),
activeIcon: Icon(Icons.search),
label: '搜索',
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite_border),
activeIcon: Icon(Icons.favorite),
label: '收藏',
),
BottomNavigationBarItem(
icon: Icon(Icons.person_outline),
activeIcon: Icon(Icons.person),
label: '我的',
),
],
),
);
}
}
高级用法示例
1. 添加徽章提示
dart
BottomNavigationBarItem(
icon: Badge(
label: Text('3'),
backgroundColor: Colors.red,
child: const Icon(Icons.notifications_none),
),
activeIcon: Badge(
label: Text('3'),
backgroundColor: Colors.red,
child: const Icon(Icons.notifications),
),
label: '通知',
),
2. 横屏布局优化
dart
bottomNavigationBar: OrientationBuilder(
builder: (context, orientation) {
return BottomNavigationBar(
// 其他属性...
landscapeLayout: orientation == Orientation.landscape
? BottomNavigationBarLandscapeLayout.centered
: BottomNavigationBarLandscapeLayout.spread,
);
},
)
3. 状态管理集成
dart
// 使用Provider管理状态
final navProvider = Provider<int>((ref) => 0);
// 在组件中
currentIndex: ref.watch(navProvider),
onTap: (index) => ref.read(navProvider.notifier).state = index,
最佳实践建议
- 导航项数量:控制在 3-5 个之间,避免过多
- 标签文本:简洁明了(1-2 个单词)
- 图标选择:使用标准 Material 图标
- 视觉反馈:使用 activeIcon 提供状态变化
- 横屏适配:使用 landscapeLayout 优化横屏体验
- 无障碍支持:dart
BottomNavigationBarItem( icon: Icon(Icons.home), label: '首页', tooltip: '导航到首页', // 屏幕阅读器提示 )
FloatingActionButton
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
onPressed | VoidCallback | 必填 | 按钮点击回调函数 |
child | Widget | 通常为 Icon | 按钮内容(图标或文本) |
tooltip | String | - | 长按提示文本(无障碍支持) |
backgroundColor | Color | 主题色 | 按钮背景颜色 |
foregroundColor | Color | 白色 | 前景色(图标/文字颜色) |
elevation | double | 6.0 | 正常状态阴影高度 |
highlightElevation | double | 12.0 | 按下状态阴影高度 |
shape | ShapeBorder | CircleBorder() | 按钮形状(可自定义) |
heroTag | Object | - | Hero 动画标识(多个 FAB 时需要) |
mini | bool | false | 是否使用迷你尺寸 |
isExtended | bool | false | 是否为扩展模式(带文字) |
extendedIconLabelSpacing | double | 8.0 | 扩展模式图标与文字的间距 |
extendedPadding | EdgeInsetsGeometry | EdgeInsets.symmetric(horizontal: 16) | 扩展模式内边距 |
基础使用示例
1. 基本悬浮按钮
dart
FloatingActionButton(
onPressed: () {
// 按钮点击逻辑
print('FAB 被点击');
},
child: const Icon(Icons.add),
tooltip: '添加新项目',
)
2. 扩展型悬浮按钮(带文字)
dart
FloatingActionButton.extended(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('新建'),
backgroundColor: Colors.blue,
)
3. 迷你尺寸按钮
dart
FloatingActionButton(
mini: true,
onPressed: () {},
child: const Icon(Icons.edit),
)
4. 自定义形状按钮
dart
FloatingActionButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
onPressed: () {},
child: const Icon(Icons.star),
)
在 Scaffold 中的定位
位置常量
dart
Scaffold(
floatingActionButton: FloatingActionButton(...),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
)
位置常量 | 描述 |
---|---|
centerFloat | 底部中心悬浮(默认) |
endFloat | 底部右端悬浮 |
centerDocked | 底部中心嵌入(与 BottomAppBar 配合) |
endDocked | 底部右端嵌入 |
startFloat | 底部左端悬浮 |
startDocked | 底部左端嵌入 |
centerTop | 顶部中心悬浮 |
endTop | 顶部右端悬浮 |
与 BottomAppBar 配合实现凸起效果
dart
Scaffold(
extendBody: true, // 重要:确保内容延伸到导航栏下方
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
shape: const CircularNotchedRectangle(),
child: Container(height: 56),
),
)
高级用法与动画效果
1. 动态显示/隐藏动画
dart
bool _fabVisible = true;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: AnimatedOpacity(
opacity: _fabVisible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: FloatingActionButton(...),
),
body: NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
// 滚动时隐藏,停止时显示
setState(() => _fabVisible = notification.scrollDelta! < 0);
}
return false;
},
child: ListView.builder(...),
),
);
}
2. 多级悬浮按钮菜单
dart
bool _expanded = false;
Scaffold(
floatingActionButton: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_expanded) ...[
FloatingActionButton(
mini: true,
heroTag: 'btn1',
onPressed: () {},
child: const Icon(Icons.camera),
),
const SizedBox(height: 10),
FloatingActionButton(
mini: true,
heroTag: 'btn2',
onPressed: () {},
child: const Icon(Icons.image),
),
const SizedBox(height: 10),
],
FloatingActionButton(
heroTag: 'main',
onPressed: () => setState(() => _expanded = !_expanded),
child: AnimatedRotation(
turns: _expanded ? 0.125 : 0,
duration: const Duration(milliseconds: 300),
child: const Icon(Icons.add),
),
),
],
),
)
3. 按钮变形动画
dart
bool _isExtended = false;
FloatingActionButton(
onPressed: () {
setState(() => _isExtended = !_isExtended);
},
isExtended: _isExtended,
label: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: _isExtended
? const Text('保存更改')
: const SizedBox.shrink(),
),
icon: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: _isExtended
? const Icon(Icons.save)
: const Icon(Icons.edit),
),
)
主题与样式定制
全局主题设置
dart
MaterialApp(
theme: ThemeData(
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: Colors.deepPurple,
foregroundColor: Colors.white,
elevation: 8,
largeSizeConstraints: BoxConstraints.tightFor(width: 72, height: 72),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
),
)
按钮尺寸类型
类型 | 尺寸 (直径) | 说明 |
---|---|---|
标准 | 56.0 | 默认大小 |
迷你 | 40.0 | mini: true |
大型 | 72.0 | FloatingActionButton.large |
最佳实践与常见问题
最佳实践
- 单一主操作:每个屏幕只使用一个主要悬浮按钮
- 位置合理:避免遮挡重要内容
- 视觉层次:使用鲜艳颜色突出主要操作
- 响应式设计:在移动端和桌面端适配不同尺寸
- 无障碍支持:始终提供
tooltip
属性
解决常见问题
多个 FAB 的 heroTag 冲突
dart
Stack(
children: [
Positioned(
bottom: 16,
right: 16,
child: FloatingActionButton(
heroTag: 'fab1', // 唯一标识
onPressed: () {},
child: const Icon(Icons.camera),
),
),
Positioned(
bottom: 16,
right: 80,
child: FloatingActionButton(
heroTag: 'fab2', // 唯一标识
onPressed: () {},
child: const Icon(Icons.image),
),
),
],
)
按钮被键盘遮挡
dart
Scaffold(
resizeToAvoidBottomInset: false, // 防止键盘挤压
floatingActionButton: FloatingActionButton(...),
)
自定义位置计算
dart
Scaffold(
floatingActionButton: FloatingActionButton(...),
floatingActionButtonLocation: (context, child) {
final mediaQuery = MediaQuery.of(context);
final bottomPadding = mediaQuery.padding.bottom;
return Offset(
mediaQuery.size.width - 72, // 右侧位置
mediaQuery.size.height - kBottomNavigationBarHeight - bottomPadding - 72,
);
},
)
完整示例
dart
import 'package:flutter/material.dart';
void main() => runApp(const FABApp());
class FABApp extends StatelessWidget {
const FABApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FAB 示例',
theme: ThemeData(
primarySwatch: Colors.blue,
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: Colors.deepPurple,
foregroundColor: Colors.white,
),
),
home: const FABDemoScreen(),
);
}
}
class FABDemoScreen extends StatefulWidget {
const FABDemoScreen({super.key});
@override
State<FABDemoScreen> createState() => _FABDemoScreenState();
}
class _FABDemoScreenState extends State<FABDemoScreen> {
bool _expanded = false;
bool _fabVisible = true;
int _counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('悬浮按钮示例')),
body: NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
// 滚动时隐藏,停止时显示
setState(() => _fabVisible = notification.scrollDelta! < 0);
}
return false;
},
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) => ListTile(title: Text('项目 $index')),
),
),
floatingActionButton: AnimatedOpacity(
opacity: _fabVisible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
if (_expanded) ...[
FloatingActionButton.small(
heroTag: 'counter',
onPressed: () => setState(() => _counter++),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.add, size: 18),
Text('$_counter', style: const TextStyle(fontSize: 12)),
],
),
),
const SizedBox(height: 12),
FloatingActionButton.small(
heroTag: 'info',
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('这是信息按钮')));
},
child: const Icon(Icons.info),
),
const SizedBox(height: 12),
],
FloatingActionButton(
heroTag: 'main',
onPressed: () => setState(() => _expanded = !_expanded),
child: AnimatedRotation(
turns: _expanded ? 0.125 : 0,
duration: const Duration(milliseconds: 300),
child: const Icon(Icons.menu),
),
),
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
);
}
}
Drawer 组件全面指南
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
backgroundColor | Color | 主题背景色 | 抽屉背景颜色 |
elevation | double | 16.0 | 抽屉阴影高度 |
width | double | 304.0 | 抽屉宽度 |
shape | ShapeBorder | - | 抽屉形状(可自定义圆角) |
semanticLabel | String | - | 无障碍语义标签 |
child | Widget | 必填 | 抽屉内容(通常为 ListView) |
clipBehavior | Clip | Clip.hardEdge | 内容裁剪方式 |
surfaceTintColor | Color | - | 材料表面色调颜色(Material 3) |
基础使用示例
1. 基本抽屉实现
dart
Scaffold(
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
DrawerHeader(
decoration: BoxDecoration(color: Colors.blue),
child: Text('应用名称', style: TextStyle(color: Colors.white, fontSize: 24)),
ListTile(
leading: Icon(Icons.home),
title: Text('首页'),
onTap: () => Navigator.pop(context),
),
ListTile(
leading: Icon(Icons.settings),
title: Text('设置'),
onTap: () => Navigator.pop(context),
),
],
),
),
appBar: AppBar(title: Text('主页面')),
body: Center(child: Text('内容区域')),
)
2. 双抽屉实现(左侧+右侧)
dart
Scaffold(
drawer: Drawer(child: _buildLeftDrawer()), // 左侧抽屉
endDrawer: Drawer(child: _buildRightDrawer()), // 右侧抽屉
appBar: AppBar(
title: Text('双抽屉示例'),
leading: IconButton( // 左侧抽屉按钮
icon: Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
actions: [
IconButton( // 右侧抽屉按钮
icon: Icon(Icons.filter_list),
onPressed: () => Scaffold.of(context).openEndDrawer(),
),
],
),
body: // ...
)
Widget _buildLeftDrawer() {
return ListView(
children: [/* 导航菜单 */]
);
}
Widget _buildRightDrawer() {
return ListView(
children: [/* 过滤选项 */]
);
}
高级用法与自定义
1. 自定义抽屉头部
dart
DrawerHeader(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade700, Colors.blue.shade300],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage('avatar.jpg'),
),
SizedBox(height: 12),
Text('用户名', style: TextStyle(color: Colors.white, fontSize: 18)),
Text('user@example.com', style: TextStyle(color: Colors.white70)),
],
),
),
2. 抽屉状态管理
dart
// 使用Provider管理抽屉状态
final drawerProvider = StateProvider((ref) => false);
// 打开/关闭抽屉
void toggleDrawer(BuildContext context) {
final isOpen = ref.read(drawerProvider);
if (isOpen) {
Scaffold.of(context).closeDrawer();
} else {
Scaffold.of(context).openDrawer();
}
ref.read(drawerProvider.notifier).state = !isOpen;
}
// 监听抽屉状态
Scaffold(
drawer: Drawer(
child: Listener(
onPointerMove: (event) => print('抽屉正在拖动'),
child: // ...
),
),
)
3. 抽屉动画效果
dart
// 自定义抽屉打开动画
Scaffold(
drawer: Drawer(
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
curve: Curves.easeOutCubic,
transform: Matrix4.translationValues(
_drawerOpen ? 0 : -300, 0, 0),
child: // ...
),
),
)
// 抽屉项动画
ListTile(
leading: AnimatedRotation(
turns: _selectedItem == index ? 0.25 : 0,
duration: Duration(milliseconds: 300),
child: Icon(Icons.arrow_forward_ios),
),
title: Text('菜单项'),
),
抽屉内容最佳实践
1. 导航菜单结构
dart
ListView(
padding: EdgeInsets.zero,
children: [
UserAccountsDrawerHeader( // 预定义头部
accountName: Text("用户名"),
accountEmail: Text("user@example.com"),
currentAccountPicture: CircleAvatar(
backgroundImage: NetworkImage("avatar.jpg"),
),
),
Divider(),
_buildDrawerItem(Icons.home, '首页', 0),
_buildDrawerItem(Icons.favorite, '收藏', 1),
_buildDrawerItem(Icons.history, '历史记录', 2),
Divider(),
_buildDrawerItem(Icons.settings, '设置', 3),
_buildDrawerItem(Icons.help, '帮助中心', 4),
Spacer(), // 将内容推到底部
AboutListTile(
icon: Icon(Icons.info),
applicationName: "应用名称",
applicationVersion: "1.0.0",
),
],
)
Widget _buildDrawerItem(IconData icon, String title, int index) {
return ListTile(
leading: Icon(icon, color: _selectedIndex == index
? Theme.of(context).colorScheme.primary
: null),
title: Text(title),
selected: _selectedIndex == index,
onTap: () {
setState(() => _selectedIndex = index);
Navigator.pop(context);
},
);
}
2. 响应式抽屉设计
dart
LayoutBuilder(
builder: (context, constraints) {
bool isWideScreen = constraints.maxWidth > 600;
return Scaffold(
drawer: isWideScreen ? null : Drawer(child: _buildDrawerContent()),
body: Row(
children: [
if (isWideScreen) ...[
SizedBox(
width: 300,
child: Material(
elevation: 4,
child: _buildDrawerContent(),
),
),
],
Expanded(child: _buildMainContent()),
],
),
);
},
)
3. 暗色模式适配
dart
Drawer(
backgroundColor: Theme.of(context).drawerTheme.backgroundColor,
child: ListView(
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
child: // ...
),
ListTile(
leading: Icon(Icons.dark_mode),
title: Text('暗色模式'),
trailing: Switch(
value: Theme.of(context).brightness == Brightness.dark,
onChanged: (value) => _toggleTheme(value),
),
),
],
),
)
完整示例代码
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() => runApp(const ProviderScope(child: MyApp()));
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '抽屉示例',
theme: ThemeData(
primarySwatch: Colors.blue,
drawerTheme: DrawerThemeData(
backgroundColor: Colors.white,
width: 300,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
),
darkTheme: ThemeData.dark().copyWith(
drawerTheme: DrawerThemeData(
backgroundColor: Colors.grey[900],
),
),
home: const MainScreen(),
);
}
}
class MainScreen extends ConsumerWidget {
const MainScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentIndex = ref.watch(navIndexProvider);
return Scaffold(
appBar: AppBar(
title: const Text('抽屉导航示例'),
leading: Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
),
drawer: const AppDrawer(),
body: _buildPage(currentIndex),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (index) => ref.read(navIndexProvider.notifier).state = index,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.favorite), label: '收藏'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '个人'),
],
),
);
}
Widget _buildPage(int index) {
final pages = [
const Center(child: Text('首页内容')),
const Center(child: Text('收藏内容')),
const Center(child: Text('个人资料')),
];
return pages[index];
}
}
final navIndexProvider = StateProvider((ref) => 0);
class AppDrawer extends ConsumerWidget {
const AppDrawer({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentIndex = ref.watch(navIndexProvider);
return Drawer(
child: Column(
children: [
UserAccountsDrawerHeader(
accountName: const Text("张三"),
accountEmail: const Text("zhangsan@example.com"),
currentAccountPicture: CircleAvatar(
backgroundColor: Colors.white,
child: Text(
"张",
style: TextStyle(
fontSize: 24,
color: Theme.of(context).primaryColor,
),
),
),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
),
),
Expanded(
child: ListView(
padding: EdgeInsets.zero,
children: [
_buildDrawerItem(Icons.home, '首页', 0, currentIndex, ref),
_buildDrawerItem(Icons.favorite, '收藏', 1, currentIndex, ref),
_buildDrawerItem(Icons.history, '历史记录', 2, currentIndex, ref),
const Divider(),
_buildDrawerItem(Icons.settings, '设置', 3, currentIndex, ref),
_buildDrawerItem(Icons.help, '帮助中心', 4, currentIndex, ref),
],
),
),
const AboutListTile(
icon: Icon(Icons.info),
applicationName: "抽屉导航示例",
applicationVersion: "1.0.0",
applicationIcon: Icon(Icons.apps),
),
],
),
);
}
Widget _buildDrawerItem(IconData icon, String title, int index,
int currentIndex, WidgetRef ref) {
final isSelected = currentIndex == index;
return ListTile(
leading: Icon(icon, color: isSelected
? Theme.of(context).colorScheme.primary
: null),
title: Text(title),
selected: isSelected,
onTap: () {
ref.read(navIndexProvider.notifier).state = index;
Navigator.pop(context);
},
trailing: isSelected
? const Icon(Icons.check, color: Colors.green)
: null,
);
}
}
常见问题解决方案
1. 在无 Scaffold 上下文访问 Drawer
dart
// 使用 Builder 创建新上下文
Scaffold(
appBar: AppBar(
leading: Builder(
builder: (context) => IconButton(
icon: Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
),
drawer: Drawer(...),
)
2. 抽屉背景透明问题
dart
Drawer(
backgroundColor: Colors.white.withOpacity(0.95), // 半透明白色
// 或者
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
child: // ...
),
)
3. 抽屉内容滚动问题
dart
// 使用 SingleChildScrollView 或 ListView
Drawer(
child: SingleChildScrollView(
child: Column(
children: [/* 长内容 */],
),
),
)
4. 抽屉状态监听
dart
// 使用 DrawerController
final drawerController = DrawerController();
Scaffold(
drawer: Drawer(
controller: drawerController,
child: // ...
),
)
// 添加监听
drawerController.addListener(() {
if (drawerController.isOpen) {
print('抽屉已打开');
} else {
print('抽屉已关闭');
}
});
5. 全局控制抽屉
dart
// 使用 GlobalKey
final scaffoldKey = GlobalKey<ScaffoldState>();
Scaffold(
key: scaffoldKey,
drawer: Drawer(...),
)
// 在其他地方打开
scaffoldKey.currentState?.openDrawer();
UserAccountsDrawerHeader 组件
UserAccountsDrawerHeader
是专门为 Drawer 设计的预置头部组件,用于展示用户账户信息,提供了一种快速创建美观用户信息区域的方式。
核心属性详解
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
accountName | Widget | 必填 | 用户名展示组件(通常为 Text) |
accountEmail | Widget | 必填 | 用户邮箱展示组件 |
currentAccountPicture | Widget | - | 当前账户头像(通常为 CircleAvatar) |
otherAccountsPictures | List<Widget> | - | 其他账户头像列表 |
decoration | Decoration | 主题色背景 | 头部装饰(可设置背景图/渐变) |
margin | EdgeInsetsGeometry | EdgeInsets.only(bottom: 8.0) | 外边距 |
onDetailsPressed | VoidCallback | - | 点击整个头部时的回调函数 |
arrowColor | Color | 白色 | 右侧箭头的颜色 |
accountNameTextStyle | TextStyle | 主题文字样式 | 用户名字体样式 |
accountEmailTextStyle | TextStyle | 主题文字样式 | 邮箱字体样式 |
基础使用示例
1. 基本用户信息展示
dart
UserAccountsDrawerHeader(
accountName: Text("张三"),
accountEmail: Text("zhangsan@example.com"),
currentAccountPicture: CircleAvatar(
backgroundColor: Colors.white,
child: Text(
"张",
style: TextStyle(fontSize: 24, color: Colors.blue),
),
),
)
2. 带背景图的头部
dart
UserAccountsDrawerHeader(
accountName: Text("李四"),
accountEmail: Text("lisi@example.com"),
currentAccountPicture: CircleAvatar(
backgroundImage: NetworkImage("https://example.com/avatar.jpg"),
),
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage("https://example.com/background.jpg"),
fit: BoxFit.cover,
),
),
)
3. 多账户切换支持
dart
UserAccountsDrawerHeader(
accountName: Text("王五"),
accountEmail: Text("wangwu@company.com"),
currentAccountPicture: CircleAvatar(
backgroundImage: NetworkImage("https://example.com/work-avatar.jpg"),
),
otherAccountsPictures: [
CircleAvatar(
backgroundImage: NetworkImage("https://example.com/personal-avatar.jpg"),
),
CircleAvatar(
backgroundColor: Colors.grey,
child: Icon(Icons.add, color: Colors.white),
),
],
)
高级用法与自定义
1. 添加点击交互
dart
UserAccountsDrawerHeader(
accountName: Text("赵六"),
accountEmail: Text("zhaoliu@example.com"),
currentAccountPicture: CircleAvatar(
child: Icon(Icons.person),
),
onDetailsPressed: () {
Navigator.pop(context); // 关闭抽屉
Navigator.push(context, MaterialPageRoute(
builder: (context) => ProfileScreen() // 跳转到个人资料页
));
},
)
2. 自定义背景装饰
dart
UserAccountsDrawerHeader(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple.shade700, Colors.purple.shade300],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
// 其他属性...
)
3. 完全自定义样式
dart
UserAccountsDrawerHeader(
accountName: Text(
"钱七",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
shadows: [Shadow(color: Colors.black, blurRadius: 2)],
),
),
accountEmail: Text(
"qianqi@example.com",
style: TextStyle(fontStyle: FontStyle.italic),
),
currentAccountPicture: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
child: CircleAvatar(
backgroundImage: NetworkImage("https://example.com/avatar.jpg"),
),
),
arrowColor: Colors.amber,
)
在 Drawer 中的完整应用
dart
Drawer(
child: Column(
children: [
UserAccountsDrawerHeader(
accountName: Text("用户名"),
accountEmail: Text("user@example.com"),
currentAccountPicture: CircleAvatar(
backgroundImage: NetworkImage("avatar.jpg"),
),
decoration: BoxDecoration(
color: Colors.blue.shade700,
image: DecorationImage(
image: NetworkImage("background-pattern.jpg"),
fit: BoxFit.cover,
opacity: 0.2,
),
),
),
Expanded(
child: ListView(
padding: EdgeInsets.zero,
children: [
ListTile(
leading: Icon(Icons.home),
title: Text('首页'),
onTap: () {/* ... */},
),
ListTile(
leading: Icon(Icons.settings),
title: Text('设置'),
onTap: () {/* ... */},
),
// 更多列表项...
],
),
),
AboutListTile(
icon: Icon(Icons.info),
applicationName: "应用名称",
applicationVersion: "1.0.0",
),
],
),
)
响应式设计技巧
1. 根据屏幕尺寸调整布局
dart
LayoutBuilder(
builder: (context, constraints) {
bool isSmallScreen = constraints.maxWidth < 400;
return UserAccountsDrawerHeader(
accountName: Text("用户名",
style: TextStyle(fontSize: isSmallScreen ? 16 : 20)),
accountEmail: Text("user@example.com",
style: TextStyle(fontSize: isSmallScreen ? 12 : 14)),
currentAccountPicture: CircleAvatar(
radius: isSmallScreen ? 24 : 30,
// ...
),
);
},
)
2. 横竖屏适配
dart
OrientationBuilder(
builder: (context, orientation) {
return UserAccountsDrawerHeader(
margin: orientation == Orientation.portrait
? EdgeInsets.only(bottom: 8.0)
: EdgeInsets.zero,
// 其他属性...
);
},
)
暗色模式适配
dart
UserAccountsDrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
accountName: Text("用户名",
style: TextStyle(color: Theme.of(context).colorScheme.onPrimaryContainer)),
accountEmail: Text("user@example.com",
style: TextStyle(color: Theme.of(context).colorScheme.onPrimaryContainer.withOpacity(0.8))),
arrowColor: Theme.of(context).colorScheme.onPrimaryContainer,
// ...
)
最佳实践与技巧
头像优化:
- 使用
CachedNetworkImage
加载网络头像 - 提供占位符和错误处理
dartcurrentAccountPicture: CachedNetworkImage( imageUrl: "avatar.jpg", imageBuilder: (context, imageProvider) => CircleAvatar( backgroundImage: imageProvider, ), placeholder: (context, url) => CircularProgressIndicator(), errorWidget: (context, url, error) => Icon(Icons.error), ),
- 使用
动态数据绑定:
dartUserAccountsDrawerHeader( accountName: Text(userProvider.name), accountEmail: Text(userProvider.email), currentAccountPicture: CircleAvatar( backgroundImage: NetworkImage(userProvider.avatarUrl), ), )
无障碍支持:
dartUserAccountsDrawerHeader( accountName: Semantics( label: "用户名", child: Text("实际用户名"), ), // ... )
性能优化:
dartconst UserAccountsDrawerHeader( accountName: Text("固定用户名"), accountEmail: Text("固定邮箱"), // ... )
替代方案:自定义 DrawerHeader
如果需要更复杂的布局,可以直接使用 DrawerHeader
自定义:
dart
DrawerHeader(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade800, Colors.blue.shade600],
),
),
child: Row(
children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage("avatar.jpg"),
),
SizedBox(width: 16),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("用户名", style: TextStyle(color: Colors.white, fontSize: 18)),
SizedBox(height: 4),
Text("user@example.com", style: TextStyle(color: Colors.white70)),
SizedBox(height: 8),
OutlinedButton(
onPressed: () {},
child: Text("查看资料", style: TextStyle(color: Colors.white)),
style: OutlinedButton.styleFrom(
side: BorderSide(color: Colors.white),
),
),
],
),
],
),
)
完整示例
dart
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:provider/provider.dart';
void main() => runApp(
ChangeNotifierProvider(
create: (context) => UserProfile(),
child: const MyApp(),
),
);
class UserProfile with ChangeNotifier {
String name = "张三";
String email = "zhangsan@example.com";
String avatarUrl = "https://example.com/avatar.jpg";
void updateProfile(String newName, String newEmail) {
name = newName;
email = newEmail;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '用户抽屉头部示例',
theme: ThemeData(
primarySwatch: Colors.blue,
drawerTheme: DrawerThemeData(
backgroundColor: Colors.white,
),
),
darkTheme: ThemeData.dark(),
home: const MainScreen(),
);
}
}
class MainScreen extends StatelessWidget {
const MainScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('用户抽屉示例')),
drawer: const AppDrawer(),
body: const Center(child: Text('主内容区域')),
);
}
}
class AppDrawer extends StatelessWidget {
const AppDrawer({super.key});
@override
Widget build(BuildContext context) {
final userProfile = Provider.of<UserProfile>(context);
final isDark = Theme.of(context).brightness == Brightness.dark;
return Drawer(
child: Column(
children: [
UserAccountsDrawerHeader(
accountName: Text(
userProfile.name,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
accountEmail: Text(
userProfile.email,
style: TextStyle(
color: isDark ? Colors.white70 : Colors.black54,
),
),
currentAccountPicture: GestureDetector(
onTap: () => _editProfile(context, userProfile),
child: Hero(
tag: 'user-avatar',
child: CircleAvatar(
backgroundColor: Colors.grey.shade200,
child: CachedNetworkImage(
imageUrl: userProfile.avatarUrl,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
placeholder: (context, url) => const CircularProgressIndicator(),
errorWidget: (context, url, error) => const Icon(Icons.person),
),
),
),
),
decoration: BoxDecoration(
color: isDark
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).primaryColor,
image: const DecorationImage(
image: NetworkImage("https://example.com/background-pattern.png"),
fit: BoxFit.cover,
opacity: 0.1,
),
),
onDetailsPressed: () => _editProfile(context, userProfile),
arrowColor: isDark ? Colors.white : Colors.white,
),
Expanded(
child: ListView(
padding: EdgeInsets.zero,
children: [
_buildDrawerItem(Icons.home, '首页'),
_buildDrawerItem(Icons.favorite, '收藏'),
_buildDrawerItem(Icons.history, '历史记录'),
const Divider(),
_buildDrawerItem(Icons.settings, '设置'),
_buildDrawerItem(Icons.help, '帮助中心'),
],
),
),
const AboutListTile(
icon: Icon(Icons.info),
applicationName: "用户抽屉示例",
applicationVersion: "1.0.0",
),
],
),
);
}
Widget _buildDrawerItem(IconData icon, String title) {
return ListTile(
leading: Icon(icon),
title: Text(title),
onTap: () {},
);
}
void _editProfile(BuildContext context, UserProfile userProfile) {
Navigator.pop(context); // 关闭抽屉
showModalBottomSheet(
context: context,
builder: (context) {
String tempName = userProfile.name;
String tempEmail = userProfile.email;
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('编辑资料', style: Theme.of(context).textTheme.titleLarge),
TextField(
decoration: const InputDecoration(labelText: '用户名'),
onChanged: (value) => tempName = value,
controller: TextEditingController(text: userProfile.name),
),
TextField(
decoration: const InputDecoration(labelText: '邮箱'),
onChanged: (value) => tempEmail = value,
controller: TextEditingController(text: userProfile.email),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
userProfile.updateProfile(tempName, tempEmail);
Navigator.pop(context);
},
child: const Text('保存'),
),
],
),
);
},
);
}
}
UserAccountsDrawerHeader
提供了快速构建专业用户信息区域的能力,特别适合需要展示用户账户信息的应用场景。通过合理利用其属性和自定义选项,可以创建出既美观又实用的抽屉头部,提升应用的整体用户体验。
AppBar
AppBar
是 Flutter 应用的标准顶部栏组件,常用于显示标题、操作按钮和导航元素。
核心属性
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
title | Widget | - | 标题组件(通常为 Text) |
actions | List<Widget> | [] | 右侧操作按钮列表 |
bottom | PreferredSizeWidget | - | 底部组件(通常放置 TabBar) |
backgroundColor | Color | 主题色 | 背景颜色 |
elevation | double | 4.0 | 阴影高度 |
leading | Widget | - | 左侧导航元素(如返回按钮) |
基本用法
dart
AppBar(
title: Text('我的应用'),
actions: [
IconButton(icon: Icon(Icons.search), onPressed: () {}),
IconButton(icon: Icon(Icons.settings), onPressed: () {}),
],
)
TabBar
TabBar
是一个水平标签导航组件,通常嵌入在 AppBar 的 bottom 属性中。
核心属性
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
tabs | List<Widget> | 必填 | Tab 组件列表 |
controller | TabController | - | 标签控制器 |
isScrollable | bool | false | 标签过多时是否可滚动 |
indicatorColor | Color | 主题色 | 指示器颜色 |
labelColor | Color | 主题色 | 选中标签文字颜色 |
unselectedLabelColor | Color | 灰色 | 未选中标签文字颜色 |
indicatorWeight | double | 2.0 | 指示器厚度 |
Tab 组件属性
dart
Tab(
icon: Icon(Icons.flight), // 图标
text: "航班", // 文字
child: Text('航班'), // 自定义子组件
)
TabBarView
TabBarView
用于显示与 TabBar 标签对应的内容区域,支持左右滑动切换。
核心属性
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
children | List<Widget> | 必填 | 标签对应的内容组件列表 |
controller | TabController | - | 必须与 TabBar 的控制器一致 |
physics | ScrollPhysics | - | 滚动物理特性 |
特殊属性
- physics:
NeverScrollableScrollPhysics()
:禁用滑动BouncingScrollPhysics()
:弹性滚动效果
AppBar + TabBar + TabBarView 三者协同使用
AppBar + TabBar + TabBarView 构成完整的标签式布局体系。
基本结构
dart
Scaffold(
appBar: AppBar(
bottom: TabBar(), // 嵌入TabBar
),
body: TabBarView(), // 内容区域
)
完整示例
dart
import 'package:flutter/material.dart';
void main() => runApp(const TabLayoutDemo());
class TabLayoutDemo extends StatelessWidget {
const TabLayoutDemo({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController( // 自动管理Tab状态
length: 3, // 标签数量
child: Scaffold(
appBar: AppBar(
title: const Text('旅行预订'),
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.flight), text: "航班"),
Tab(icon: Icon(Icons.train), text: "火车"),
Tab(icon: Icon(Icons.directions_car), text: "自驾"),
],
indicatorColor: Colors.white,
labelColor: Colors.white,
),
),
body: TabBarView(
children: [
_buildPage("航班预订页面", Colors.blue[100]!),
_buildPage("火车票查询", Colors.green[100]!),
_buildPage("租车服务", Colors.orange[100]!),
],
),
),
),
);
}
Widget _buildPage(String msg, Color color) {
return Container(
color: color,
child: Center(
child: Text(
msg,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
);
}
}
高级用法
1. 手动控制 TabController
dart
class _TabDemoState extends State<TabDemo>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabController,
tabs: [/*...*/],
),
),
body: TabBarView(
controller: _tabController,
children: [/*...*/],
),
floatingActionButton: FloatingActionButton(
onPressed: () => _tabController.animateTo(2), // 切换到第三个标签
child: Icon(Icons.swap_horiz),
),
);
}
}
2. 自定义标签样式
dart
TabBar(
tabs: [/*...*/],
indicator: BoxDecoration(
color: Colors.deepPurple,
borderRadius: BorderRadius.circular(10),
),
labelStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold
),
unselectedLabelColor: Colors.grey,
)
3. 带图标的标签
dart
TabBar(
tabs: [
Tab(icon: Icon(Icons.chat), text: "聊天"),
Tab(icon: Icon(Icons.contacts), text: "联系人"),
Tab(icon: Icon(Icons.settings), text: "设置"),
],
)
常见问题
1. 标签与内容数量不一致
错误信息: 'package:flutter/src/material/tab_controller.dart': Failed assertion: 'length == tabs.length'
解决方案: 确保 DefaultTabController
的 length 与 TabBar 的 tabs 数量和 TabBarView 的 children 数量完全一致。
2. 嵌套滚动冲突
现象:
TabBarView 内部有可滚动组件时,滑动冲突。
解决方案:
dart
TabBarView(
physics: NeverScrollableScrollPhysics(), // 禁用TabBarView滑动
children: [
ListView(/*...*/), // 内部组件自行处理滚动
],
)
3. 动态更新标签
需求: 运行时添加/删除标签
解决方案:
dart
// 1. 使用StatefulWidget管理状态
// 2. 在TabController初始化时添加监听
_tabController = TabController(length: _tabs.length, vsync: this);
_tabController.addListener(_handleTabChange);
// 3. 更新数据后重建
setState(() {
_tabs.add(Tab(text: "新标签"));
_tabController = TabController(
length: _tabs.length,
vsync: this,
initialIndex: _tabController.index
);
});
4. 自定义标签布局
使用 CustomScrollView
+ SliverAppBar
实现复杂布局:
dart
CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(/*...*/),
bottom: TabBar(/*...*/),
),
SliverFillRemaining(
child: TabBarView(/*...*/),
)
],
)
提示:使用
DefaultTabController
可简化状态管理,适合大多数基础场景。需要复杂交互时使用手动控制方式。
preferredSize 组件
- PreferredSize 可以改变 appBar 的高度
1. 自定义 AppBar 底部组件
当需要创建自定义的 AppBar 底部组件时,必须使用 PreferredSize
包装:
dart
AppBar(
title: Text('自定义 AppBar'),
bottom: PreferredSize(
preferredSize: Size.fromHeight(80.0),
child: Container(
height: 80,
color: Colors.blue,
child: Center(
child: Text('自定义底部组件'),
),
),
),
)
2. 创建自定义 TabBar
实现自定义样式的 TabBar:
dart
PreferredSize(
preferredSize: Size.fromHeight(60.0),
child: Container(
decoration: BoxDecoration(
color: Colors.deepPurple,
borderRadius: BorderRadius.circular(30),
),
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: TabBar(
tabs: [
Tab(text: '首页'),
Tab(text: '发现'),
Tab(text: '我的'),
],
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.purpleAccent,
),
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
),
),
)
3. 固定高度的标题栏
创建固定高度的自定义标题栏:
dart
PreferredSize(
preferredSize: Size.fromHeight(100.0),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.purple],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Padding(
padding: const EdgeInsets.only(top: 40.0),
child: Center(
child: Text(
'应用标题',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
),
)
完整示例:自定义 AppBar
dart
import 'package:flutter/material.dart';
void main() => runApp(const CustomAppBarDemo());
class CustomAppBarDemo extends StatelessWidget {
const CustomAppBarDemo({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(150.0), // 自定义高度
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.deepPurple, Colors.purpleAccent],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
offset: Offset(0, 4),
)
],
),
child: SafeArea(
child: Column(
children: [
// 顶部状态栏区域
SizedBox(height: MediaQuery.of(context).padding.top),
// 标题区域
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.menu, color: Colors.white),
onPressed: () {},
),
const Expanded(
child: Text(
'我的应用',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
IconButton(
icon: const Icon(Icons.search, color: Colors.white),
onPressed: () {},
),
],
),
),
// 标签栏
const PreferredSize(
preferredSize: Size.fromHeight(48.0),
child: TabBar(
tabs: [
Tab(text: '推荐'),
Tab(text: '热门'),
Tab(text: '最新'),
],
indicatorColor: Colors.white,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
),
),
],
),
),
),
),
body: TabBarView(
children: [
_buildPage('推荐内容', Colors.blue[100]!),
_buildPage('热门内容', Colors.green[100]!),
_buildPage('最新内容', Colors.orange[100]!),
],
),
),
);
}
Widget _buildPage(String msg, Color color) {
return Container(
color: color,
child: Center(
child: Text(
msg,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
);
}
}
高级用法
响应式 PreferredSize
根据屏幕尺寸动态调整首选大小:
dart
PreferredSize(
preferredSize: Size(
double.infinity,
MediaQuery.of(context).size.height * 0.15,
),
child: // ...
)
与 SliverAppBar 结合使用
在 CustomScrollView 中使用:
dart
CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('弹性标题'),
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(50.0),
child: TabBar(
tabs: [/* ... */],
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('项目 $index')),
childCount: 20,
),
),
],
)
阴影效果被裁剪
问题:自定义 AppBar 的阴影效果被裁剪
解决方案:在容器外部添加阴影,而不是在内部
dart
PreferredSize(
preferredSize: Size.fromHeight(100),
child: Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(color: Colors.black26, blurRadius: 10, spreadRadius: 2)
],
),
child: // 实际内容
),
)
GetX Dialog
基础对话框
默认对话框
dart
Get.defaultDialog(
title: "提示",
middleText: "确定要删除这条记录吗?",
textConfirm: "确定",
textCancel: "取消",
confirmTextColor: Colors.white,
onConfirm: () {
// 确认操作
Get.back(result: true); // 返回结果
},
onCancel: () {
// 取消操作
Get.back(result: false);
},
);
核心参数
参数 | 类型 | 默认值 | 说明 |
---|---|---|---|
title | String | - | 对话框标题 |
titleStyle | TextStyle | - | 标题样式 |
middleText | String | - | 主要内容文本 |
middleTextStyle | TextStyle | - | 内容文本样式 |
textConfirm | String | "确认" | 确认按钮文本 |
textCancel | String | "取消" | 取消按钮文本 |
onConfirm | VoidCallback | - | 确认按钮回调 |
onCancel | VoidCallback | - | 取消按钮回调 |
confirmTextColor | Color | - | 确认按钮文字颜色 |
cancelTextColor | Color | - | 取消按钮文字颜色 |
backgroundColor | Color | Colors.white | 对话框背景色 |
radius | double | 20.0 | 对话框圆角半径 |
barrierDismissible | bool | true | 点击背景是否关闭对话框 |
自定义对话框
完全自定义对话框
dart
Get.dialog(
AlertDialog(
title: Text("自定义对话框"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(decoration: InputDecoration(labelText: "输入内容")),
SizedBox(height: 20),
Text("这是自定义内容区域"),
],
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text("取消"),
),
TextButton(
onPressed: () => Get.back(result: "提交结果"),
child: Text("提交"),
),
],
),
barrierDismissible: false,
);
使用 GetBuilder 自定义
dart
class CustomDialog extends StatelessWidget {
const CustomDialog({super.key});
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.info, color: Colors.blue, size: 50),
SizedBox(height: 20),
Text("自定义对话框内容", style: TextStyle(fontSize: 18)),
SizedBox(height: 20),
GetBuilder<DialogController>(
init: DialogController(),
builder: (controller) {
return ElevatedButton(
onPressed: controller.updateCount,
child: Text("点击计数: ${controller.count}"),
);
},
)
],
),
),
);
}
}
class DialogController extends GetxController {
int count = 0;
void updateCount() {
count++;
update();
}
}
// 使用
Get.dialog(CustomDialog());
对话框类型
加载对话框
dart
// 显示加载框
void showLoading() {
Get.dialog(
Center(
child: CircularProgressIndicator(),
),
barrierDismissible: false,
);
}
// 关闭加载框
void hideLoading() {
if (Get.isDialogOpen!) Get.back();
}
底部对话框
dart
Get.bottomSheet(
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.camera),
title: Text("拍照"),
onTap: () {
Get.back(result: "camera");
},
),
ListTile(
leading: Icon(Icons.photo),
title: Text("从相册选择"),
onTap: () {
Get.back(result: "gallery");
},
),
ListTile(
leading: Icon(Icons.close),
title: Text("取消"),
onTap: () => Get.back(),
),
],
),
),
);
全屏对话框
dart
Get.dialog(
Scaffold(
appBar: AppBar(
title: Text("全屏对话框"),
leading: IconButton(
icon: Icon(Icons.close),
onPressed: () => Get.back(),
),
),
body: Center(
child: Text("这是一个全屏对话框"),
),
),
barrierDismissible: false,
);
对话框控制
检查对话框状态
dart
if (Get.isDialogOpen!) {
print("对话框已打开");
}
关闭对话框
dart
Get.back(); // 关闭当前对话框
Get.back(result: "返回值"); // 关闭并返回结果
等待对话框结果
dart
final result = await Get.dialog(MyDialog());
print("对话框返回结果: $result");
全局对话框管理
dart
// 显示对话框
Get.dialog(MyDialog(), id: "myDialog");
// 检查特定对话框
if (Get.isDialogOpen("myDialog")) {
// 关闭特定对话框
Get.back(id: "myDialog");
}
高级用法
对话框动画
dart
Get.dialog(
MyDialog(),
transitionDuration: Duration(milliseconds: 500),
transitionCurve: Curves.easeInOut,
barrierColor: Colors.black54,
);
对话框主题
dart
Get.dialog(
AlertDialog(
backgroundColor: Get.theme.cardColor,
title: Text("主题对话框", style: Get.textTheme.headline6),
content: Text("使用当前主题样式", style: Get.textTheme.bodyText2),
),
);
国际化对话框
dart
Get.dialog(
AlertDialog(
title: Text("dialog_title".tr),
content: Text("dialog_message".tr),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text("cancel".tr),
),
TextButton(
onPressed: () => Get.back(result: true),
child: Text("confirm".tr),
),
],
),
);
对话框路由中间件
dart
class DialogMiddleware extends GetMiddleware {
@override
RouteSettings? redirect(String? route) {
if (needsLogin && !isLoggedIn) {
Get.dialog(LoginDialog()); // 显示登录对话框
return null; // 阻止原始路由
}
return super.redirect(route);
}
}
常见问题
1. 对话框无法显示
原因:
- 未使用
GetMaterialApp
- 在
build
方法中同步调用
解决方案:
dart
// 正确调用方式
void showMyDialog() {
WidgetsBinding.instance.addPostFrameCallback((_) {
Get.dialog(MyDialog());
});
}
2. 对话框内状态管理
问题: 对话框内状态无法更新
解决方案:
dart
// 使用 GetBuilder 或 GetX
GetBuilder<DialogController>(
init: DialogController(),
builder: (controller) {
return Text("计数: ${controller.count}");
},
)
3. 嵌套对话框关闭问题
问题: Get.back()
关闭了所有对话框
解决方案:
dart
// 为每个对话框指定唯一ID
Get.dialog(Dialog1(), id: "dialog1");
Get.dialog(Dialog2(), id: "dialog2");
// 关闭指定对话框
Get.back(id: "dialog1");
4. 对话框位置不正确
问题: 对话框出现在屏幕底部
原因: 在 Scaffold 的 body 中调用
解决方案:
dart
// 在按钮事件中调用,而不是在 build 方法中
ElevatedButton(
onPressed: () => Get.dialog(MyDialog()),
child: Text("打开对话框"),
)
5. 对话框内存泄漏
问题: 控制器未正确释放
解决方案:
dart
Get.dialog(
GetBuilder<DialogController>(
init: DialogController(),
dispose: (_) => Get.delete<DialogController>(), // 明确释放
builder: (controller) => ...,
),
);
6. 对话框自适应大小
dart
Get.dialog(
Dialog(
insetPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 40),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: 600, // 最大宽度
maxHeight: MediaQuery.of(Get.context!).size.height * 0.8,
),
child: ...,
),
),
);