Skip to content

Flutter 组件

Container 容器组件

属性名类型说明示例
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 常用样式属性

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

💡 最佳实践

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

Image组件属性详解

  • Image.asset, 本地图片
  • 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  // 控制图案密度
        )
      )
    )

💡 性能提示

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

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

方法 1:使用 ClipOval(最常用)

dart
ClipOval(
  child: Image.network(
    'https://example.com/avatar.jpg',
    width: 100,
    height: 100,
    fit: BoxFit.cover,  // 关键:确保图片填充裁剪区域
  ),
)

方法 2:使用 CircleAvatar(头像专用)

dart
CircleAvatar(
  radius: 60,  // 控制圆形大小
  backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
  child: Text('AB'),  // 可添加备用文本
)

方法 3:使用 Container + BoxDecoration

dart
Container(
  width: 120,
  height: 120,
  decoration: BoxDecoration(
    shape: BoxShape.circle,
    // borderRadius: BorderRadius.circular(75),
    image: DecorationImage(
      image: NetworkImage('https://example.com/avatar.jpg'),
      fit: BoxFit.cover,
    ),
  ),
)

方法 4:使用 ClipRRect + 圆角(可调整圆角程度)

dart
ClipRRect(
  borderRadius: BorderRadius.circular(100),  // 设置为宽高的一半
  child: Image.asset(
    'assets/profile.jpg',
    width: 100,
    height: 100,
    fit: BoxFit.cover,
  ),
)

方法 5:使用 PhysicalModel(带物理效果)

dart
PhysicalModel(
  color: Colors.transparent,
  shape: BoxShape.circle,
  elevation: 5,  // 添加阴影深度
  child: Image.network(
    'https://example.com/avatar.jpg',
    width: 100,
    height: 100,
    fit: BoxFit.cover,
  ),
)

最佳实践建议:

  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 而不是图片资源

官方图标组件

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

常见问题解决

  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默认垂直滚动,适合单列数据展示
垂直图文列表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 网格组件

核心属性总览

属性名类型说明
scrollDirectionAxis滚动方向:Axis.vertical(垂直,默认),Axis.horizontal(水平)
paddingEdgeInsetsGeometry内边距
reversebool是否反向排序(默认 false)
crossAxisSpacingdouble水平子 Widget 之间的间距
mainAxisSpacingdouble垂直子 Widget 之间的间距
crossAxisCountintGridView.count中使用,指定一行中的网格数量
maxCrossAxisExtentdoubleGridView.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 组件

属性名类型说明示例
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)

属性名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()]);    // 窄屏布局
  },
)

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层叠布局容器,允许子组件重叠显示支持相对定位和绝对定位
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 组件的详细解析:

属性名类型默认值说明
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)),
          ],
        ),
      ],
    ),
  ),
)

提示

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

CircleAvatar 组件

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

核心属性表

属性名类型默认值说明
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), // 初始大字体
    ),
  ),
)

提示

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

Button 按钮组件

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

按钮类型对比表

按钮类型视觉风格适用场景核心组件
凸起按钮有阴影和背景色主要操作、强调操作ElevatedButton
文本按钮无背景、仅文字次要操作、对话框操作TextButton
描边按钮带边框、无填充中等重要性操作、取消操作OutlinedButton
图标按钮仅图标工具栏、紧凑空间操作IconButton
悬浮按钮圆形悬浮按钮主屏幕主要操作FloatingActionButton
下拉按钮带下拉菜单选项选择DropdownButton
弹出菜单按钮三点菜单更多操作菜单PopupMenuButton
自定义按钮完全自定义样式品牌化设计、特殊交互InkWell/GestureDetector

一、基础按钮组件

1. ElevatedButton (凸起按钮)

dart
ElevatedButton(
  onPressed: () {
    print('按钮被点击');
  },
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.blue, // 背景色
    foregroundColor: Colors.white, // 文字/图标颜色
    elevation: 5, // 阴影深度
    padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), // 内边距
    shape: RoundedRectangleBorder( // 形状
      borderRadius: BorderRadius.circular(8),
    ),
  ),
  child: const Text('确认提交'),
)

2. TextButton (文本按钮)

dart
TextButton(
  onPressed: () {},
  style: TextButton.styleFrom(
    foregroundColor: Colors.blue,
    padding: EdgeInsets.all(12),
  ),
  child: const Text('取消操作'),
)

3. OutlinedButton (描边按钮)

dart
OutlinedButton(
  onPressed: () {},
  style: OutlinedButton.styleFrom(
    side: BorderSide(color: Colors.blue, width: 2), // 边框样式
    shape: StadiumBorder(), // 圆角形状
    padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
  ),
  child: const Text('查看详情'),
)

4. IconButton (图标按钮)

dart
IconButton(
  onPressed: () {},
  icon: Icon(Icons.favorite),
  iconSize: 30,
  color: Colors.red,
  tooltip: '收藏', // 长按提示
)

5. FloatingActionButton (悬浮按钮)

dart
FloatingActionButton(
  onPressed: () {},
  backgroundColor: Colors.blue,
  foregroundColor: Colors.white,
  elevation: 6,
  shape: CircleBorder(), // 圆形
  child: Icon(Icons.add),
)

二、高级按钮组件

1. DropdownButton (下拉按钮)

dart
String? _selectedValue = '选项1';

DropdownButton<String>(
  value: _selectedValue,
  onChanged: (newValue) {
    setState(() => _selectedValue = newValue);
  },
  items: <String>['选项1', '选项2', '选项3']
      .map<DropdownMenuItem<String>>((String value) {
    return DropdownMenuItem<String>(
      value: value,
      child: Text(value),
    );
  }).toList(),
)

2. PopupMenuButton (弹出菜单按钮)

dart
PopupMenuButton<String>(
  icon: Icon(Icons.more_vert),
  itemBuilder: (context) => [
    PopupMenuItem(value: 'edit', child: Text('编辑')),
    PopupMenuItem(value: 'delete', child: Text('删除')),
  ],
  onSelected: (value) {
    if (value == 'delete') _deleteItem();
  },
)

3. ButtonBar (按钮组)

dart
ButtonBar(
  alignment: MainAxisAlignment.end, // 对齐方式
  children: [
    TextButton(onPressed: () {}, child: Text('取消')),
    ElevatedButton(onPressed: () {}, child: Text('确定')),
  ],
)

三、按钮状态管理

1. 禁用状态

dart
ElevatedButton(
  onPressed: isLoading ? null : _submitForm, // null表示禁用
  child: isLoading
      ? CircularProgressIndicator(color: Colors.white)
      : Text('提交'),
)

2. 加载状态

dart
bool _isLoading = false;

ElevatedButton(
  onPressed: _isLoading ? null : _fetchData,
  child: _isLoading
      ? SizedBox(
          width: 20,
          height: 20,
          child: CircularProgressIndicator(strokeWidth: 2),
        )
      : Text('加载数据'),
)

3. 按钮状态样式

dart
ElevatedButton(
  style: ButtonStyle(
    backgroundColor: MaterialStateProperty.resolveWith<Color>(
      (Set<MaterialState> states) {
        if (states.contains(MaterialState.disabled)) {
          return Colors.grey; // 禁用状态
        }
        if (states.contains(MaterialState.pressed)) {
          return Colors.blue[700]!; // 按下状态
        }
        return Colors.blue; // 默认状态
      },
    ),
  ),
  // ...
)

四、自定义按钮

1. 使用 InkWell 创建自定义按钮

dart
InkWell(
  onTap: () {},
  borderRadius: BorderRadius.circular(8),
  splashColor: Colors.blue[100], // 水波纹颜色
  child: Container(
    padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
    decoration: BoxDecoration(
      color: Colors.blue[50],
      borderRadius: BorderRadius.circular(8),
      border: Border.all(color: Colors.blue),
    ),
    child: Text('自定义按钮'),
  ),
)

2. 渐变按钮

dart
ElevatedButton(
  style: ElevatedButton.styleFrom(
    padding: EdgeInsets.zero, // 清除默认内边距
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(10),
    ),
  ),
  onPressed: () {},
  child: Ink(
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: [Colors.blue, Colors.purple],
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
      ),
      borderRadius: BorderRadius.circular(10),
    ),
    padding: EdgeInsets.symmetric(horizontal: 30, vertical: 15),
    child: Text('渐变按钮', style: TextStyle(color: Colors.white)),
  ),
)

3. 图标+文字按钮

dart
ElevatedButton.icon(
  onPressed: () {},
  icon: Icon(Icons.cloud_upload),
  label: Text('上传文件'),
  style: ElevatedButton.styleFrom(
    padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
  ),
)

五、最佳实践指南

1. 按钮尺寸规范

dart
// 小尺寸按钮
TextButton(
  style: TextButton.styleFrom(padding: EdgeInsets.all(8)),
  child: Text('小按钮'),
)

// 中尺寸按钮(默认)
ElevatedButton(child: Text('中按钮'),)

// 大尺寸按钮
ElevatedButton(
  style: ElevatedButton.styleFrom(padding: EdgeInsets.all(16)),
  child: Text('大按钮'),
)

2. 响应式按钮设计

dart
LayoutBuilder(
  builder: (context, constraints) {
    bool isMobile = constraints.maxWidth < 600;
    return isMobile
        ? FloatingActionButton(onPressed: () {}, child: Icon(Icons.add))
        : ElevatedButton.icon(
            onPressed: () {},
            icon: Icon(Icons.add),
            label: Text('添加新项目'),
          );
  },
)

3. 按钮主题统一

dart
MaterialApp(
  theme: ThemeData(
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
        textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(8),
        ),
      ),
    ),
    textButtonTheme: TextButtonThemeData(
      style: TextButton.styleFrom(
        foregroundColor: Colors.blue,
      ),
    ),
  ),
)

4. 按钮无障碍优化

dart
ElevatedButton(
  onPressed: () {},
  child: Text('确认'),
  autofocus: true, // 自动获取焦点
  focusNode: _focusNode, // 焦点控制
  onFocusChange: (hasFocus) {
    if (hasFocus) _announceAccessibility('确认按钮已聚焦');
  },
)

六、常见问题解决方案

1. 按钮点击无响应

dart
// 原因:onPressed为null
ElevatedButton(
  onPressed: _isEnabled ? _handlePress : null, // 正确管理状态
  // ...
)

2. 按钮样式不生效

dart
// 解决方案:使用styleFrom代替已弃用属性
ElevatedButton(
  style: ElevatedButton.styleFrom( // 正确方式
    backgroundColor: Colors.red,
  ),
  // ...
)

3. 按钮尺寸异常

dart
// 解决方案:包裹约束容器
SizedBox(
  width: double.infinity, // 撑满宽度
  height: 50, // 固定高度
  child: ElevatedButton(child: Text('登录')),
)

4. 按钮水波纹效果超出边界

dart
Material(
  borderRadius: BorderRadius.circular(10), // 设置裁剪边界
  child: InkWell(
    borderRadius: BorderRadius.circular(10), // 与Material一致
    onTap: () {},
    child: Container(
      padding: EdgeInsets.all(16),
      child: Text('自定义按钮'),
    ),
  ),
)

七、实际应用场景

1. 表单提交按钮组

dart
Column(
  children: [
    // 表单字段...
    ButtonBar(
      alignment: MainAxisAlignment.spaceBetween,
      children: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: Text('取消'),
        ),
        ElevatedButton(
          onPressed: _submitForm,
          child: Text('保存设置'),
        ),
      ],
    ),
  ],
)

2. 购物车操作按钮

dart
Row(
  children: [
    IconButton(
      icon: Icon(Icons.favorite_border),
      onPressed: _toggleFavorite,
    ),
    SizedBox(width: 16),
    Expanded(
      child: ElevatedButton.icon(
        style: ElevatedButton.styleFrom(
          padding: EdgeInsets.symmetric(vertical: 16),
        ),
        icon: Icon(Icons.shopping_cart),
        label: Text('加入购物车'),
        onPressed: _addToCart,
      ),
    ),
  ],
)

3. 社交登录按钮组

dart
Column(
  children: [
    OutlinedButton.icon(
      icon: Image.asset('assets/google.png', width: 24),
      label: Text('使用Google登录'),
      onPressed: _signInWithGoogle,
    ),
    SizedBox(height: 12),
    OutlinedButton.icon(
      icon: Image.asset('assets/facebook.png', width: 24),
      label: Text('使用Facebook登录'),
      onPressed: _signInWithFacebook,
    ),
  ],
)

4. 多功能悬浮按钮

dart
FloatingActionButton(
  onPressed: () {},
  backgroundColor: Colors.blue,
  foregroundColor: Colors.white,
  child: Icon(Icons.add),
  heroTag: 'main_fab', // 唯一标识
)

// 次级悬浮按钮
ScaleTransition(
  scale: CurvedAnimation(
    parent: _animationController,
    curve: Curves.easeInOut,
  ),
  child: FloatingActionButton(
    heroTag: 'fab1', // 不同heroTag
    mini: true, // 小型按钮
    onPressed: () {},
    child: Icon(Icons.camera),
  ),
)

提示

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

Wrap 组件

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


属性说明默认值
direction主轴方向(Axis.horizontal 水平 / Axis.vertical 垂直)Axis.horizontal
alignment主轴对齐方式(如 WrapAlignment.start.center.spaceEvenlyWrapAlignment.start
spacing主轴方向子组件的间距0.0
runSpacing交叉轴方向行/列的间距(控制换行后的间隔)0.0
runAlignment交叉轴的行/列对齐方式(需父容器高度足够才生效)WrapAlignment.start
crossAxisAlignment子组件在交叉轴的对齐方式(如 WrapCrossAlignment.startWrapCrossAlignment.start
children子组件列表必填

💡 关键特性
当主轴空间不足时,Wrap 会自动换行(水平布局换行,垂直布局换列),而 Row/Column 会溢出报错。


🛠️ 二、基本用法示例

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

dart
Wrap(
  spacing: 10.0,      // 水平间距
  runSpacing: 15.0,   // 行间距
  alignment: WrapAlignment.spaceEvenly,
  children: List.generate(10, (index) =>
    Chip(
      label: Text("标签${index + 1}"),
      backgroundColor: Colors.blue[100],
    ),
  ),
)

垂直流式布局

dart
Wrap(
  direction: Axis.vertical, // 垂直主轴
  runSpacing: 20.0,
  children: [
    Icon(Icons.star),
    Icon(Icons.favorite),
    // 更多子组件...
  ],
)

⚙️ 三、高级技巧

1. 响应式布局

根据屏幕宽度动态切换方向:

dart
Wrap(
  direction: MediaQuery.of(context).size.width > 600
      ? Axis.horizontal
      : Axis.vertical,
  children: [/*...*/],
)

2. 嵌套组合

嵌套 Wrap 实现复杂流式结构:

dart
Wrap(
  children: [
    Wrap( // 内层垂直Wrap
      direction: Axis.vertical,
      children: [Chip(label: Text("A")), Chip(label: Text("B"))],
    ),
    Chip(label: Text("主内容区")),
  ],
)

3. 与状态管理结合

动态添加/删除标签:

dart
List<String> tags = ["Flutter", "Dart"];
Wrap(
  children: tags.map((tag) =>
    InputChip(
      label: Text(tag),
      onDeleted: () => setState(() => tags.remove(tag)),
    ),
  ).toList(),
)

⚠️ 四、常见问题解决

1. 换行后行对齐不生效

原因runAlignment 需父容器有额外空间才能生效。
方案:为父容器设置足够高度:

dart
Container(
  height: 300, // 明确高度
  child: Wrap(runAlignment: WrapAlignment.end, children: [...]),
)

2. 子组件间距异常

  • 使用 spacing 控制主轴间距,runSpacing 控制行间距。
  • 避免子组件自身外边距(如 Padding),优先用 Wrap 的间距属性。

3. 性能优化

  • 子组件数量过大时,用 Wrap 替代嵌套 Row/Column 避免布局溢出。
  • 复杂子组件提取为 const 或独立 Widget 减少重建。

🎯 五、应用场景

1. 标签选择器

dart
Wrap(
  spacing: 8.0,
  children: ["喜剧", "动作", "科幻"].map((genre) =>
    FilterChip(
      label: Text(genre),
      selected: _selectedGenre == genre,
      onSelected: (v) => setState(() => _selectedGenre = genre),
    ),
  ).toList(),
)

2. 自适应按钮组

dart
Wrap(
  runSpacing: 10.0,
  children: [
    ElevatedButton(onPressed: () {}, child: Text("保存")),
    OutlinedButton(onPressed: () {}, child: Text("取消")),
    // 更多按钮...
  ],
)

3. 瀑布流图片布局

dart
Wrap(
  spacing: 5.0,
  runSpacing: 5.0,
  children: imageUrls.map((url) =>
    Image.network(url, width: 100, height: 100, fit: BoxFit.cover),
  ).toList(),
)

🔍 六、对比其他布局

组件特点适用场景
Wrap自动换行,简单易用标签、动态内容流
Flow精确控制子组件位置,性能更高但代码复杂自定义复杂流式布局
Row/Column单行/列布局,溢出报错线性固定内容

💡 提示
对于大多数场景,Wrap 的简洁性和自动换行特性已足够;需要像素级控制时(如重叠布局),再考虑 Flow


💎 综合示例

dart
Wrap(
  spacing: 12,
  runSpacing: 16,
  alignment: WrapAlignment.center,
  children: [
    _buildCategory("音乐", Icons.music_note),
    _buildCategory("电影", Icons.movie),
    // 添加更多分类...
  ],
)

Widget _buildCategory(String name, IconData icon) {
  return Column(
    children: [
      Icon(icon, size: 36),
      SizedBox(height: 8),
      Text(name),
    ],
  );
}

通过灵活组合 Wrap 的属性,可轻松实现各类自适应布局,尤其适合内容动态生成的场景(如搜索关键词、用户兴趣标签等)。

BottomNavigationBar

属性名类型默认值说明
itemsList<BottomNavigationBarItem>必填导航项集合(至少需要 2 个)
iconSizedouble24.0导航图标大小
currentIndexint0当前选中项的索引
onTapValueChanged<int>-点击导航项的回调函数
fixedColorColor主题色选中项的颜色
typeBottomNavigationBarTypefixed导航栏类型:fixedshifting
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

属性名类型默认值说明
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 组件全面指南

属性名类型默认值说明
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('内容区域')),
)

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

dart
Scaffold(
  drawer: Drawer(child: _buildLeftDrawer()), // 左侧抽屉
  endDrawer: Drawer(child: _buildRightDrawer()), // 右侧抽屉
  appBar: AppBar(
    title: Text('双抽屉示例'),
    leading: IconButton( // 左侧抽屉按钮
      icon: Icon(Icons.menu),
      onPressed: () => Scaffold.of(context).openDrawer(),
    ),
    actions: [
      IconButton( // 右侧抽屉按钮
        icon: Icon(Icons.filter_list),
        onPressed: () => Scaffold.of(context).openEndDrawer(),
      ),
    ],
  ),
  body: // ...
)

Widget _buildLeftDrawer() {
  return ListView(
    children: [/* 导航菜单 */]
  );
}

Widget _buildRightDrawer() {
  return ListView(
    children: [/* 过滤选项 */]
  );
}

高级用法与自定义

1. 自定义抽屉头部

dart
DrawerHeader(
  decoration: BoxDecoration(
    gradient: LinearGradient(
      colors: [Colors.blue.shade700, Colors.blue.shade300],
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
    ),
  ),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      CircleAvatar(
        radius: 30,
        backgroundImage: NetworkImage('avatar.jpg'),
      ),
      SizedBox(height: 12),
      Text('用户名', style: TextStyle(color: Colors.white, fontSize: 18)),
      Text('user@example.com', style: TextStyle(color: Colors.white70)),
    ],
  ),
),

2. 抽屉状态管理

dart
// 使用Provider管理抽屉状态
final drawerProvider = StateProvider((ref) => false);

// 打开/关闭抽屉
void toggleDrawer(BuildContext context) {
  final isOpen = ref.read(drawerProvider);
  if (isOpen) {
    Scaffold.of(context).closeDrawer();
  } else {
    Scaffold.of(context).openDrawer();
  }
  ref.read(drawerProvider.notifier).state = !isOpen;
}

// 监听抽屉状态
Scaffold(
  drawer: Drawer(
    child: Listener(
      onPointerMove: (event) => print('抽屉正在拖动'),
      child: // ...
    ),
  ),
)

3. 抽屉动画效果

dart
// 自定义抽屉打开动画
Scaffold(
  drawer: Drawer(
    child: AnimatedContainer(
      duration: Duration(milliseconds: 500),
      curve: Curves.easeOutCubic,
      transform: Matrix4.translationValues(
        _drawerOpen ? 0 : -300, 0, 0),
      child: // ...
    ),
  ),
)

// 抽屉项动画
ListTile(
  leading: AnimatedRotation(
    turns: _selectedItem == index ? 0.25 : 0,
    duration: Duration(milliseconds: 300),
    child: Icon(Icons.arrow_forward_ios),
  ),
  title: Text('菜单项'),
),

抽屉内容最佳实践

1. 导航菜单结构

dart
ListView(
  padding: EdgeInsets.zero,
  children: [
    UserAccountsDrawerHeader( // 预定义头部
      accountName: Text("用户名"),
      accountEmail: Text("user@example.com"),
      currentAccountPicture: CircleAvatar(
        backgroundImage: NetworkImage("avatar.jpg"),
      ),
    ),
    Divider(),
    _buildDrawerItem(Icons.home, '首页', 0),
    _buildDrawerItem(Icons.favorite, '收藏', 1),
    _buildDrawerItem(Icons.history, '历史记录', 2),
    Divider(),
    _buildDrawerItem(Icons.settings, '设置', 3),
    _buildDrawerItem(Icons.help, '帮助中心', 4),
    Spacer(), // 将内容推到底部
    AboutListTile(
      icon: Icon(Icons.info),
      applicationName: "应用名称",
      applicationVersion: "1.0.0",
    ),
  ],
)

Widget _buildDrawerItem(IconData icon, String title, int index) {
  return ListTile(
    leading: Icon(icon, color: _selectedIndex == index
        ? Theme.of(context).colorScheme.primary
        : null),
    title: Text(title),
    selected: _selectedIndex == index,
    onTap: () {
      setState(() => _selectedIndex = index);
      Navigator.pop(context);
    },
  );
}

2. 响应式抽屉设计

dart
LayoutBuilder(
  builder: (context, constraints) {
    bool isWideScreen = constraints.maxWidth > 600;

    return Scaffold(
      drawer: isWideScreen ? null : Drawer(child: _buildDrawerContent()),
      body: Row(
        children: [
          if (isWideScreen) ...[
            SizedBox(
              width: 300,
              child: Material(
                elevation: 4,
                child: _buildDrawerContent(),
              ),
            ),
          ],
          Expanded(child: _buildMainContent()),
        ],
      ),
    );
  },
)

3. 暗色模式适配

dart
Drawer(
  backgroundColor: Theme.of(context).drawerTheme.backgroundColor,
  child: ListView(
    children: [
      DrawerHeader(
        decoration: BoxDecoration(
          color: Theme.of(context).colorScheme.primaryContainer,
        ),
        child: // ...
      ),
      ListTile(
        leading: Icon(Icons.dark_mode),
        title: Text('暗色模式'),
        trailing: Switch(
          value: Theme.of(context).brightness == Brightness.dark,
          onChanged: (value) => _toggleTheme(value),
        ),
      ),
    ],
  ),
)

完整示例代码

dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() => runApp(const ProviderScope(child: MyApp()));

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '抽屉示例',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        drawerTheme: DrawerThemeData(
          backgroundColor: Colors.white,
          width: 300,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.only(
              topRight: Radius.circular(20),
              bottomRight: Radius.circular(20),
          ),
        ),
      ),
      darkTheme: ThemeData.dark().copyWith(
        drawerTheme: DrawerThemeData(
          backgroundColor: Colors.grey[900],
        ),
      ),
      home: const MainScreen(),
    );
  }
}

class MainScreen extends ConsumerWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final currentIndex = ref.watch(navIndexProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('抽屉导航示例'),
        leading: Builder(
          builder: (context) => IconButton(
            icon: const Icon(Icons.menu),
            onPressed: () => Scaffold.of(context).openDrawer(),
          ),
        ),
      ),
      drawer: const AppDrawer(),
      body: _buildPage(currentIndex),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: currentIndex,
        onTap: (index) => ref.read(navIndexProvider.notifier).state = index,
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.favorite), label: '收藏'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: '个人'),
        ],
      ),
    );
  }

  Widget _buildPage(int index) {
    final pages = [
      const Center(child: Text('首页内容')),
      const Center(child: Text('收藏内容')),
      const Center(child: Text('个人资料')),
    ];
    return pages[index];
  }
}

final navIndexProvider = StateProvider((ref) => 0);

class AppDrawer extends ConsumerWidget {
  const AppDrawer({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final currentIndex = ref.watch(navIndexProvider);

    return Drawer(
      child: Column(
        children: [
          UserAccountsDrawerHeader(
            accountName: const Text("张三"),
            accountEmail: const Text("zhangsan@example.com"),
            currentAccountPicture: CircleAvatar(
              backgroundColor: Colors.white,
              child: Text(
                "张",
                style: TextStyle(
                  fontSize: 24,
                  color: Theme.of(context).primaryColor,
                ),
              ),
            ),
            decoration: BoxDecoration(
              color: Theme.of(context).primaryColor,
            ),
          ),
          Expanded(
            child: ListView(
              padding: EdgeInsets.zero,
              children: [
                _buildDrawerItem(Icons.home, '首页', 0, currentIndex, ref),
                _buildDrawerItem(Icons.favorite, '收藏', 1, currentIndex, ref),
                _buildDrawerItem(Icons.history, '历史记录', 2, currentIndex, ref),
                const Divider(),
                _buildDrawerItem(Icons.settings, '设置', 3, currentIndex, ref),
                _buildDrawerItem(Icons.help, '帮助中心', 4, currentIndex, ref),
              ],
            ),
          ),
          const AboutListTile(
            icon: Icon(Icons.info),
            applicationName: "抽屉导航示例",
            applicationVersion: "1.0.0",
            applicationIcon: Icon(Icons.apps),
          ),
        ],
      ),
    );
  }

  Widget _buildDrawerItem(IconData icon, String title, int index,
      int currentIndex, WidgetRef ref) {
    final isSelected = currentIndex == index;

    return ListTile(
      leading: Icon(icon, color: isSelected
          ? Theme.of(context).colorScheme.primary
          : null),
      title: Text(title),
      selected: isSelected,
      onTap: () {
        ref.read(navIndexProvider.notifier).state = index;
        Navigator.pop(context);
      },
      trailing: isSelected
          ? const Icon(Icons.check, color: Colors.green)
          : null,
    );
  }
}

常见问题解决方案

1. 在无 Scaffold 上下文访问 Drawer

dart
// 使用 Builder 创建新上下文
Scaffold(
  appBar: AppBar(
    leading: Builder(
      builder: (context) => IconButton(
        icon: Icon(Icons.menu),
        onPressed: () => Scaffold.of(context).openDrawer(),
      ),
    ),
  ),
  drawer: Drawer(...),
)

2. 抽屉背景透明问题

dart
Drawer(
  backgroundColor: Colors.white.withOpacity(0.95), // 半透明白色
  // 或者
  child: Container(
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.only(
        topRight: Radius.circular(20),
        bottomRight: Radius.circular(20),
      ),
    ),
    child: // ...
  ),
)

3. 抽屉内容滚动问题

dart
// 使用 SingleChildScrollView 或 ListView
Drawer(
  child: SingleChildScrollView(
    child: Column(
      children: [/* 长内容 */],
    ),
  ),
)

4. 抽屉状态监听

dart
// 使用 DrawerController
final drawerController = DrawerController();

Scaffold(
  drawer: Drawer(
    controller: drawerController,
    child: // ...
  ),
)

// 添加监听
drawerController.addListener(() {
  if (drawerController.isOpen) {
    print('抽屉已打开');
  } else {
    print('抽屉已关闭');
  }
});

5. 全局控制抽屉

dart
// 使用 GlobalKey
final scaffoldKey = GlobalKey<ScaffoldState>();

Scaffold(
  key: scaffoldKey,
  drawer: Drawer(...),
)

// 在其他地方打开
scaffoldKey.currentState?.openDrawer();

UserAccountsDrawerHeader 组件

UserAccountsDrawerHeader 是专门为 Drawer 设计的预置头部组件,用于展示用户账户信息,提供了一种快速创建美观用户信息区域的方式。

核心属性详解

属性名类型默认值说明
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 应用的标准顶部栏组件,常用于显示标题、操作按钮和导航元素。

核心属性

属性类型默认值说明
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 构成完整的标签式布局体系。

基本结构

dart
Scaffold(
  appBar: AppBar(
    bottom: TabBar(),  // 嵌入TabBar
  ),
  body: TabBarView(),   // 内容区域
)

完整示例

dart
import 'package:flutter/material.dart';

void main() => runApp(const TabLayoutDemo());

class TabLayoutDemo extends StatelessWidget {
  const TabLayoutDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(  // 自动管理Tab状态
        length: 3,  // 标签数量
        child: Scaffold(
          appBar: AppBar(
            title: const Text('旅行预订'),
            bottom: const TabBar(
              tabs: [
                Tab(icon: Icon(Icons.flight), text: "航班"),
                Tab(icon: Icon(Icons.train), text: "火车"),
                Tab(icon: Icon(Icons.directions_car), text: "自驾"),
              ],
              indicatorColor: Colors.white,
              labelColor: Colors.white,
            ),
          ),
          body: TabBarView(
            children: [
              _buildPage("航班预订页面", Colors.blue[100]!),
              _buildPage("火车票查询", Colors.green[100]!),
              _buildPage("租车服务", Colors.orange[100]!),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildPage(String msg, Color color) {
    return Container(
      color: color,
      child: Center(
        child: Text(
          msg,
          style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
        ),
      ),
    );
  }
}

高级用法

1. 手动控制 TabController

dart
class _TabDemoState extends State<TabDemo>
    with SingleTickerProviderStateMixin {

  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        bottom: TabBar(
          controller: _tabController,
          tabs: [/*...*/],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [/*...*/],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _tabController.animateTo(2), // 切换到第三个标签
        child: Icon(Icons.swap_horiz),
      ),
    );
  }
}

2. 自定义标签样式

dart
TabBar(
  tabs: [/*...*/],
  indicator: BoxDecoration(
    color: Colors.deepPurple,
    borderRadius: BorderRadius.circular(10),
  ),
  labelStyle: TextStyle(
    fontSize: 16,
    fontWeight: FontWeight.bold
  ),
  unselectedLabelColor: Colors.grey,
)

3. 带图标的标签

dart
TabBar(
  tabs: [
    Tab(icon: Icon(Icons.chat), text: "聊天"),
    Tab(icon: Icon(Icons.contacts), text: "联系人"),
    Tab(icon: Icon(Icons.settings), text: "设置"),
  ],
)

常见问题

1. 标签与内容数量不一致

错误信息: 'package:flutter/src/material/tab_controller.dart': Failed assertion: 'length == tabs.length'

解决方案: 确保 DefaultTabController 的 length 与 TabBar 的 tabs 数量和 TabBarView 的 children 数量完全一致。

2. 嵌套滚动冲突

现象:
TabBarView 内部有可滚动组件时,滑动冲突。

解决方案:

dart
TabBarView(
  physics: NeverScrollableScrollPhysics(), // 禁用TabBarView滑动
  children: [
    ListView(/*...*/), // 内部组件自行处理滚动
  ],
)

3. 动态更新标签

需求: 运行时添加/删除标签

解决方案:

dart
// 1. 使用StatefulWidget管理状态
// 2. 在TabController初始化时添加监听
_tabController = TabController(length: _tabs.length, vsync: this);
_tabController.addListener(_handleTabChange);

// 3. 更新数据后重建
setState(() {
  _tabs.add(Tab(text: "新标签"));
  _tabController = TabController(
    length: _tabs.length,
    vsync: this,
    initialIndex: _tabController.index
  );
});

4. 自定义标签布局

使用 CustomScrollView + SliverAppBar 实现复杂布局:

dart
CustomScrollView(
  slivers: [
    SliverAppBar(
      expandedHeight: 200,
      flexibleSpace: FlexibleSpaceBar(/*...*/),
      bottom: TabBar(/*...*/),
    ),
    SliverFillRemaining(
      child: TabBarView(/*...*/),
    )
  ],
)

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

preferredSize 组件

  • PreferredSize 可以改变 appBar 的高度

1. 自定义 AppBar 底部组件

当需要创建自定义的 AppBar 底部组件时,必须使用 PreferredSize 包装:

dart
AppBar(
  title: Text('自定义 AppBar'),
  bottom: PreferredSize(
    preferredSize: Size.fromHeight(80.0),
    child: Container(
      height: 80,
      color: Colors.blue,
      child: Center(
        child: Text('自定义底部组件'),
      ),
    ),
  ),
)

2. 创建自定义 TabBar

实现自定义样式的 TabBar:

dart
PreferredSize(
  preferredSize: Size.fromHeight(60.0),
  child: Container(
    decoration: BoxDecoration(
      color: Colors.deepPurple,
      borderRadius: BorderRadius.circular(30),
    ),
    margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    child: TabBar(
      tabs: [
        Tab(text: '首页'),
        Tab(text: '发现'),
        Tab(text: '我的'),
      ],
      indicator: BoxDecoration(
        borderRadius: BorderRadius.circular(30),
        color: Colors.purpleAccent,
      ),
      labelColor: Colors.white,
      unselectedLabelColor: Colors.white70,
    ),
  ),
)

3. 固定高度的标题栏

创建固定高度的自定义标题栏:

dart
PreferredSize(
  preferredSize: Size.fromHeight(100.0),
  child: Container(
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: [Colors.blue, Colors.purple],
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
      ),
    ),
    child: Padding(
      padding: const EdgeInsets.only(top: 40.0),
      child: Center(
        child: Text(
          '应用标题',
          style: TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
            color: Colors.white,
          ),
        ),
      ),
    ),
  ),
)

完整示例:自定义 AppBar

dart
import 'package:flutter/material.dart';

void main() => runApp(const CustomAppBarDemo());

class CustomAppBarDemo extends StatelessWidget {
  const CustomAppBarDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: PreferredSize(
          preferredSize: Size.fromHeight(150.0), // 自定义高度
          child: Container(
            decoration: const BoxDecoration(
              gradient: LinearGradient(
                colors: [Colors.deepPurple, Colors.purpleAccent],
                begin: Alignment.topLeft,
                end: Alignment.bottomRight,
              ),
              boxShadow: [
                BoxShadow(
                  color: Colors.black26,
                  blurRadius: 10,
                  offset: Offset(0, 4),
                )
              ],
            ),
            child: SafeArea(
              child: Column(
                children: [
                  // 顶部状态栏区域
                  SizedBox(height: MediaQuery.of(context).padding.top),

                  // 标题区域
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16.0),
                    child: Row(
                      children: [
                        IconButton(
                          icon: const Icon(Icons.menu, color: Colors.white),
                          onPressed: () {},
                        ),
                        const Expanded(
                          child: Text(
                            '我的应用',
                            style: TextStyle(
                              fontSize: 20,
                              fontWeight: FontWeight.bold,
                              color: Colors.white,
                            ),
                          ),
                        ),
                        IconButton(
                          icon: const Icon(Icons.search, color: Colors.white),
                          onPressed: () {},
                        ),
                      ],
                    ),
                  ),

                  // 标签栏
                  const PreferredSize(
                    preferredSize: Size.fromHeight(48.0),
                    child: TabBar(
                      tabs: [
                        Tab(text: '推荐'),
                        Tab(text: '热门'),
                        Tab(text: '最新'),
                      ],
                      indicatorColor: Colors.white,
                      labelColor: Colors.white,
                      unselectedLabelColor: Colors.white70,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
        body: TabBarView(
          children: [
            _buildPage('推荐内容', Colors.blue[100]!),
            _buildPage('热门内容', Colors.green[100]!),
            _buildPage('最新内容', Colors.orange[100]!),
          ],
        ),
      ),
    );
  }

  Widget _buildPage(String msg, Color color) {
    return Container(
      color: color,
      child: Center(
        child: Text(
          msg,
          style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
        ),
      ),
    );
  }
}

高级用法

响应式 PreferredSize

根据屏幕尺寸动态调整首选大小:

dart
PreferredSize(
  preferredSize: Size(
    double.infinity,
    MediaQuery.of(context).size.height * 0.15,
  ),
  child: // ...
)

与 SliverAppBar 结合使用

在 CustomScrollView 中使用:

dart
CustomScrollView(
  slivers: [
    SliverAppBar(
      expandedHeight: 200.0,
      flexibleSpace: FlexibleSpaceBar(
        title: Text('弹性标题'),
      ),
      bottom: PreferredSize(
        preferredSize: Size.fromHeight(50.0),
        child: TabBar(
          tabs: [/* ... */],
        ),
      ),
    ),
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (context, index) => ListTile(title: Text('项目 $index')),
        childCount: 20,
      ),
    ),
  ],
)

阴影效果被裁剪

问题:自定义 AppBar 的阴影效果被裁剪
解决方案:在容器外部添加阴影,而不是在内部

dart
PreferredSize(
  preferredSize: Size.fromHeight(100),
  child: Container(
    decoration: BoxDecoration(
      boxShadow: [
        BoxShadow(color: Colors.black26, blurRadius: 10, spreadRadius: 2)
      ],
    ),
    child: // 实际内容
  ),
)

GetX Dialog


基础对话框

默认对话框

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

核心参数

参数类型默认值说明
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: ...,
    ),
  ),
);