Skip to content

Flutter 组件 ​

Flutter 一切皆 Widget——从最基础的 Container、Text、Image,到 Row/Column/Stack 布局,再到AppBar/Drawer/TabBar 等成品组件,都是通过嵌套 Widget 树声明式地拼出界面。本篇按「基础展示 → 布局排列 → 导航与骨架 → 交互反馈」四条主线,系统梳理常用组件的属性、用法与避坑要点,配代码可直接复用。

导航目录 ​

基础展示篇(内容承载) ​

布局排列篇(组件如何摆放) ​

卡片与头像篇(成品展示组件) ​

导航与骨架篇(页面结构) ​

交互反馈篇(按钮与对话框) ​

Container 容器组件 ​

一切皆 Widget:从 Container 理解 Flutter 组合思想

Flutter 界面由 Widget 层层嵌套而成,Container 是最常用的「万能盒子」——它把对齐、装饰、间距、尺寸、变换等能力集于一身,本质是多个单一职责 Widget 的组合封装。

text
Container(万能盒子)
    ├── alignment    对齐子元素
    ├── decoration   背景/边框/圆角/阴影/渐变
    ├── margin       外间距
    ├── padding      内边距
    ├── transform    矩阵变换
    ├── width/height 尺寸
    └── child        唯一子 Widget

只需一个子 Widget(child)——要放多个请配合 Column/Row/Stack。

属性名类型说明示例
alignmentAlignmentGeometry子元素对齐方式topCenter/topLeft/topRight
center/centerLeft/centerRight
bottomCenter/bottomLeft/bottomRight
decorationBoxDecoration容器装饰样式(背景/边框/阴影等)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>
marginEdgeInsetsGeometry容器外间距(与外部组件的距离)EdgeInsets.all(20.0)
EdgeInsets.only(top: 10)
paddingEdgeInsetsGeometry容器内边距(边缘与子元素的间距)EdgeInsets.all(10.0)
EdgeInsets.symmetric(horizontal: 15)
transformMatrix4矩阵变换(旋转/缩放/平移等)Matrix4.rotationZ(0.2)
Matrix4.translationValues(10, 0, 0)
heightdouble容器高度height: 100.0
widthdouble容器宽度width: 200.0
childWidget子元素内容child: Text("Hello")
child: Icon(Icons.star)

关键说明: ​

  1. alignment 使用 Alignment 类的静态常量控制子元素位置,如:

    dart
    alignment: Alignment.topCenter  // 顶部居中
  2. decoration 支持嵌套多种样式效果:

    • color:背景色(与 gradient 冲突)
    • border:边框样式
    • borderRadius:圆角半径
    • boxShadow:阴影效果
    • gradient:线性/径向渐变背景
  3. margin & padding 使用 EdgeInsets 类定义间距,常用构造方法:

    dart
    EdgeInsets.all(20)       // 全方向
    EdgeInsets.only(left:10) // 指定方向
    EdgeInsets.symmetric(vertical: 10) // 对称方向
  4. transform 通过 4x4 矩阵实现 3D 变换,常用操作:

    dart
    Matrix4.rotationZ(angle) // Z轴旋转
    Matrix4.translationValues(x, y, z) // 平移

Text 组件属性详解 ​

属性名类型说明可选值/示例
textAlignTextAlign文本水平对齐方式TextAlign.center(居中)
TextAlign.left(左对齐)
TextAlign.right(右对齐)
TextAlign.justify(两端对齐)
textDirectionTextDirection文本阅读方向TextDirection.ltr(从左至右)
TextDirection.rtl(从右至左)
overflowTextOverflow文本溢出处理方式TextOverflow.clip(裁剪)
TextOverflow.fade(渐隐)
TextOverflow.ellipsis(省略号)
textScaleFactordouble字体缩放比例(基于系统字体大小)1.0(默认大小)
1.2(放大 20%)
0.8(缩小 20%)
maxLinesint最大显示行数1(单行显示)
3(最多 3 行)
null(无限制)
styleTextStyle文本样式设置(支持嵌套多种样式)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>)

关键属性详解: ​

  1. textAlign
    控制文本在水平方向的对齐方式:

    dart
    Text('Hello World', textAlign: TextAlign.center)
  2. textDirection
    影响对齐方向和溢出行为:

    dart
    Text('مرحبا', textDirection: TextDirection.rtl) // 阿拉伯语从右向左
  3. overflow 与 maxLines 配合使用
    常见组合:

    dart
    Text('长文本内容...',
      maxLines: 1,
      overflow: TextOverflow.ellipsis // 单行省略
    )
  4. textScaleFactor
    响应系统字体设置:

    dart
    Text('可缩放的文本', textScaleFactor: 1.2)
  5. style
    常用样式属性建议优先用 TextStyle 管理并复用(如通过 copyWith())。

TextStyle 常用属性速查 ​

样式属性类型/值说明
colorColor文字颜色
fontSizedouble字体大小
fontWeightFontWeight.w100-w900字体粗细
fontStyleFontStyle.normal / italic正常 / 斜体
letterSpacingdouble字符间距
decorationTextDecoration.underline / lineThrough下划线 / 删除线
fontFamilyString自定义字体

Text 最佳实践

  • 多语言文本务必设置正确的 textDirection
  • 长文本推荐组合使用 maxLines 和 overflow
  • 使用 textScaleFactor 保持 UI 可访问性
  • 通过 style 的 copyWith() 复用文本样式:
dart
TextStyle baseStyle = TextStyle(fontSize: 16);
Text('标题', style: baseStyle.copyWith(fontWeight: FontWeight.bold))

Image 组件属性详解 ​

  • Image.asset:本地资源图片(需要在 pubspec.yaml 声明)
  • Image.network:网络图片(可配合缓存库使用)
属性名类型说明可选值/示例
alignmentAlignment图片在容器内的对齐方式Alignment.topCenter
Alignment.bottomLeft
Alignment(-0.5, 0.5)
color
colorBlendMode
Color
BlendMode
颜色叠加与混合模式(需配合使用)dart<br>color: Colors.green,<br>colorBlendMode: BlendMode.difference
fitBoxFit图片填充方式BoxFit.fill
BoxFit.contain
BoxFit.cover
BoxFit.fitWidth
BoxFit.fitHeight
BoxFit.scaleDown
repeatImageRepeat图片平铺方式ImageRepeat.repeat
ImageRepeat.repeatX
ImageRepeat.repeatY
ImageRepeat.noRepeat
widthdouble图片宽度(常配合圆形裁剪使用)width: 120.0
heightdouble图片高度(常配合圆形裁剪使用)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,
  )
)

最佳实践: ​

  1. 混合模式选择:

    • 使用color属性实现单色图标
    • 通过colorBlendMode创建特殊滤镜效果
  2. 响应式图片处理:

    dart
    LayoutBuilder(
      builder: (context, constraints) {
        return Image.asset('bg.jpg',
          width: constraints.maxWidth,
          fit: BoxFit.cover
        );
      }
    )
  3. 圆形头像实现:

    dart
    ClipOval(
      child: Image.asset('avatar.jpg',
        width: 80,
        height: 80,
        fit: BoxFit.cover,  // 关键:确保图片居中裁剪
      ),
    )
  4. 平铺背景优化:

    dart
    Container(
      decoration: BoxDecoration(
        image: DecorationImage(
          image: AssetImage('tile.png'),
          repeat: ImageRepeat.repeat,
          scale: 0.5  // 控制图案密度
        )
      )
    )

Image 性能提示

  • 网络图片使用 cacheHeight/cacheWidth 减少内存占用
  • 需要圆角效果优先考虑 ClipRRect 而非 borderRadius
  • 平铺小图替代大图可显著降低内存消耗

Flutter 实现圆形图片的 5 种方法 ​

5 种方案怎么选

方案关键 API适用场景
ClipOvalClipOval最常用,任意图片裁圆
CircleAvatarbackgroundImage头像专用,自带占位文本
Container + BoxDecorationshape: BoxShape.circle需要背景/边框/装饰一体化
ClipRRect + 圆角BorderRadius.circular圆角可调(半径=宽高一半即正圆)
PhysicalModelelevation需要物理阴影/层次感

核心前提:宽高必须相等才能得到正圆。

方法 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,
  ),
)

最佳实践建议: ​

  1. 尺寸控制:

    • 宽高必须相等才能得到正圆
    • 使用 LayoutBuilder 实现响应式圆形:
    dart
    LayoutBuilder(
      builder: (context, constraints) {
        double size = constraints.maxWidth;
        return ClipOval(
          child: Image.asset('avatar.jpg',
            width: size,
            height: size,
            fit: BoxFit.cover,
          ),
        );
      }
    )
  2. 占位与错误处理:

    dart
    ClipOval(
      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),
      ),
    )
  3. 添加边框:

    dart
    Container(
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        border: Border.all(color: Colors.blue, width: 3),
      ),
      child: ClipOval(
        child: Image.asset(...),
      ),
    )
  4. 带点击效果的圆形图片:

    dart
    InkWell(
      onTap: () {},  // 点击事件
      borderRadius: BorderRadius.circular(50),
      child: ClipOval(
        child: Image.asset(...),
      ),
    )

圆形图片性能优化

  • 网络图片使用 cached_network_image 包缓存
  • 本地图片使用 Image.asset 并指定精确尺寸
  • 对于小图标,优先使用 Icon 而不是图片资源

Icon 图标组件(Material 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:获取图标文件 ​

  1. 访问阿里巴巴图标库
  2. 创建项目并添加所需图标
  3. 下载图标包(选择 Font class 方式)
  4. 解压后得到:
    • iconfont.ttf(字体文件)
    • iconfont.css(包含 Unicode 编码)

步骤 2:配置 Flutter 项目 ​

  1. 在项目根目录创建 assets/fonts 文件夹
  2. 将 iconfont.ttf 复制到此文件夹
  3. 修改 pubspec.yaml 添加字体配置:
yaml
flutter:
  uses-material-design: true
  assets:
    - assets/images/ # 如果有其他资源
  fonts:
    - family: iconfont # 自定义字体名称
      fonts:
        - asset: assets/fonts/iconfont.ttf

高级用法:自动生成映射类 ​

  1. 安装代码生成工具:

    bash
    flutter pub global activate iconfont_builder
  2. 使用命令生成 Dart 类:

    bash
    flutter pub global run iconfont_builder --from path/to/iconfont.css --to lib/iconfont.dart

常见问题解决 ​

字体图标高频踩坑

图标显示为方块 □ 是最常见问题,几乎都出在字体路径/pubspec.yaml 缩进上——改完配置务必 flutter clean 后重新运行。颜色不生效多因图标为多色图标(需改用 SVG)。

  1. 图标显示为方块 □

    • 检查字体文件路径是否正确
    • 确保 pubspec.yaml 缩进正确
    • 执行 flutter clean 后重新运行
  2. 图标颜色不生效

    • 确认图标不是多色图标(多色图标需要使用 SVG)
    • 检查是否在父组件中覆盖了图标颜色
  3. 图标大小异常

    • 使用 size 参数明确指定尺寸
    • 避免在父容器中使用固定宽高限制
  4. 图标偏移问题

    dart
    Icon(
      Iconfont.cart,
      size: 24,
      textDirection: TextDirection.ltr, // 解决方向问题
    )

最佳实践建议 ​

  1. 图标管理

    • 创建单独的 iconfont.dart 文件管理所有图标
    • 使用脚本自动更新图标映射类
    • 为图标添加详细注释说明用途
  2. 性能优化

    • 使用 const 修饰图标组件
    • 避免在动画中频繁改变图标
    • 复杂图标优先考虑 SVG 格式
  3. 多主题适配

    dart
    Icon(
      Iconfont.home,
      color: Theme.of(context).iconTheme.color,
    )
  4. 图标命名规范

    dart
    class AppIcons {
      static const IconData home = IconData(0xe600, fontFamily: 'iconfont');
      static const IconData search = IconData(0xe601, fontFamily: 'iconfont');
      // 按功能模块分组
    }

多色图标提示

对于多色图标或需要动态变化的图标,建议使用 flutter_svg 包加载 SVG 格式图标。

ListView 列表组件 ​

列表选型一句话

静态少量数据用 ListView(children:);数据量大或不确定必用 ListView.builder(按需构建、性能最优);需要分隔线用 ListView.separated;吸顶/复杂滚动用 CustomScrollView + Sliver。

列表类型及核心属性 ​

列表类型实现方式特点描述
垂直列表ListView默认垂直滚动,适合单列数据展示
垂直图文列表ListView + ListTile图文混合布局,适合商品列表、新闻列表等复杂内容
水平列表ListView(scrollDirection: Axis.horizontal)横向滚动,适合轮播图、分类标签等
动态列表ListView.builder按需构建列表项,性能最优,适合大数据集

核心属性详解 ​

属性名类型说明
scrollDirectionAxis滚动方向:Axis.vertical(垂直,默认),Axis.horizontal(水平)
paddingEdgeInsetsGeometry列表内边距,如:EdgeInsets.all(10)
reversebool是否反向显示列表(从下往上/从右往左)
childrenList<Widget>直接子组件列表(静态列表使用)
itemCountint动态列表项数量(仅builder使用)
itemBuilderIndexedWidgetBuilder动态列表项构建函数((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,
      ),
    ),
  ],
)

性能优化建议 ​

  1. 静态列表:

    dart
    ListView(
      children: const [ // 使用const
        ItemWidget(), // 组件也使用const
        ItemWidget(),
      ],
    )
  2. 动态列表黄金法则:

    • 始终使用 ListView.builder
    • 设置精确的 itemCount
    • 避免在 itemBuilder 中创建新函数
  3. 懒加载图片:

    dart
    ListView.builder(
      itemBuilder: (context, index) => CachedNetworkImage(
        imageUrl: images[index],
        placeholder: (_, __) => LoadingWidget(),
      ),
    )
  4. 列表项保持单一职责:

    dart
    // ❌ 避免
    itemBuilder: (c, i) => Column(
      children: [
        if (i == 0) Header(),
        Item(data[i])
      ]
    )
    
    // ✅ 正确
    Column(
      children: [
        Header(),
        Expanded(child: ListView.builder(...))
      ]
    )
  5. 使用 RepaintBoundary:

    dart
    ListView.builder(
      itemBuilder: (c, i) => RepaintBoundary(
        child: ComplexItemWidget(data[i])
      )
    )

常见问题解决方案 ​

  1. 嵌套滚动冲突:

    dart
    ListView(
      physics: const ClampingScrollPhysics(), // 解决嵌套滚动
      // ...
    )
  2. 列表高度错误:

    dart
    // 在Column中包裹ListView
    Expanded(
      child: ListView(...)
    )
  3. 滚动位置保持:

    dart
    class _MyListState extends State<MyList> with AutomaticKeepAliveClientMixin {
      @override
      bool get wantKeepAlive => true;
    
      @override
      Widget build(context) {
        super.build(context);
        return ListView(...);
      }
    }
  4. 动态更新优化:

    dart
    // 使用Key强制重建特定项
    ListView.builder(
      itemBuilder: (c, i) => ItemWidget(
        key: ValueKey(items[i].id), // 唯一Key
        data: items[i],
      )
    )

GridView 网格组件 ​

ListView vs GridView 怎么选

ListView 是单列(或单行)线性滚动,GridView 是多列网格滚动。两者的动态构建(.builder)思路一致:数据量大时都应按需构建、设置精确 itemCount。GridView 的关键在于 gridDelegate 控制每行数量与子项宽高比。

核心属性总览 ​

属性名类型说明
scrollDirectionAxis滚动方向:Axis.vertical(垂直,默认),Axis.horizontal(水平)
paddingEdgeInsetsGeometry内边距
reversebool是否反向排序(默认 false)
crossAxisSpacingdouble水平子 Widget 之间的间距
mainAxisSpacingdouble垂直子 Widget 之间的间距
crossAxisCountint在GridView.count中使用,指定一行中的网格数量
maxCrossAxisExtentdouble在GridView.extent中使用,指定横轴子元素的最大长度
childAspectRatiodouble子 Widget 宽高比例(宽度/高度)
childrenList<Widget>子组件列表(用于静态网格)
gridDelegateSliverGridDelegate布局控制器(用于动态网格),有两种类型:
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 vs Container

只需要内边距时优先用 Padding——它更轻量、语义更清晰;当你同时需要背景、边框、圆角、尺寸时才用 Container(其 padding 属性本质就是内嵌了一个 Padding)。

属性名类型说明示例
paddingEdgeInsetsGeometry设置内边距(核心属性)EdgeInsets.all(16)
EdgeInsets.only(top: 20)
EdgeInsets.symmetric(horizontal: 10)
childWidget需要添加内边距的子组件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) ​

布局怎么选:一张图看懂主流布局组件

Flutter 布局的核心是「沿什么方向摆放子组件」。线性布局(Row/Column)是最基础的一维排列,配合 Expanded/Flexible 控制占比。

text
             ┌──────────────────────┐
             │  要摆放多个子组件?    │
             └──────────┬───────────┘
                        │ 排列方向
    ┌───────────┬───────┴───────┬──────────────┐
  水平一行    垂直一列        自动换行        层叠重合
    │           │               │              │
   Row       Column           Wrap      Stack + Positioned/Align
    │           │
主轴=水平    主轴=垂直
交叉轴=垂直   交叉轴=水平
    └─────┬─────┘
          ▼
  MainAxisAlignment 控主轴
          ▼
  CrossAxisAlignment 控交叉轴
          ▼
  Expanded/Flexible 分配剩余空间

记住:MainAxisAlignment 管主轴方向的分布,CrossAxisAlignment 管交叉轴方向的对齐。

属性名Row(水平布局)Column(垂直布局)说明
主轴方向水平(从左到右)垂直(从上到下)布局的主轴方向
主轴对齐MainAxisAlignmentMainAxisAlignment控制子组件在主轴上的排列方式
交叉轴对齐CrossAxisAlignmentCrossAxisAlignment控制子组件在交叉轴上的排列方式
主轴尺寸MainAxisSizeMainAxisSize主轴方向占用的空间(max 或 min)
文本方向TextDirection-控制水平方向子组件的排列顺序
垂直方向-VerticalDirection控制垂直方向子组件的排列顺序
子组件列表childrenchildren包含的子组件

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()]); // 窄屏布局(示例占位组件)
  },
)

说明:这里的 ContentArea() / NavigationRail() / AppBar() 作为“占位组件”用于演示布局切换。实际项目中请替换为你自己的页面组件,避免和 Flutter 的 AppBar Widget 概念混淆。

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('内容文本...'),
        ],
      ),
    ),
  ],
)

性能优化指南 ​

  1. 使用 const 构造函数:

    dart
    const Row(
      children: [
        Icon(Icons.star),
        Text('固定内容'),
      ],
    )
  2. 提取子组件:

    dart
    // 提取为独立组件
    class RatingBar extends StatelessWidget {
      const RatingBar({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Row(/*...*/);
      }
    }
  3. 避免深层嵌套:

    dart
    // ❌ 不推荐(嵌套过深)
    Row(children: [Column(children: [Row(children: [...]])])
    
    // ✅ 推荐(提取子组件)
    CustomComplexWidget()
  4. 使用 IntrinsicHeight(谨慎使用):

    dart
    IntrinsicHeight(
      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 让子组件像图层一样重叠堆放;Positioned 用 top/right/bottom/left 精确定位子组件(仅在 Stack 内有效);Align 用对齐方式(如 Alignment.bottomRight)相对定位。典型场景:图片上叠加文字、角标、悬浮按钮。

组件主要功能核心特点
Stack层叠布局容器,允许子组件重叠显示支持相对定位和绝对定位
Align控制子组件在父容器中的对齐位置支持精确的分数定位(FractionalOffset)
Positioned在 Stack 中精确控制子组件的位置可设置上下左右四个方向的偏移值

Stack 属性 ​

属性名类型说明
alignmentAlignmentDirectional默认子组件对齐方式(默认左上角)
textDirectionTextDirection文本方向(影响对齐方向)
fitStackFit未定位子组件的尺寸调整方式(loose/expand/passthrough)
clipBehaviorClip内容溢出处理方式(默认为 Clip.hardEdge)
childrenList<Widget>子组件列表

Align 属性 ​

属性名类型说明
alignmentAlignment对齐方式(如 Alignment.center)
widthFactordouble宽度因子(相对于子组件宽度)
heightFactordouble高度因子(相对于子组件高度)
childWidget子组件

Positioned 属性 ​

属性名类型说明
leftdouble距离 Stack 左边的距离
topdouble距离 Stack 顶部的距离
rightdouble距离 Stack 右边的距离
bottomdouble距离 Stack 底部的距离
widthdouble指定宽度(非必须)
heightdouble指定高度(非必须)
childWidget子组件

基础用法示例 ​

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'),
          ),
        ],
      ),
    ),
  ],
)

层叠布局实践建议

  1. 优先使用 Positioned 进行精确控制,使用 Align 进行相对定位
  2. 使用 LayoutBuilder 实现响应式层叠布局
  3. 对于复杂布局,组合使用 Stack + Transform + Opacity
  4. 使用 Clip.none 允许内容溢出容器边界
  5. 避免在 Stack 中嵌套过多子组件(超过 5 个考虑重构)

Card 组件全面指南 ​

Card 是 Flutter 中用于创建卡片式布局的核心组件,具有圆角、阴影和立体效果,非常适合展示内容区块。以下是 Card 组件的详细解析:

Card 使用场景

Card 本质是带阴影和圆角的 Material 容器,适合列表项、信息卡片、商品展示等需要「区块感」的场景。elevation 控制阴影深度体现层级;配合 InkWell/ListTile 可实现点击涟漪效果。Card 只有一个 child,多内容需嵌套 Column/Row。

属性名类型默认值说明
marginEdgeInsetsGeometrynull卡片外部的边距
childWidget必填卡片内容区域
elevationdouble1.0阴影深度(值越大阴影越明显)
colorColorTheme.cardColor卡片背景颜色
shadowColorColorTheme.shadowColor阴影颜色
shapeShapeBorderRoundedRectangleBorder(borderRadius: BorderRadius.circular(4.0))卡片形状(可自定义圆角)
clipBehaviorClipClip.none内容裁剪方式(none/antiAlias/antiAliasWithSaveLayer/hardEdge)
borderOnForegroundbooltrue是否在子组件前绘制边框
surfaceTintColorColornull材料表面色调颜色(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)),
          ],
        ),
      ],
    ),
  ),
)

Card 实践建议

  1. 使用 elevation 控制卡片层次感,但避免过度使用(建议 1-12 范围)
  2. 结合 ClipRRect 实现图片圆角效果更自然
  3. 使用 InkWell 或 InkResponse 为卡片添加水波纹效果
  4. 在暗色模式下调整卡片颜色:color: Theme.of(context).cardColor
  5. 使用 Card 嵌套 Card 创建分层设计效果
  6. 遵循 Material Design 指南,卡片间距至少 8dp,内容内边距至少 16dp

CircleAvatar 组件 ​

CircleAvatar 是一个圆形头像组件,用于展示用户头像、图标或文字,支持图片、图标、文字等多种内容形式,是用户界面中展示个人信息的核心组件。

CircleAvatar 内容优先级

CircleAvatar 的内容显示遵循优先级:foregroundImage > backgroundImage > child(文字/图标)> backgroundColor 纯色背景。加载网络头像时建议同时设置 backgroundColor 作为占位色,避免图片加载前出现空白。

核心属性表 ​

属性名类型默认值说明
backgroundColorColor主题默认色圆形背景颜色(当没有背景图片时)
backgroundImageImageProvidernull背景图片(如 NetworkImage、AssetImage)
foregroundImageImageProvidernull前景图片(覆盖在背景之上)
childWidgetnull子组件(通常是文字或图标)
radiusdouble20圆形半径(定义大小)
minRadiusdoublenull最小半径
maxRadiusdoublenull最大半径
onBackgroundImageErrorFunctionnull背景图片加载失败的回调函数
onForegroundImageErrorFunctionnull前景图片加载失败的回调函数

基础用法示例 ​

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), // 初始大字体
    ),
  ),
)

CircleAvatar 实践建议

  1. 使用 CachedNetworkImage 优化网络头像加载性能
  2. 对于未知用户,使用姓名首字母 + 随机背景色方案
  3. 使用 Hero 动画实现头像详情页的平滑过渡
  4. 在暗色模式下调整背景色:backgroundColor: Theme.of(context).colorScheme.surfaceVariant
  5. 使用 Badge 组件添加通知徽章
  6. 组合 Tooltip 提供用户信息提示
  7. 重要用户头像添加 BoxShadow 提升视觉层次

按钮组件(ElevatedButton / TextButton / OutlinedButton / IconButton 等) ​

Flutter 提供了丰富的按钮组件体系,满足不同场景下的交互需求。以下是完整的按钮组件分类与详解:

关键规则:Flutter 中按钮的“禁用态”通常是把 onPressed 设为 null。这会自动触发灰态/不可点击(并符合无障碍语义)。

dart
ElevatedButton(
  onPressed: null, // 禁用
  child: Text('提交'),
)

按钮类型对比表 ​

按钮类型视觉风格适用场景核心组件
凸起按钮有阴影和背景色主要操作、强调操作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),
  ),
)

按钮实践建议

  1. 使用 ButtonStyle 替代已弃用的按钮样式属性
  2. 对于重要操作,使用 ElevatedButton 提高视觉优先级
  3. 在长列表中使用 NotificationListener 优化滚动时按钮性能
  4. 使用 Focus 和 FocusTraversalGroup 管理按钮焦点顺序
  5. 通过 MaterialStateProperty 创建响应不同状态的按钮样式
  6. 在暗色模式下使用 ThemeData 自动适配按钮颜色
  7. 为图标按钮添加 tooltip 属性提升无障碍访问体验

Wrap 组件 ​

Flutter 的 Wrap 组件是一种流式布局(Flow Layout)工具,用于在空间不足时自动换行排列子组件,特别适合标签云、动态列表等场景。以下是其核心特性和使用指南:

Wrap 自动换行原理

Row/Column 在空间不足时会溢出报错(黄黑警戒条),而 Wrap 会在当前行/列排不下时自动换到下一行/列。用 spacing 控制主轴间距、runSpacing 控制换行后的行间距。标签云、动态数量的按钮组首选 Wrap。


属性说明默认值
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 更容易出现溢出(RenderFlex overflowed)。


二、基本用法示例 ​

水平标签布局(自动换行) ​

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 vs Flow

多数场景下 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 ​

BottomNavigationBar 底部导航栏

底部导航栏用于在 3~5 个主页面间切换,赋值给 Scaffold 的 bottomNavigationBar。核心是 currentIndex 记录当前选中项 + onTap 回调更新状态,通常配合 IndexedStack 或 PageView 切换页面内容。超过 4 项时需显式设置 type: BottomNavigationBarType.fixed 避免选中项放大位移。

属性名类型默认值说明
itemsList<BottomNavigationBarItem>必填导航项集合(至少需要 2 个)
iconSizedouble24.0导航图标大小
currentIndexint0当前选中项的索引
onTapValueChanged<int>-点击导航项的回调函数
fixedColorColor主题色选中项的颜色
typeBottomNavigationBarTypefixed导航栏类型:fixed 或 shifting
backgroundColorColor主题背景色导航栏背景颜色
elevationdouble8.0导航栏阴影高度
selectedFontSizedouble14.0选中项字体大小
unselectedFontSizedouble12.0未选中项字体大小
selectedItemColorColorfixedColor选中项颜色
unselectedItemColorColor主题未选中色未选中项颜色
selectedLabelStyleTextStyle-选中项标签样式
unselectedLabelStyleTextStyle-未选中项标签样式
showSelectedLabelsbooltrue是否显示选中项标签
showUnselectedLabelsbooltype == fixed是否显示未选中项标签
mouseCursorMouseCursorSystemMouseCursors.click鼠标悬停效果
enableFeedbackbooltrue是否启用触觉反馈
landscapeLayoutBottomNavigationBarLandscapeLayoutspread横屏布局模式

两种导航类型对比 ​

特性Fixed 类型Shifting 类型
导航项宽度固定相同宽度动态变化宽度
文字标签始终显示仅选中项显示
背景颜色统一背景色选中项背景色变化
动画效果简单丰富动效
适用场景项较多 (>3)项较少 (3-4)
显示效果Fixed类型Shifting类型

完整使用示例 ​

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,

最佳实践建议 ​

  1. 导航项数量:控制在 3-5 个之间,避免过多
  2. 标签文本:简洁明了(1-2 个单词)
  3. 图标选择:使用标准 Material 图标
  4. 视觉反馈:使用 activeIcon 提供状态变化
  5. 横屏适配:使用 landscapeLayout 优化横屏体验
  6. 无障碍支持:
    dart
    BottomNavigationBarItem(
      icon: Icon(Icons.home),
      label: '首页',
      tooltip: '导航到首页', // 屏幕阅读器提示
    )

FloatingActionButton ​

FloatingActionButton 悬浮按钮

FAB 是悬浮在页面右下角的圆形操作按钮,用于承载页面的主要操作(如新增、发消息)。赋值给 Scaffold 的 floatingActionButton,可通过 floatingActionButtonLocation 调整位置。一个页面建议只放一个 FAB,突出核心动作。

属性名类型默认值说明
onPressedVoidCallback必填按钮点击回调函数
childWidget通常为 Icon按钮内容(图标或文本)
tooltipString-长按提示文本(无障碍支持)
backgroundColorColor主题色按钮背景颜色
foregroundColorColor白色前景色(图标/文字颜色)
elevationdouble6.0正常状态阴影高度
highlightElevationdouble12.0按下状态阴影高度
shapeShapeBorderCircleBorder()按钮形状(可自定义)
heroTagObject-Hero 动画标识(多个 FAB 时需要)
miniboolfalse是否使用迷你尺寸
isExtendedboolfalse是否为扩展模式(带文字)
extendedIconLabelSpacingdouble8.0扩展模式图标与文字的间距
extendedPaddingEdgeInsetsGeometryEdgeInsets.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.0mini: true
大型72.0FloatingActionButton.large

最佳实践与常见问题 ​

最佳实践 ​

  1. 单一主操作:每个屏幕只使用一个主要悬浮按钮
  2. 位置合理:避免遮挡重要内容
  3. 视觉层次:使用鲜艳颜色突出主要操作
  4. 响应式设计:在移动端和桌面端适配不同尺寸
  5. 无障碍支持:始终提供 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 组件全面指南 ​

Drawer 侧滑抽屉结构

Drawer 是从屏幕侧边滑出的导航面板,赋值给 Scaffold 的 drawer(左侧)或 endDrawer(右侧)后,AppBar 会自动出现汉堡菜单图标。内部通常放 ListView + UserAccountsDrawerHeader + 若干 ListTile。点击项后用 Navigator.pop(context) 关闭抽屉再跳转。

属性名类型默认值说明
backgroundColorColor主题背景色抽屉背景颜色
elevationdouble16.0抽屉阴影高度
widthdouble304.0抽屉宽度
shapeShapeBorder-抽屉形状(可自定义圆角)
semanticLabelString-无障碍语义标签
childWidget必填抽屉内容(通常为 ListView)
clipBehaviorClipClip.hardEdge内容裁剪方式
surfaceTintColorColor-材料表面色调颜色(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('内容区域')),
)

常见坑:

  • 打开抽屉时的 context:如果你在 AppBar.leading 里直接调用 Scaffold.of(context).openDrawer(),经常会因为 context 不在 Scaffold 下而报错。推荐用 Builder 包一层:

    dart
    appBar: AppBar(
      leading: Builder(
        builder: (context) => IconButton(
          icon: const Icon(Icons.menu),
          onPressed: () => Scaffold.of(context).openDrawer(),
        ),
      ),
    )

2. 双抽屉实现(左侧+右侧) ​

dart
Scaffold(
  drawer: Drawer(child: _buildLeftDrawer()), // 左侧抽屉
  endDrawer: Drawer(child: _buildRightDrawer()), // 右侧抽屉
  appBar: AppBar(
    title: Text('双抽屉示例'),
    leading: Builder(
      builder: (context) => IconButton(
        icon: Icon(Icons.menu),
        onPressed: () => Scaffold.of(context).openDrawer(),
      ),
    ),
    actions: [
      Builder(
        builder: (context) => 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 设计的预置头部组件,用于展示用户账户信息,提供了一种快速创建美观用户信息区域的方式。

为什么用 UserAccountsDrawerHeader

它开箱即用地组合了头像(currentAccountPicture)、用户名(accountName)、邮箱(accountEmail)和背景,省去手写 DrawerHeader + Row/Column 布局的繁琐。作为 Drawer 的第一个子元素使用即可。

核心属性详解 ​

属性名类型默认值说明
accountNameWidget必填用户名展示组件(通常为 Text)
accountEmailWidget必填用户邮箱展示组件
currentAccountPictureWidget-当前账户头像(通常为 CircleAvatar)
otherAccountsPicturesList<Widget>-其他账户头像列表
decorationDecoration主题色背景头部装饰(可设置背景图/渐变)
marginEdgeInsetsGeometryEdgeInsets.only(bottom: 8.0)外边距
onDetailsPressedVoidCallback-点击整个头部时的回调函数
arrowColorColor白色右侧箭头的颜色
accountNameTextStyleTextStyle主题文字样式用户名字体样式
accountEmailTextStyleTextStyle主题文字样式邮箱字体样式

基础使用示例 ​

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,
  // ...
)

最佳实践与技巧 ​

  1. 头像优化:

    • 使用 CachedNetworkImage 加载网络头像
    • 提供占位符和错误处理
    dart
    currentAccountPicture: CachedNetworkImage(
      imageUrl: "avatar.jpg",
      imageBuilder: (context, imageProvider) => CircleAvatar(
        backgroundImage: imageProvider,
      ),
      placeholder: (context, url) => CircularProgressIndicator(),
      errorWidget: (context, url, error) => Icon(Icons.error),
    ),
  2. 动态数据绑定:

    dart
    UserAccountsDrawerHeader(
      accountName: Text(userProvider.name),
      accountEmail: Text(userProvider.email),
      currentAccountPicture: CircleAvatar(
        backgroundImage: NetworkImage(userProvider.avatarUrl),
      ),
    )
  3. 无障碍支持:

    dart
    UserAccountsDrawerHeader(
      accountName: Semantics(
        label: "用户名",
        child: Text("实际用户名"),
      ),
      // ...
    )
  4. 性能优化:

    dart
    const 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 应用的标准顶部栏组件,常用于显示标题、操作按钮和导航元素。

AppBar 结构速记

AppBar 从左到右三块:leading(左侧返回/菜单)+ title(标题)+ actions(右侧操作按钮组);底部 bottom 可挂 TabBar。赋值给 Scaffold.appBar 即自动占据顶部并处理状态栏留白。

核心属性 ​

属性类型默认值说明
titleWidget-标题组件(通常为 Text)
actionsList<Widget>[]右侧操作按钮列表
bottomPreferredSizeWidget-底部组件(通常放置 TabBar)
backgroundColorColor主题色背景颜色
elevationdouble4.0阴影高度
leadingWidget-左侧导航元素(如返回按钮)

基本用法 ​

dart
AppBar(
  title: Text('我的应用'),
  actions: [
    IconButton(icon: Icon(Icons.search), onPressed: () {}),
    IconButton(icon: Icon(Icons.settings), onPressed: () {}),
  ],
)

TabBar ​

TabBar 是一个水平标签导航组件,通常嵌入在 AppBar 的 bottom 属性中。

核心属性 ​

属性类型默认值说明
tabsList<Widget>必填Tab 组件列表
controllerTabController-标签控制器
isScrollableboolfalse标签过多时是否可滚动
indicatorColorColor主题色指示器颜色
labelColorColor主题色选中标签文字颜色
unselectedLabelColorColor灰色未选中标签文字颜色
indicatorWeightdouble2.0指示器厚度

Tab 组件属性 ​

dart
Tab(
  icon: Icon(Icons.flight),   // 图标
  text: "航班",               // 文字
  child: Text('航班'),        // 自定义子组件
)

TabBarView ​

TabBarView 用于显示与 TabBar 标签对应的内容区域,支持左右滑动切换。

核心属性 ​

属性类型默认值说明
childrenList<Widget>必填标签对应的内容组件列表
controllerTabController-必须与 TabBar 的控制器一致
physicsScrollPhysics-滚动物理特性

特殊属性 ​

  • physics:
    • NeverScrollableScrollPhysics():禁用滑动
    • BouncingScrollPhysics():弹性滚动效果

AppBar + TabBar + TabBarView 三者协同使用 ​

AppBar + TabBar + TabBarView 构成完整的标签式布局体系。

三者协同关系

TabBar(标签栏)放在 AppBar.bottom,TabBarView(内容区)放在 Scaffold.body,二者通过同一个 TabController 联动——点标签切内容、滑内容切标签。用 DefaultTabController 包裹可自动创建并共享控制器,无需手动管理。

text
DefaultTabController(提供共享控制器)
        │
        ▼
     Scaffold
      ├── AppBar
      │      └── bottom: TabBar(标签栏)  ──┐
      │                                      │ 同一 TabController 联动
      └── body: 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(/*...*/),
    )
  ],
)

TabBar 状态管理

使用 DefaultTabController 可简化状态管理,适合大多数基础场景。需要复杂交互时使用手动控制方式(自建 TabController)。

PreferredSize 组件 ​

为什么需要 PreferredSize

AppBar.bottom 只接受 PreferredSizeWidget(自带首选高度的组件)。当你想放一个普通 Widget 作为 AppBar 底部区域时,必须用 PreferredSize 包一层并通过 preferredSize 指定高度,否则无法确定布局尺寸。

  • PreferredSize 可以改变 AppBar 的高度(常用于 AppBar.bottom 的自定义区域)

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(对话框) ​

为什么用 GetX 弹窗

原生 showDialog 需要 BuildContext,而 GetX 的 Get.dialog/Get.defaultDialog/Get.bottomSheet 无需 context 即可全局调用,适合在业务层、控制器中随处弹窗。前提是入口用 GetMaterialApp 而非 MaterialApp,用 Get.back() 关闭。


基础对话框 ​

默认对话框 ​

dart
Get.defaultDialog(
  title: "提示",
  middleText: "确定要删除这条记录吗?",
  textConfirm: "确定",
  textCancel: "取消",
  confirmTextColor: Colors.white,
  onConfirm: () {
    // 确认操作
    Get.back(result: true); // 返回结果
  },
  onCancel: () {
    // 取消操作
    Get.back(result: false);
  },
);

核心参数 ​

参数类型默认值说明
titleString-对话框标题
titleStyleTextStyle-标题样式
middleTextString-主要内容文本
middleTextStyleTextStyle-内容文本样式
textConfirmString"确认"确认按钮文本
textCancelString"取消"取消按钮文本
onConfirmVoidCallback-确认按钮回调
onCancelVoidCallback-取消按钮回调
confirmTextColorColor-确认按钮文字颜色
cancelTextColorColor-取消按钮文字颜色
backgroundColorColorColors.white对话框背景色
radiusdouble20.0对话框圆角半径
barrierDismissiblebooltrue点击背景是否关闭对话框

自定义对话框 ​

完全自定义对话框 ​

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: ...,
    ),
  ),
);

组件选型速查

  • 展示内容:单块盒子 → Container;文字 → Text;图片 → Image;圆形头像 → CircleAvatar;图标 → Icon/字体图标。
  • 排列布局:横排 → Row;竖排 → Column;自动换行 → Wrap;层叠定位 → Stack + Positioned/Align;间距 → Padding。
  • 列表网格:长列表/大数据 → ListView.builder;网格 → GridView。
  • 卡片区块:Card(自带圆角阴影)。
  • 页面骨架:顶部栏 AppBar、底部导航 BottomNavigationBar、侧滑抽屉 Drawer、悬浮按钮 FloatingActionButton、标签页 TabBar + TabBarView(同一 TabController 联动)。
  • 交互反馈:按钮组件、GetX Dialog(无需 context 全局弹窗)。

一句话:Flutter 一切皆 Widget,UI 就是把这些组件像积木一样组合成树。 记住「主轴/交叉轴」和「组合优于继承」,布局问题迎刃而解。