flutter 常用组件

81 阅读18分钟

Container

Container 是 Flutter 中最常用、最强大的布局组件之一,它是一个单子组件容器,可以用来控制子组件的大小、边距、填充、装饰(背景、边框、阴影、圆角等)、对齐、变换等属性。

简单来说:Container = 装饰(Decoration) + Padding + Margin + 固定宽高/约束 + 对齐 + 变换 的组合。

核心作用

  • 当你想给一个 Widget 加背景色、圆角、边框、阴影 时 → 用 Container。
  • 当你想控制一个 Widget 的 宽高、内外边距、对齐方式 时 → 用 Container。
  • 当你想居中、偏移、旋转某个子组件时 → 用 Container。

常用参数一览

参数作用示例值/说明
child容器内的唯一子组件任何 Widget(如 Text、Image、Column)
width / height固定宽度/高度100.0、double.infinity
padding内边距(内容与容器边框之间的距离)EdgeInsets.all(16)
margin外边距(容器与其他组件的距离)EdgeInsets.symmetric(horizontal: 20)
decoration装饰:背景色、边框、圆角、阴影、渐变等BoxDecoration(...)
color背景色(简便写法,不能与 decoration 同时用)Colors.blue
alignment子组件在容器内的对齐方式Alignment.center
constraints额外尺寸约束BoxConstraints(maxWidth: 200)
transform变换(旋转、缩放、平移)Matrix4.rotationZ(0.1)

常见使用示例

Container(
  padding: EdgeInsets.all(16),
  margin: EdgeInsets.symmetric(vertical: 8),
  decoration: BoxDecoration(
    color: Colors.white, // 背景色
    // 渐变背景色
    gradient: LinearGradient(
      begin: Alignment.topCenter,
      end: Alignment.bottomCenter,
      colors: [Color(0xFFFEF4D7), Color(0xFFFFFFFF)],
      stops: [0.0, 1.0],
    ),
    // 圆角
    borderRadius: BorderRadius.circular(10),
    // 边框
    border: Border.all(color: Color(0xFFD5D5D5), width: 1),
    // 阴影
    boxShadow: [
      BoxShadow(
            color: Color(0xFFD6D6D6),
            offset: Offset(0, 10),
            blurRadius: 0,
            spreadRadius: 0,
      ),
    ],
  ),
  child: Text('这是一个卡片'),
)

注意事项

  • color 和 decoration 不能同时使用: 如果你用了 decoration,背景色要写在 BoxDecoration(color: ...) 里。
  • 没有 child 时:Container 会尽量收缩(除非指定宽高)。
  • 性能:Container 本身很轻量,但嵌套太多层时建议用 Padding/Margin/DecoratedBox 替代部分功能以优化。

与其他组件的关系(替代方案)

需求可以用 Container更轻量的替代方案
只有内边距可以用 Padding
只有外边距可以用 Margin(或 SizedBox)
只有背景/边框/圆角可以用 DecoratedBox
居中子组件可以用 Center

文本

文本展示

Text(
                    '文本内容',
                    style: TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.w500,
                      color: Color(0xff333333),
                    ),
                  ),

限制行数:增加2个参数即可

maxLines: 1,
overflow: TextOverflow.ellipsis,

RichText 和 Text.rich

显示富文本(即一段文字中包含多种样式,如不同颜色、字体、大小、粗体等)

它们的区别仅在于使用方式:

项目RichTextText.rich
类型独立的 WidgetText Widget 的命名构造函数
使用方式直接作为 Widget 使用,需要传入 TextSpan通过 Text Widget 的静态方法传入 TextSpan
代码风格更“底层”、更明确(常用于复杂自定义布局)更简洁、更符合 Text 的语义(推荐日常使用)
性能完全相同完全相同
适用场景需要与其它 Inline Widget(如 WidgetSpan)混合布局时纯文本多样式显示(最常见场景)

RichText

RichText(
  text: TextSpan(
    style: TextStyle(color: Colors.black, fontSize: 16),
    children: [
      TextSpan(text: '你好,'),
      TextSpan(text: '世界', style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),
      TextSpan(text: '!'),
    ],
  ),
),

Text.rich

Text.rich(
  TextSpan(
    style: TextStyle(color: Colors.black, fontSize: 16),
    children: [
      TextSpan(text: '你好,'),
      TextSpan(text: '世界', style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),
      TextSpan(text: '!'),
    ],
  ),
),

从 Flutter 1.20 开始,官方文档更倾向于推荐使用 Text.rich,因为:

  • 语义更清晰:你明明是在写一个“Text”,为什么不用 Text 呢?
  • 代码更一致:和其他 Text 构造函数(如 Text('普通文字'))风格统一。
  • 更易读:一眼看出这是个 Text Widget。

什么时候必须用 RichText?

只有在极少数情况下,你需要将富文本与其他 inline-level 的 Widget 混合(如图片、自定义小部件)时,才必须用 RichText + WidgetSpan:

RichText(
  text: TextSpan(
    children: [
      TextSpan(text: '文字 '),
      WidgetSpan(child: Icon(Icons.star, size: 18, color: Colors.amber)),
      TextSpan(text: ' 更多文字'),
    ],
  ),
),

注意:Text.rich 不支持 WidgetSpan,所以这种场景只能用 RichText。

总结

  • 日常 99% 的富文本场景:用 Text.rich 更简洁、推荐。
  • 需要嵌入图片、Icon、小部件等:必须用 RichText + WidgetSpan。
  • 两者性能无差别,效果完全一致。

推荐:以后写多样式文本,直接用 Text.rich 就行了!

补充

如果需要让文本有点击功能可以这样做:

1.引入

import 'package:flutter/gestures.dart';

2.实现点击功能

TextSpan(
                            text: '去编辑',
                            style: TextStyle(color: Color(0xff4A80F0)),
                            recognizer: TapGestureRecognizer()
                              ..onTap = () async {},
                          ),

flex布局(Row、Column、Wrap)

Flutter 中 Row、Column 和 Wrap 都是用来排列多个子组件(children)的布局 Widget,它们都属于 Flex 布局家族,但适用场景和行为有明显区别。

Widget排列方向主轴(Main Axis)交叉轴(Cross Axis)当子组件超出空间时的行为典型使用场景
Row水平(从左到右)水平方向垂直方向不自动换行,超出屏幕会报溢出错误(黄色警告条)顶部导航栏、按钮组、水平列表项
Column垂直(从上到下)垂直方向水平方向不自动换行,超出屏幕会报溢出错误页面主体内容、表单、垂直列表项
Wrap水平开始,空间不够自动换行水平方向(run)垂直方向(spacing)自动换行,子组件太多会向下延伸,不会溢出标签云、表情包选择、芯片(Chip)组

核心区别总结

  • Row 和 Column不换行,严格单行/单列布局,适合固定数量的子组件。
  • Wrap会自动换行,像“流式布局”,适合子组件数量不确定或动态的情况。

Row

水平排列,不换行(如果子组件太多,会溢出报错(屏幕放不下))

                  Row(
                    children: List.generate(
                      3,
                      (i) => Container(
                        width: 80,
                        height: 80,
                        color: Colors.blue[100 * (i % 9)],
                        margin: EdgeInsets.all(4),
                        child: Center(child: Text('$i')),
                      ),
                    ),
                  ),

image.png

Column

垂直排列,不换行(太多会向下溢出(需要放在 SingleChildScrollView 中才能滚动))

Column(
                    children: List.generate(
                      5,
                      (i) => Container(
                        height: 60,
                        color: Colors.green[100 * (i % 9)],
                        margin: EdgeInsets.all(4),
                        child: Center(child: Text('$i')),
                      ),
                    ),
                  ),

image.png

Expanded

占满(Row、Column)的剩余空间

Wrap

自动换行,像标签云(自动换行,不会溢出,非常自然)

Wrap(
            spacing: 8.0, // 水平间距
            runSpacing: 8.0, // 垂直间距(换行后)
            alignment: WrapAlignment.start,
            children: List.generate(typeList.length, (i) {
              final item = typeList[i];
              return Container(
                padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                decoration: BoxDecoration(
                  color: Colors.orange[100 * (i % 9)],
                  borderRadius: BorderRadius.circular(20),
                ),
                child: Text('标签 $i'),
              );
            }),
          ),

image.png

实际项目中使用建议

  • 需要固定一行按钮或图标 → 用 Row
  • 需要垂直堆叠内容(如个人信息、列表项) → 用 Column
  • 子组件数量不确定,想自动换行(如标签、表情、芯片) → 用 Wrap
  • 想让 Row/Column 也能滚动 → 包裹在 SingleChildScrollView 中
  • 想让 Wrap 支持滚动 → 同样可以放进 SingleChildScrollView

SingleChildScrollView

SingleChildScrollView 是 Flutter 中最简单的滚动容器,它的作用是为单个子组件添加滚动能力。

核心特点

  • 只接受一个 child(子组件)。
  • 当 child 的尺寸超过屏幕时,自动提供滚动(垂直或水平)。
  • 不负责布局,只负责“让内容可滚动”。
  • 性能开销极小,适合内容不太长或子组件简单的场景。

常用参数

  • scrollDirection: Axis.vertical(默认垂直滚动)或 Axis.horizontal(水平滚动)。
  • physics:滚动物理效果(如 BouncingScrollPhysics iOS 回弹)。
  • padding、clipBehavior 等。

注意:下面这行代码会强制 SingleChildScrollView 即使内容短也能响应滚动/下拉手势

physics: const AlwaysScrollableScrollPhysics(),

适用场景

  • 长页面整体滚动(表单、详情页)。
  • 水平滚动图片轮播(配合 Row)。
  • 标签云可滚动(配合 Wrap)。
  • 内容高度不确定但不太多的情况。

注意

  • 如果数据量很大(数百上千项),不推荐用 SingleChildScrollView + Column(会一次性构建所有子组件,性能差)。
  • 这种情况下应该用 ListView或与ListView类似的组件。

列表(ListView、GridView、StaggeredGridView)

这三个都是内置或常用的可滚动列表/网格组件,它们自带滚动能力(内部已包裹类似 SingleChildScrollView 的机制),并且支持懒加载(builder 模式),非常适合大数据量场景。

Widget布局类型子组件特点懒加载支持典型场景需要额外包?
ListView垂直列表(可水平)单列,子组件高度可不同是(.builder)聊天记录、新闻feed、设置页面、长列表无(内置)
GridView规则网格多列,所有子组件宽高统一是(.builder)商品列表、相册、表情包、图标选择无(内置)
StaggeredGridView不规则网格(瀑布流)多列,每项高度可不同Pinterest 图片墙、动态卡片、内容高度不一的列表是(flutter_staggered_grid_view)

共同优点(相比 SingleChildScrollView):

  • 支持懒加载(.builder 构造函数):只构建可见项,性能极好,适合成百上千甚至上万数据。
  • 自带滚动,无需额外包裹。
  • 支持分隔线(ListView.separated)、缓存(cacheExtent)等优化。

ListView

适用于绝大多数垂直滚动列表

ListView.builder(
                    itemCount: 50, // 假设有50条数据
                    itemBuilder: (context, index) {
                      return Container();
                    },
                  ),

ListView 水平布局

List<String> items = ['苹果', '香蕉', '橙子', '芒果', '菠萝', '草莓'];

SizedBox(
  height: 140,  // 必须给高度,否则 ListView 不知道怎么布局
  child: ListView.builder(
    scrollDirection: Axis.horizontal,
    itemCount: items.length,
    itemBuilder: (context, index) {
      return Container(
        width: 100,
        margin: const EdgeInsets.symmetric(horizontal: 8),
        decoration: BoxDecoration(
          color: Colors.blue[100],
          borderRadius: BorderRadius.circular(12),
        ),
        child: Center(child: Text(items[index])),
      );
    },
  ),
)

为什么加 SizedBox(height: ...) ?

水平 ListView 需要明确的高度(否则会报错或高度塌陷为 0)

GridView

所有格子大小一致,整齐美观

GridView.builder(
                    padding: const EdgeInsets.all(10),
                    gridDelegate:
                        const SliverGridDelegateWithFixedCrossAxisCount(
                          crossAxisCount: 3, // 每行显示3列
                          crossAxisSpacing: 10, // 列间距
                          mainAxisSpacing: 10, // 行间距
                          childAspectRatio: 1.0, // 子项宽高比(1.0 表示正方形)
                        ),
                    itemCount: 50,
                    itemBuilder: (context, index) {
                      return Container();
                    },
                  ),

注意:如果ListView、GridView组件是SingleChildScrollView组件的子组件那么需要加上下面两个属性,不然会报错:

  • SingleChildScrollView和ListView/GridView都是滚动组件,嵌套时会导致"谁滚动谁"的问题。
  • ListView/GridView默认会占用无限高度,导致布局溢出。
  • 解决方案: 让ListView/GridView的高度自适应并禁止内部滚动
shrinkWrap: true, // 让高度自适应
physics: NeverScrollableScrollPhysics(), // 禁止内部滚动

清除ListView、GridView组件自带的边距:

padding: EdgeInsets.zero, // 清除 ListView 的默认内边距);

StaggeredGridView

瀑布流,高度由内容决定,排列紧凑不浪费空间,视觉效果最佳

注意需要安装插件:flutter_staggered_grid_view

具体使用请看官方文档

// 瀑布流布局
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
MasonryGridView.count(
                            shrinkWrap: true, // 让高度自适应
                            physics: NeverScrollableScrollPhysics(), // 禁止内部滚动
                            crossAxisCount: 2, // 固定 2 列
                            mainAxisSpacing: 12, // 垂直间距
                            crossAxisSpacing: 12, // 水平间距
                            itemCount: 5,
                            itemBuilder: (context, index) {
                              // 模拟不同高度(实际项目中高度由内容决定,比如图片+文字)
                              final height =
                                  100 + (index % 5) * 40.0; // 示例:100~260px 随机高度

                              return Container(
                                height: height,
                                decoration: BoxDecoration(
                                  color:
                                      Colors.primaries[index %
                                          Colors.primaries.length],
                                  borderRadius: BorderRadius.circular(12),
                                ),
                                alignment: Alignment.center,
                                child: Text(
                                  '${index + 1}',
                                  style: const TextStyle(
                                    fontSize: 32,
                                    color: Colors.white,
                                  ),
                                ),
                              );
                            },
                          ),

虚线/实线

实线

// 方式一
Divider(color: Color(0xff292D3B), thickness: 1),
// 方式二
Container(height: 1.h, width: 168.w, color: Color(0xff006CBA)),

虚线

SizedBox(
                        height: 1, // 厚度
                        child: LayoutBuilder(
                          builder:
                              (
                                BuildContext context,
                                BoxConstraints constraints,
                              ) {
                                const double dashWidth =
                                    4.0; // 每段短線的寬度(可調整,2~6 都行)
                                const double dashSpace = 4.0; // 短線之間的間距(可調整)
                                final int dashCount =
                                    (constraints.maxWidth /
                                            (dashWidth + dashSpace))
                                        .floor() +
                                    1;

                                return Flex(
                                  direction: Axis.horizontal,
                                  mainAxisAlignment:
                                      MainAxisAlignment.spaceBetween,
                                  children: List.generate(dashCount, (_) {
                                    return SizedBox(
                                      width: dashWidth,
                                      height: 1,
                                      child: DecoratedBox(
                                        decoration: BoxDecoration(
                                          color: Color(0xff292D3B),
                                        ),
                                      ),
                                    );
                                  }),
                                );
                              },
                        ),
                      ),

实现点击事件

flutter中的点击事件需要GestureDetector

GestureDetector(onTap: () {}, child: Container()),

裁剪

Flutter 中提供了多种裁剪(Clip)组件,它们的作用是强制限制子组件的绘制区域,防止内容溢出指定的形状,从而实现圆角、圆形、矩形或自定义形状的视觉效果。

裁剪组件本质上是在绘制阶段“剪掉”子组件超出部分的内容,非常适合实现圆角图片、圆形头像、波浪边框等 UI。

常用裁剪组件对比

组件名称裁剪形状主要用途是否支持抗锯齿(推荐)性能/灵活性
ClipRRect圆角矩形(Rounded Rect)最常用:圆角卡片、圆角图片、圆角容器是(默认 antiAlias)
ClipRect直角矩形简单矩形裁剪,避免内容溢出可选
ClipOval椭圆 / 正圆(宽高相等时为圆)圆形头像、圆形按钮、圆形图标
ClipPath任意自定义路径复杂形状(如波浪、心形、五角星、对话气泡箭头)可选中(稍复杂)

详细介绍每个组件

1. ClipRRect(最常用!)

  • 作用:将子组件裁剪成圆角矩形。

  • 关键参数

    • borderRadius:圆角半径(BorderRadius.circular(20) 等)。
    • clipBehavior:裁剪模式,默认 Clip.antiAlias(抗锯齿,边缘更平滑)。

示例:圆角图片卡片

ClipRRect(
  borderRadius: BorderRadius.circular(16),  // 圆角16
  child: Image.network(
    'https://example.com/image.jpg',
    width: 200,
    height: 200,
    fit: BoxFit.cover,  // 关键:填满并被裁剪
  ),
),

2. ClipOval(专为圆形设计)

  • 作用:裁剪成椭圆,宽高相等时就是正圆。
  • 无需设置 radius,直接根据容器大小自动计算。

示例:圆形头像

ClipOval(
  child: Image.network(
    'https://example.com/avatar.jpg',
    width: 80,
    height: 80,
    fit: BoxFit.cover,
  ),
),

更推荐:对于纯圆形头像,Flutter 官方提供了 CircleAvatar(内置 ClipOval + 更多头像功能),优先用它。

3. ClipRect(简单矩形裁剪)

  • 作用:裁剪成直角矩形,常用于防止子组件溢出父容器。
  • 适合临时限制范围。

示例:只显示图片上半部分

ClipRect(
  child: Image.network(
    'https://example.com/tall-image.jpg',
    width: 200,
    height: 100,  // 只显示前100高度
    fit: BoxFit.cover,
  ),
),

4. ClipPath(最灵活,自定义形状)

  • 作用:通过 CustomClipper 定义任意路径裁剪。
  • 适合高级 UI:对话气泡小箭头、票券虚线边、不规则卡片等。

示例:三角形裁剪

ClipPath(
  clipper: TriangleClipper(),
  child: Container(
    width: 200,
    height: 200,
    color: Colors.purple,
  ),
)

// 自定义 Clipper
class TriangleClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.lineTo(size.width / 2, size.height);
    path.lineTo(size.width, 0);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}

使用建议总结

需求场景推荐组件原因
圆角图片、卡片ClipRRect最常用、最简单、支持抗锯齿
圆形头像ClipOval 或 CircleAvatarClipOval 更底层,CircleAvatar 更语义化
防止内容溢出矩形区域ClipRect轻量高效
复杂不规则形状(波浪、箭头)ClipPath完全自定义
Container 已有圆角但子元素不裁剪加 ClipRRect 或 Container(clipBehavior: Clip.hardEdge)解决常见“圆角失效”问题

性能提示:裁剪操作在 GPU 上执行,性能很好,放心使用。但避免在高频动画中频繁重建 CustomClipper。

CircleAvatar 实现圆形头像

CircleAvatar(
  radius: 50,  // 半径(直径=100),控制大小
  backgroundImage: NetworkImage('https://example.com/user-avatar.jpg'),  // 网络图片
  // backgroundImage: AssetImage('assets/avatar.png'),  // 本地资产图片
  // backgroundColor: Colors.grey,  // 无图片时的背景色
  // child: Text('P', style: TextStyle(fontSize: 40, color: Colors.white)),  // 无图片时显示首字母
),

需求:头像外层加边框和阴影

CircleAvatar(
  radius: 50,
  backgroundColor: Colors.white,  // 边框颜色
  foregroundImage: NetworkImage('https://example.com/avatar.jpg'),
  child: Container(
    decoration: BoxDecoration(
      shape: BoxShape.circle,
      boxShadow: [
        BoxShadow(
          color: Colors.black26,
          blurRadius: 8,
          offset: Offset(0, 4),
        ),
      ],
    ),
  ),
),

输入框

  • 记得在页面销毁时执行:phoneFocusNode.dispose();
  • 关闭所有焦点:FocusScope.of(context).unfocus();
  • 获取焦点:phoneFocusNode.requestFocus();
final TextEditingController phoneController = TextEditingController();
  final FocusNode phoneFocusNode = FocusNode();
  Future<void> handlePhone() async {}
 TextField(
          controller: phoneController,
          focusNode: phoneFocusNode,
          keyboardType: TextInputType.phone, // 输入的类型
          cursorColor: Color(0xff63C1C4), // 输入框光标颜色
          style: TextStyle(color: Colors.black, fontSize: 14.sp), // 文本样式
          textInputAction: TextInputAction.go, // 设置键盘右下角按键的文本(或图标)
          onSubmitted: (value) => handlePhone(), // 点击键盘右下角按键的回调
          decoration: InputDecoration(
            hintText: '请输入手机号',
            hintStyle: TextStyle(
              color: Color(0xff9a9a9a),
              fontSize: 14.sp,
            ), // 占位符样式
            // 未激活时边框样式
            enabledBorder: OutlineInputBorder(
              borderSide: BorderSide(color: Color(0xffffffff)),
              borderRadius: BorderRadius.all(Radius.circular(15.r)),
            ),
            // 激活时边框样式
            focusedBorder: OutlineInputBorder(
              borderSide: BorderSide(color: Color(0xff63C1C4)),
              borderRadius: BorderRadius.all(Radius.circular(15.r)),
            ),
            isDense: true, // 缩小输入框高度
            // 输入框内边距
            contentPadding: EdgeInsets.symmetric(
              vertical: 12.h,
              horizontal: 20.w,
            ),
            // 前置图标
            prefixIcon: Container(
              padding: EdgeInsets.only(left: 20.w),
              child: Image.asset(
                'assets/images/login/sjh.png',
                width: 22.w,
                height: 22.w,
              ),
            ),
            // 前置图标取消默认的最小约束
            prefixIconConstraints: const BoxConstraints(
              minWidth: 0,
              minHeight: 0,
            ),
          ),
        ),

多行输入的文本域

多行输入的文本域用的也是输入框:TextField,只不过是加上一些属性:

属性说明常见值
maxLines最大行数null(无限)、5、8 等
minLines最小显示行数1、3、4
maxLength最大字符数100、500、2000 等

按钮

Flutter 中提供了丰富的按钮组件,主要基于 Material Design 风格(Material 3 已全面更新)。下面介绍最常用的几种按钮,以及它们的特点和适用场景。

按钮类型Widget 名外观特点推荐场景旧版对应(已弃用)
FilledButtonFilledButton实心填充背景色,无阴影(默认主按钮)主要动作(Primary Action),如“提交”“确认”RaisedButton
FilledButton.tonalFilledButton.tonal次级填充(柔和色调)次要但重要的动作-
ElevatedButtonElevatedButton实心背景 + 阴影(有立体感)需要突出但不是最主要的动作RaisedButton
OutlinedButtonOutlinedButton只有描边,无填充中等重要动作,如“取消”“下一步”OutlineButton
TextButtonTextButton纯文本,无背景无边框次要动作、链接风格,如“查看更多”FlatButton
IconButtonIconButton只显示图标,水波纹点击效果工具栏、列表项操作(如删除、喜欢)-

特殊按钮

  • FloatingActionButton(FAB) 圆形、浮动在内容之上,通常用于最重要的动作(如“添加”)。

    FloatingActionButton(
      onPressed: () {},
      child: Icon(Icons.add),
    )
    

代码示例(常用按钮对比)

Column(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    FilledButton(onPressed: () {}, child: Text('主要按钮')),
    FilledButton.tonal(onPressed: () {}, child: Text('柔和填充按钮')),
    ElevatedButton(onPressed: () {}, child: Text('凸起按钮')),
    OutlinedButton(onPressed: () {}, child: Text('描边按钮')),
    TextButton(onPressed: () {}, child: Text('文本按钮')),
    IconButton(onPressed: () {}, icon: Icon(Icons.favorite)),
  ],
)

自定义样式

所有按钮都支持 style 参数,可以统一主题或单独自定义:

FilledButton(
  onPressed: () {},
  style: FilledButton.styleFrom(
    backgroundColor: Colors.purple,
    foregroundColor: Colors.white,
    padding: EdgeInsets.symmetric(horizontal: 30, vertical: 15),
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
  ),
  child: Text('自定义按钮'),
)

选择建议

  • 最重要的动作 → FilledButton(默认主题色最显眼)
  • 次要动作 → FilledButton.tonal 或 ElevatedButton
  • 取消/中性动作 → OutlinedButton
  • 内联文字操作 → TextButton
  • 图标操作 → IconButton
  • 页面核心操作 → FloatingActionButton

绝对定位

在 Flutter 中,没有直接对应的 CSS position: absolute,但可以通过几种方式实现类似绝对定位的效果(即让子组件相对于父容器某个角落、边缘或任意位置定位,而不参与正常流式布局)。

下面按推荐顺序介绍最常用的几种方法:

1. 最推荐:Stack + Positioned(最接近 CSS absolute)

Stack 是 Flutter 的“层叠布局”,允许子组件叠加。 Positioned 用于在 Stack 中对子组件进行绝对定位。

Stack(
  children: [
    // 背景或基础内容
    Container(color: Colors.blue[100], width: 300, height: 200),

    // 绝对定位的子组件
    Positioned(
      left: 20,    // 距离左边 20
      top: 30,     // 距离顶部 30
      child: Container(width: 80, height: 80, color: Colors.red),
    ),

    Positioned(
      right: 10,   // 距离右边 10
      bottom: 15,  // 距离底部 15
      child: Icon(Icons.favorite, color: Colors.pink, size: 40),
    ),

    Positioned(
      top: 0,
      left: 0,
      right: 0,
      child: Container(height: 50, color: Colors.black54, child: Center(child: Text('标题栏', style: TextStyle(color: Colors.white)))),
    ),
  ],
)

Positioned 参数(类似 CSS):

  • left、top、right、bottom:分别控制距离父容器四边的距离(可组合使用)。
  • width、height:固定宽高(可选)。
  • 如果不指定 width/height,子组件会根据自身内容大小决定。

适用场景:头像上的小红点、浮动按钮、卡片上的标签、复杂叠加布局等。

2. Stack + Align(快速居中或角落定位)

适合快速把子组件放到 Stack 的九宫格位置(类似 CSS 的 top-left、center 等)。

Stack(
  children: [
    Container(color: Colors.grey[300], width: 200, height: 200),
    
    Align(
      alignment: Alignment.topLeft,        // 左上角
      child: Icon(Icons.star, color: Colors.yellow),
    ),
    
    Align(
      alignment: Alignment.center,         // 正中心
      child: Text('中心文字'),
    ),
    
    Align(
      alignment: Alignment.bottomRight,    // 右下角
      child: FloatingActionButton.mini(onPressed: () {}, child: Icon(Icons.add)),
    ),
  ],
)

常用 Alignment 值

  • Alignment.topLeft、topCenter、topRight
  • centerLeft、center、centerRight
  • bottomLeft、bottomCenter、bottomRight
  • 自定义:Alignment(0.5, -0.8)(x/y 范围 -1~1)

3. Stack + Transform(偏移定位)

适合从某个基准点平移(类似 CSS transform: translate)。

Stack(
  children: [
    Container(color: Colors.green[200]),
    
    Transform.translate(
      offset: Offset(50, 100),  // x 偏移 50,y 偏移 100
      child: Container(width: 60, height: 60, color: Colors.orange),
    ),
  ],
)

子元素溢出Stack被裁剪问题

Stack组件默认是会裁剪边界之外的内容,解决方案:

声明属性:clipBehavior: Clip.none(允许子组件溢出 Stack 边界)

总结推荐

CSS 效果Flutter 实现方式
position: absoluteStack + Positioned(最常用)
top/left/right/bottomPositioned 的对应参数
居中Stack + Align(Alignment.center)
角落定位Align 的 topLeft / bottomRight 等
偏移Transform.translate

进行 3D/2D 几何变换(Transform)

Transform 是 Flutter 中一个强大的布局 Widget,它的作用是对子组件应用矩阵变换(Matrix4) ,从而实现**平移(translate)、旋转(rotate)、缩放(scale)、倾斜(skew)**等视觉效果。

简单来说:Transform = 对 child 进行 3D/2D 几何变换,常用于动画、倾斜卡片、偏移定位、3D 翻转等酷炫 UI。

核心特点

  • 不改变子组件在布局流中的位置(布局仍按原位置计算)。
  • 只影响绘制阶段(视觉上移动/旋转,但点击区域通常不变,除非用 Transform.hitTest 调整)。
  • 支持 4x4 矩阵(Matrix4),几乎所有 2D/3D 变换都能实现。

常用构造函数(推荐使用这些静态方法,更简单)

方法作用参数示例
Transform.translate平移(移动位置)offset: Offset(dx, dy)
Transform.rotate旋转angle: 角度(弧度)origin: 旋转中心
Transform.scale缩放scaleXscaleY(或统一 scale
Transform.skew倾斜(错切)skewXskewY(弧度)

代码示例

1. 平移(类似负 margin 或偏移)

Transform.translate(
  offset: Offset(20, -30),  // x 右移20,y 上移30(负值向上)
  child: Container(
    width: 100,
    height: 100,
    color: Colors.blue,
    child: Center(child: Text('向上偏移')),
  ),
)

2. 旋转(常见卡片倾斜效果)

Transform.rotate(
  angle: 0.2,  // 约 11.5 度(π ≈ 3.14,0.2 ≈ 11.5°)
  origin: Offset(0, 50),  // 以左上角向下50px处为旋转中心
  child: Container(
    width: 150,
    height: 100,
    color: Colors.green,
    child: Center(child: Text('旋转卡片')),
  ),
)

3. 缩放(放大/缩小)

Transform.scale(
  scale: 1.5,  // 放大 1.5 倍
  child: Icon(Icons.favorite, size: 50, color: Colors.red),
)

4. 组合变换(高级:矩阵方式)

Transform(
  transform: Matrix4.identity()
    ..rotateZ(0.1)      // 先旋转
    ..scale(1.2)        // 再放大
    ..translate(30, 0), // 最后平移
  child: YourWidget(),
)

常见应用场景

  • 负 margin 效果:用 translate 向上/左偏移(你之前问的重叠布局常用)。
  • 倾斜卡片:rotate 小角度 + 阴影 → 立体感。
  • 3D 翻转动画:配合 AnimationController 用 Matrix4.rotationY。
  • 自定义动画:在 AnimatedBuilder 中动态改变 Transform 参数。

注意事项

  • 点击区域不随变换:旋转后点击区域仍是原矩形(可用 Matrix4 的 hitTest 调整,但复杂)。
  • 性能:轻量,但嵌套太多或复杂矩阵可能影响 GPU。
  • alignment 参数(在构造函数中):控制变换的原点(类似 CSS transform-origin),默认中心。

与其他组件对比

需求推荐方式
简单偏移Transform.translate
动画变换AnimatedContainer 或 Transform + Animation
仅布局偏移(不视觉)用 Padding/Margin/Positioned

总结: Transform 是 Flutter 中实现“视觉变换”的核心工具,配合 Stack、Animation,能做出各种高级 UI 效果。 日常最常用的是 Transform.translate(负值偏移)和 Transform.rotate(倾斜)。

动画(Animation)

Flutter 中的动画(Animation) 是让 UI 元素随时间平滑变化的核心机制,比如淡入淡出、缩放、平移、旋转、颜色渐变等,能极大提升 App 的交互感和精致度。

Flutter 的动画系统设计得非常强大、灵活且性能优秀(全部在 GPU 上运行),分为隐式动画显式动画两大类。

1. 隐式动画(Implicit Animation)—— 最简单、最常用

特点:只需指定“目标状态”,Flutter 自动处理从当前到目标的过渡动画。 适合:大多数日常动画(如按钮点击变大、容器颜色渐变等)。

常见隐式动画 Widget(直接在原 Widget 前加 “Animated”):

Widget作用示例
AnimatedContainer宽高、颜色、圆角、边框等变化动画背景色渐变、卡片展开
AnimatedOpacity透明度渐变(淡入淡出)加载提示淡出、图片渐现
AnimatedAlign对齐方式变化动画子组件从左移到右
AnimatedPadding内边距变化动画按钮点击时扩大内边距
AnimatedPositioned在 Stack 中位置变化动画浮动按钮移动
AnimatedScale缩放动画(Flutter 3.7+)图标点击放大
AnimatedRotation旋转动画箭头展开/收起

示例:点击按钮变大变色

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _expanded = !_expanded),
      child: AnimatedContainer(
        duration: Duration(milliseconds: 300),  // 动画时长(必须)
        curve: Curves.easeInOut,                // 动画曲线(可选)
        width: _expanded ? 200 : 100,
        height: _expanded ? 200 : 100,
        color: _expanded ? Colors.red : Colors.blue,
        child: Center(child: Text('点击我')),
      ),
    );
  }
}

2. 显式动画(Explicit Animation)—— 更强大、更可控

特点:需要手动创建 AnimationController 和 Animation,适合复杂、同步、多阶段或需要精确控制的动画。

核心组件:

  • AnimationController:动画的“控制器”,控制时长、正逆向播放、重复等。
  • Animation:具体的值变化(如 double、Color)。
  • Tween:定义起始值和结束值。
  • CurvedAnimation:添加缓动曲线。

常用显式动画 Widget

  • TweenAnimationBuilder:最简单显式动画(推荐入门)。
  • AnimatedBuilder:性能更好,适合复杂动画。

示例:用 TweenAnimationBuilder 实现旋转动画

TweenAnimationBuilder<double>(
  tween: Tween(begin: 0.0, end: 1.0),
  duration: Duration(seconds: 2),
  builder: (context, value, child) {
    return Transform.rotate(
      angle: value * 2 * pi,  // 旋转一圈
      child: Icon(Icons.refresh, size: 50),
    );
  },
)

示例:经典 AnimationController(加载旋转圈)

class LoadingWidget extends StatefulWidget {
  @override
  State<LoadingWidget> createState() => _LoadingWidgetState();
}

class _LoadingWidgetState extends State<LoadingWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,  // 必须(提供 ticker)
      duration: Duration(seconds: 1),
    )..repeat();  // 无限重复
  }

  @override
  Widget build(BuildContext context) {
    return RotationTransition(
      turns: _controller,  // 直接用 controller
      child: Icon(Icons.sync, size: 40),
    );
  }

  @override
  void dispose() {
    _controller.dispose();  // 必须释放
    super.dispose();
  }
}

总结建议

需求场景推荐方式
简单状态变化(颜色、大小)隐式动画(AnimatedContainer 等)
淡入淡出、透明度AnimatedOpacity
需要重复、循环、精确控制显式动画 + AnimationController
快速自定义值变化TweenAnimationBuilder
页面切换共享元素Hero 动画

使用技巧

深浅拷贝

浅拷贝

用于大多数业务场景 适用场景

  • data 和 arr 里面的值主要是基本类型(String、int、double、bool、null)
  • 或者你不关心嵌套对象/数组被修改(浅拷贝只复制第一层)
void handlePlaySound(Map<String, dynamic> data, List<dynamic> arr) {
  // 方式1: 创建浅拷贝(最常用、最快)
  final dataCopy = Map<String, dynamic>.from(data);
  final arrCopy  = List<dynamic>.from(arr);
  
  // 方式2:也是浅拷贝,但是没指定数据类型
  final dataCopy = {...data};
  final arrCopy  = [...arr];

  // 现在你可以随便修改 dataCopy 和 arrCopy
  dataCopy['volume'] = 0.8;
  dataCopy.remove('tempKey');
  
  arrCopy.add('new_item');
  arrCopy.removeAt(0);
  arrCopy[1] = 'modified';

  // 后续逻辑使用 dataCopy 和 arrCopy
  // 原 data 和 arr 完全不受影响
}

深拷贝

当 data 或 arr 里面有嵌套的 Map 或 List,并且你希望修改嵌套层也不影响原数据时,需要深拷贝。

注意: json 深拷贝的限制:

  • 只支持可序列化的类型(DateTime、自定义类不行)
  • 会丢失一些特殊类型(Function、Symbol 等)
  • 性能比浅拷贝低一些
import 'dart:convert'; // 用于 json 方法实现深拷贝

void handlePlaySound(Map<String, dynamic> data, List<dynamic> arr) {
  final dataCopy = jsonDecode(jsonEncode(data)) as Map<String, dynamic>;
  final arrCopy  = jsonDecode(jsonEncode(arr))  as List<dynamic>;

  // 现在修改 dataCopy 和 arrCopy 完全安全
  dataCopy['nested']?['child'] = 'new value';
  arrCopy[2]?['sub'] = 999;
}

在数组中根据id查找元素(firstWhere)

Map<String, dynamic> d2 = {'id': 1};

List<Map<String, dynamic>> arr2 = [
  {'id': 1},
  {'id': 2},
  {'id': 3},
];
var foundItem = arr2.firstWhere((item) => item['id'] == d2['id']);

在数组中根据id查找元素的索引(indexWhere)

Map<String, dynamic> d2 = {'id': 1};

List<Map<String, dynamic>> arr2 = [
  {'id': 1},
  {'id': 2},
  {'id': 3},
];

// 找到第一个 id 相等的元素的索引
int index = arr2.indexWhere((item) => item['id'] == d2['id']);

想访问数组中每一个元素和索引

List arr2 = [
  {'id': 1},
  {'id': 2},
  {'id': 3},
];
// 1.使用forin + .indexed(支持await)
for (final (index, item) in arr2.indexed) {
  print('索引: $index, 元素: $item');
  // 或使用 item['id']
  print('id 值: ${item['id']}');
}
// 2.使用.asMap().entries(支持await)
for (final entry in arr2.asMap().entries) {
  final index = entry.key;
  final item  = entry.value;
  
  print('索引: $index, 元素: $item');
  print('id: ${item['id']}');
}
// 3.使用forEach(不支持await)
arr2.asMap().forEach((index, item) {
  print('索引: $index → 元素: $item → id: ${item['id']}');
});
// 4.使用传统 for 循环(支持await)
for (int i = 0; i < arr2.length; i++) {
  final item = arr2[i];
  print('索引: $i, 元素: $item, id: ${item['id']}');
  
  // 如果需要修改元素
  arr2[i]['newKey'] = 'value';
}