Skip to content

Flutter Component

核心定位:UI 的“包装盒” Container 并不是一个基础渲染对象,它只是一个 方便的组合包

Container(
width: 100,
height: 100,
color: Colors.red,
padding: const EdgeInsets.all(10),
alignment: Alignment.center,
child: const Text("Hi"),
)

margin / padding: 默认为 EdgeInsets.zero(无间距)。

alignment: 默认为 null(导致它遵循法则二:包裹内容)。

decoration: 默认为 null(无背景、无边框)。

clipBehavior: 默认为 Clip.none(内容超出不剪切)。

默认行为

  1. 法则一:无子无求,占满全家 (No Child = Fill):有child的时候会默认包裹子组件,宽高由子组件决定

  2. 法则二:有子随子,缩成一团 (Has Child = Wrap):没有的话,会无限放大,占用宽高的可用空间

  3. 法则三:对齐触发的“领地扩张” (Alignment = Expansion)

    一旦你给 Container 设置了 alignment 属性(即使它有 child)

    • 默认表现:它会打破“收缩”行为,转而再次尝试撑满父组件可用空间,然后让子组件在内部进行对齐。
    • 逻辑:如果你要在一个地方“居中”一个东西,你必须先定义出整个可用的空间。
  4. 法则四:约束优先 (Constraints over all)

  5. 不管 Container 想撑开还是收缩,它必须遵守父组件的绝对约束

    • 强制尺寸:如果父组件是 SizedBox(width: 100, height: 100),内部的 Container 设置 width: 500 也是无效的,它会被强制锁死在 100x100。

    • 无边界环境:在 ListView(纵向)里,Container 的高度如果设为 double.infinity 会导致内存溢出/报错,因为父组件在纵向上不提供边界

Flutter 基础布局组件全集 (Container 的拆解版)

功能分类独立组件说明
只需要尺寸固定SizedBox最轻量的固定宽高组件。
尺寸约束ConstrainedBox设置 min/max 宽高。
只需要边距控制Padding专门处理 EdgeInsets 内边距。
只需要背景颜色ColoredBox仅绘制颜色,不涉及其他布局逻辑。
复杂装饰DecoratedBox负责边框、圆角、阴影、渐变。
对齐/居中Align / Center控制子组件在父空间的位置。
矩阵变换Transform处理旋转、缩放、倾斜。
溢出剪辑ClipRect / ClipRRect负责将子组件裁切为矩形或圆角矩形。
比例控制AspectRatio强制子组件维持特定的宽高比。
条件约束LimitedBox仅在父级约束为无限时(如 ListView)生效。

最常用的场景是给两个组件之间加空隙,或者固定子组件大小。

SizedBox(
width: 100,
height: 50,
child: ElevatedButton(onPressed: () {}, child: Text("确定")),
)
// 常见用法:作为间距
// SizedBox(height: 20)

当你希望组件能够自适应,但必须在某个范围内时使用。

  • Container 包含了 ConstrainedBox 所有功能, 同时还能处理样式(颜色、边框)、间距、变换等。
  • ConstrainedBox 只有约束
  • 为什么要区分使用?
  • 追求极致性能:如果你只需要限制尺寸,不需要颜色、间距等,直接使用 ConstrainedBox(或者更简单的 SizedBox)会更轻量,因为跳过了 Container 内部复杂的属性检查逻辑。
  • 代码意图清晰:使用 ConstrainedBox 能明确告诉其他开发者:“我在这里是在做尺寸限制”,而 Container 的意图通常是“我要做一个带样式的区域”
ConstrainedBox(
constraints: BoxConstraints(
minWidth: 100,
maxWidth: 250,
),
child: Text("这段文字短时宽100,长时最多250"),
)
Container (
constraints: BoxConstraints(
minWidth: 100,
maxWidth: 250,
),
child: Text("这段文字短时宽100,长时最多250"),
)

Flutter 中最常用的组件之一,专门负责留白。

Padding(
padding: EdgeInsets.all(16.0),
child: Text("我有 16 像素的内边距"),
)

Container 设置 color 性能更好,因为它只负责绘制颜色,不参与复杂的布局计算。

dart

ColoredBox(
color: Colors.blue,
child: Text("我有蓝色背景"),
)

用于实现圆角、边框、阴影或渐变。

DecoratedBox(
// 1. decoration: 核心参数,定义背景装饰(圆角、颜色、阴影等)
decoration: BoxDecoration(
color: Colors.blue, // 背景色
borderRadius: BorderRadius.circular(10), // 圆角
image: DecorationImage(...), // 2. 背景图片
border: Border.all(...), // 3. 边框
boxShadow: [BoxShadow(...)], // 5. 阴影(数组,可叠加)
gradient: LinearGradient(...), // 6. 渐变色
backgroundBlendMode: BlendMode.multiply, // 7. 混合模式 是一个高级属性,它决定了 BoxDecoration 内部各层(背景色、渐变、背景图)是如何“融合”在一起的。
shape: BoxShape.rectangle, // 8. 形状
),
// 2. position: 决定装饰是在子组件“下面”还是“上面”
// DecorationPosition.background (默认): 背景模式,内容挡住装饰
// DecorationPosition.foreground: 前景模式,装饰挡住内容
position: DecorationPosition.background,
// 3. child: 被装饰的子组件
child: SizedBox(
width: 200,
height: 100,
child: Center(child: Text("内容区域")),
),
)

常见的其他 BlendMode 推荐

除了 multiply,这几个也经常用到:

  • BlendMode.screen (滤色):与 multiply 相反,会让画面变亮,适合做光效。

  • BlendMode.overlay (叠加):在保留底色明暗细节的同时增加饱和度。

  • BlendMode.darken (变暗):只保留两层中较暗的颜色。

  • BlendMode.color (颜色):只保留上层的色相和饱和度,保留底层的亮度(非常适合黑白照上色)。

  • 注意事项

  • 兼容性:在 Flutter Web 上,使用某些复杂的 BlendMode 可能会有性能开销,或者在开启 HTML 渲染器时效果不一致(建议使用 CanvasKit 模式)。

  • 层级逻辑:它只混合 BoxDecoration 内部的东西,不会Container 的子组件(child)也混合进去。如果想让子组件也参与混合,你需要使用 ColorFilteredShaderMask 组件。

Center 实际上是 Align 的一个子类。

dart

Align(
alignment: Alignment.bottomRight,
child: FlutterLogo(size: 50),
)

常用于旋转或倾斜 UI 元素。

dart

Transform.rotate(
angle: 0.2, // 弧度
child: Text("我旋转了"),
)

如果你需要强制让子组件(比如图片)变成圆角,用这个。

ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.network('https://picsum.photos'),
)

常用于视频播放器或图片占位,保证比例不失真。

AspectRatio(
aspectRatio: 16 / 9,
child: ColoredBox(color: Colors.black),
)

当父组件(如 ListView)不限制子组件大小时,给子组件一个“最大值”限制。

为什么需要 LimitedBox

在 Flutter 中,像 ListViewRowColumn 这样的组件,在主轴方向上会给子组件提供 “无限”的约束(Unconstrained)。

  • 如果你在 ListView 里放一个不指定高度的 Container,它会因为想占满“无限”的空间而导致崩溃或无法渲染。
  • 使用 LimitedBox 可以给它一个默认的最大值,这样只有在被放入这种“无限空间”时,它才会有个保底的大小。

核心区别:简单说你封装组件之后被引用,如果组件有宽度父组件的,没有还有个兜底

特性ConstrainedBox / BoxConstraintsLimitedBox
触发条件始终生效。无论父组件约束是什么,它都会强制叠加。仅在父组件约束为“无边界”时生效
强制性极强。会覆盖子组件的原始尺寸。较弱。如果父组件给了明确大小,它就完全消失(不工作)。
典型场景限制按钮的最大宽度、设置容器的最小高度。解决在 ListViewColumn 中组件无限扩张导致的报错。

// 场景 1:在固定大小的父组件里(不生效)
Container(
width: 100,
child: LimitedBox(
maxWidth: 200,
child: Container(color: Colors.red), // 最终还是 100 宽,LimitedBox 被无视了
),
)
// 场景 2:在 ListView 里(生效!)
ListView(
children: [
LimitedBox(
maxHeight: 100, // ListView 纵向是无限的,此时 LimitedBox 起作用
child: Container(color: Colors.blue), // 最终高度 100
),
],
)

Row 的默认表现可以概括为:水平方向“尽可能伸展”,垂直方向“包裹内容”。

默认行为MainAxisSize.max

表现:即使子组件只有一点点大,Row 默认也会横向撑满父组件允许的最大宽度。

后果:如果你在 Row 外面套一个带边框的 Container,你会发现边框横向占满了屏幕。

常见问题

A. 为什么 Row 报错“溢出 (Overflowed)”?

Row 的子组件默认是没有自动换行功能的。如果子组件的总宽度超过了 Row 的最大宽度,就会出现黄色斑马线的溢出警告。

  • 解决方法:给子组件套上 ExpandedFlexible,或者改用 Wrap 组件。

B. 怎么让 Row 只包裹内容而不撑满全屏?

  • mainAxisSize 改为 min:让 Row 宽度等于子组件总和
Row(
// crossAxisAlignment 决定垂直方向的对齐
crossAxisAlignment: CrossAxisAlignment.start, // 置顶对齐
// crossAxisAlignment: CrossAxisAlignment.end, // 置底对齐
// crossAxisAlignment: CrossAxisAlignment.stretch, // 强制子组件填满 Row 的高度
mainAxisSize: MainAxisSize.min, // 关键:让 Row 宽度等于子组件总和
children: [
Container(height: 100, width: 50, color: Colors.red),
Container(height: 50, width: 50, color: Colors.blue),
],
)

Column(列)的表现与 Row 刚好相反,你可以理解为:垂直方向“尽可能伸展”,水平方向“包裹内容”。

默认行为总结

维度属性默认值表现描述
垂直方向 (主轴)mainAxisSizeMainAxisSize.max高度撑满父空间(只要父级允许)。
垂直对齐mainAxisAlignmentMainAxisAlignment.start子组件靠部排列。
水平对齐 (交叉轴)crossAxisAlignmentCrossAxisAlignment.center子组件在水平方向居中对齐。

方案一(收缩):使用 ExpandedFlexible(最常用),因为它们2个都允许收缩

方案二(换行):使用 Wrap 替代 Row/Column

方案三(滚动):使用 SingleChildScrollView 通过滑动查看超出的部分。

如果你的子组件(如 TextImage)太长,套上 Expanded 后,它会被迫收缩并填满剩余空间,不再撑破父容器。

  • 适用场景:想要一个组件自动占满剩下的地方,不许超出。
  • CSS 类比flex: 1
Row(
children: [
const Icon(Icons.star),
Expanded( // 关键:让这段文字在剩下的空间内自动换行/裁剪
child: Text("这是一段非常非常长的文字,不套Expanded就会导致Row溢出报错"),
),
],
)
Wrap(
spacing: 8.0, // 左右间距
runSpacing: 4.0, // 行间距
children: [
Chip(label: Text("标签1")),
Chip(label: Text("标签2")),
Chip(label: Text("超长标签3会换行")),
],

.核心对比:Expanded | Flexible 实际区别就是是否拉伸子组件

组件核心行为类似于 CSS 的
Expanded强制扩张。必须占满分配给它的所有空间,哪怕子组件没那么大。flex: 1
Flexible灵活扩张。允许子组件小于分配的空间(不强制撑开),但绝不能超过空间。flex: 1 (带有 fit: FlexFit.loose)

Row(
children: [
Expanded(
child: Container(width: 50, color: Colors.red), // 虽然设了 50,但会被强行拉伸占满全行剩余空间
),
],
)
Row(
children: [
Flexible(
// 这里的 50px 会被尊重!
child: Container(width: 50, color: Colors.green),
),
// 剩下的 150px 会空着
],
)

css对比

.item {
flex-grow: 1; /* 贪婪:必须吃掉剩余空间 */
flex-shrink: 1; /* 允许收缩 */
flex-basis: 0%; /* 忽略组件自身原始大小,完全按比例分配 */
}
.item {
flex-grow: 1;
flex-shrink: 1;
flex-basis: auto; /* 参考组件自身大小 */
max-width: 100%; /* 绝不超出分给我的份额 */
}

RowColumn 中,如果你不给子组件包裹 ExpandedFlexible,它的默认行为就是不收缩

  • CSS 的 flex-shrink: 0,你可以使用 SizedBoxConstrainedBox 来锁死尺寸:
  • 不要在 Wrap 里直接用 ExpandedExpanded 的前提是父组件必须有剩余空间分配。在 Wrap 中,主轴是“无限”延伸直到换行的,这会导致 Expanded 无法计算宽度而报错。
  • 性能:由于需要计算换行逻辑,性能略低于 Row/Column,但处理几十个标签完全没问题。
Wrap(
// 1. 布局方向
direction: Axis.horizontal, // 默认水平排列(像行一样换行);Axis.vertical 则像列一样换列
// 2. 主轴(行内)对齐方式
alignment: WrapAlignment.start, // 类似于 Row 的 mainAxisAlignment
// 3. 主轴(行内)间距
spacing: 8.0, // 左右两个子组件之间的距离 (CSS: gap)
// 4. 交叉轴(行与行之间)对齐方式
runAlignment: WrapAlignment.start, // 决定多行作为一个整体在纵向如何分布
// 5. 交叉轴(行与行之间)间距
runSpacing: 4.0, // 第一行和第二行之间的距离
// 6. 每一行内部子组件的对齐(如果这一行高度不一致)
crossAxisAlignment: WrapCrossAlignment.center, // 类似于 Row 的 crossAxisAlignment
// 7. 文本方向与排列顺序
textDirection: TextDirection.ltr,
verticalDirection: VerticalDirection.down,
children: <Widget>[ ... ],
)

图片适配:让背景图 Cover 整个屏幕。

Logo 适配:在不同尺寸的导航栏里,让 Logo 自动缩放而不变形。

单行长文本:如余额显示,当数字非常大时,让它自动变小而不是溢出或换行

需要明确边界FittedBox 的父级必须有明确的尺寸限制(Constraints)。如果父级是无限大的(如直接放在横向 ListView 里且不给宽),FittedBox 就不知道该按什么比例缩放了。

性能:它是通过缩放矩阵(Transform)实现的,性能非常高。

Container(
width: 200, // 定义父容器宽度
height: 200, // 定义父容器高度
color: Colors.grey[300],
child: FittedBox(
// 1. fit: 决定子组件如何缩放以适应 200x200 的空间
// BoxFit.contain: 等比例缩放,保证内容完整显示(默认)
// BoxFit.cover: 等比例缩放直到充满容器,超出部分会被裁切
// BoxFit.fill: 强制拉伸填满,不保持比例
// BoxFit.scaleDown: 只有当子组件比容器大时才缩小,小时不放大
fit: BoxFit.cover,
// 2. alignment: 当缩放后有留白或超出时,决定子组件在容器内的摆放位置
// Alignment.center: 居中(默认)
// Alignment.topLeft: 靠左上角
alignment: Alignment.center,
// 3. clipBehavior: 决定如何处理超出容器边界的部分(常与 BoxFit.cover 配合)
// Clip.none: 不剪裁,内容会溢出到容器外(默认)
// Clip.antiAlias: 抗锯齿剪裁,边缘平滑(推荐)
clipBehavior: Clip.antiAlias,
// 4. child: 被缩放的对象
child: const Icon(
Icons.flutter_dash,
size: 400, // 注意:这里的原始尺寸(400)远大于父容器(200)
color: Colors.blue,
),
),
);

我们可以在布局过程中拿到父组件传递的约束信息,然后我们可以根据约束信息动态构建不同的布局

  1. 可以使用 LayoutBuilder 来根据设备的尺寸来实现响应式布局。
  2. LayoutBuilder 可以帮我们高效排查问题。比如我们在遇到布局问题或者想调试组件树中某一个节点布局的约束时 LayoutBuilder 就很有用
// 通过 LayoutBuilder 拿到父组件传递的约束,然后判断 maxWidth 是否小于200
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth < 200) {
// 最大宽度小于200,显示单列
return Column(children: children, mainAxisSize: MainAxisSize.min);
} else {
// 大于200,显示双列
var _children = <Widget>[];
for (var i = 0; i < children.length; i += 2) {
if (i + 1 < children.length) {
_children.add(Row(
children: [children[i], children[i + 1]],
mainAxisSize: MainAxisSize.min,
));
} else {
_children.add(children[i]);
}
}
return Column(children: _children, mainAxisSize: MainAxisSize.min);
}
},
);

获取组件大小和相对于屏幕的坐标

Builder(builder: (context) {
return Container(
color: Colors.grey.shade200,
alignment: Alignment.center,
width: 100,
height: 100,
child: AfterLayout(
callback: (RenderAfterLayout ral) {
Offset offset = ral.localToGlobal(
Offset.zero,
// 传一个父级元素
ancestor: context.findRenderObject(),
);
print('A 在 Container 中占用的空间范围为:${offset & ral.size}');
},
child: Text('A'),
),
);
}),

IntrinsicHeight 是 Flutter 中的一个布局组件,主要用于将子组件的高度强制调整为其“固有高度” (Intrinsic Height)

  • 统一高度:最常见的用途是包裹一个 Row。如果你希望 Row 中所有的子项(如按钮、图标和文本)都与其中最核心/最高的那个组件对齐并拥有相同高度,就需要用到它。

大致什么意思呢?就是默认Row 高度是由子元素中最高元素决定的,剩下在子元素没有设置高度的情况下是保持自己的高度的,IntrinsicHeight 可以帮忙把子元素拉伸到和最高元素一样高

IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch, // 关键:拉伸子项以填充 IntrinsicHeight 确定的高度
children: [
Container(color: Colors.red, height: 50),
Container(color: Colors.blue, height: 100), // Row 的总高度将被定为 100
VerticalDivider(color: Colors.black), // 现在可以正确显示了
],
),
)
  • 很有用 css 中 flex 不设置 items-center 的话会自动拉伸子元素的因为默认值是stretch
  • flutter不会,在不使用IntrinsicHeight 设置 stretch 会报错。

用于展示静态或动态文本。

  • Text:最基础的文本组件。支持样式、对齐、裁剪等(CSS 的 <span><p>)。

  • DefaultTextStyle:用于给子树中所有的 Text 设置默认样式。如果在父级设置了它,其下所有没有设置样式的 Text 都会继承这个样式。

  • Text.rich / RichText:在一句话里实现不同颜色、大小、甚至嵌入图标

  • SelectableText可长按选中、复制的文本。普通 Text 是无法被用户选中的。

  • TextField:最常用的输入框。支持装饰(InputDecoration)、密码隐藏、监听输入等。

  • TextFormFieldTextField 的加强版。通常配合 Form 组件使用,支持表单校验(Validator)

辅助类

Baseline:强制让不同大小的文本按照基线对齐。

需求推荐组件
普通文字显示Text
可复制文字SelectableText
一行文字多种样式RichText
普通搜索/输入TextField
带校验的注册表单TextFormField
文字带图标点击Text.rich + WidgetSpan
Text(
'这是一段全参数演示的文本,内容非常长以便测试换行和裁剪的表现。',
style: TextStyle(
color: Colors.blue,
fontSize: 18,
fontWeight: FontWeight.bold,
height: 1.5, // 1.5倍行高
),
textAlign: TextAlign.center, // 居中对齐
maxLines: 2, // 最多显示两行
overflow: TextOverflow.ellipsis, // 省略号结尾
softWrap: true, // 自动换行
textDirection: TextDirection.ltr, // 从左往右
textWidthBasis: TextWidthBasis.longestLine, // 紧凑布局
)
  • TextOverflow.ellipsis: 显示省略号 ...(最常用)。
  • TextOverflow.clip: 直接切断,没有任何提示(CSS 默认行为)。
  • TextOverflow.fade: 末尾淡出虚化(需要容器有固定高度)。
Row(
children: [
Text("¥", style: TextStyle(fontSize: 20)),
Text("99.9", style: TextStyle(fontSize: 50)),
Text("起", style: TextStyle(fontSize: 20)),
],
)
Row(
children: [
Baseline(
baseline: 50, // 距离父组件顶部的距离
baselineType: TextBaseline.alphabetic, TextBaseline.alphabetic 的意思是:以“字母基线”作为对齐标准
child: Text("¥", style: TextStyle(fontSize: 20)),
),
Baseline(
baseline: 50, // 保持一致的 baseline 值,两个文字的底部就会对齐
baselineType: TextBaseline.alphabetic,
child: Text("99.9", style: TextStyle(fontSize: 50)),
)
],
)

为什么不直接用 Row 的 crossAxisAlignment?

虽然 Row 也有 CrossAxisAlignment.baseline 参数,但它要求你必须同时在 Row 里指定 textBaseline

Baseline 组件的优势在于它是一个独立的容器,你可以更精细地控制某一个特定组件的基线偏移量,而不需要改变整个 Row 的对齐逻辑。

它们与普通 Text 的本质区别在于:Text 只能应用 一种 统一样式,而它们可以组合 一组 样式。

相当于html中的span 可以给一段文字挂样式

虽然它们都接受 TextSpan(样式片段),但使用场景不同:

特性Text.richRichText
定位Text 组件的构造函数一个独立的底层单功能组件
样式继承会自动继承父级的 DefaultTextStyle不继承。如果不手动写样式,文字会变成默认的丑陋白色(甚至看不见)
建议首选。更省事,更符合 Flutter 的样式传播逻辑仅在需要极致性能或完全自定义样式、不希望被外部干扰时使用
Text.rich(
TextSpan(
text: '已阅读并同意', // 基础文字
style: TextStyle(color: Colors.grey), // 默认样式
children: [
TextSpan(
text: '《隐私政策》',
style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
recognizer: TapGestureRecognizer()..onTap = () {
print('跳转到隐私政策');
},
),
const WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Icon(Icons.info, size: 16, color: Colors.grey),
),
],
),
)

核心概念:

在富文本里,你可以放入两种“片段”:

  • TextSpan:用于放置文本。可以无限嵌套 children
    • recognizer: 用于添加点击事件(如超链接)。
  • WidgetSpan:用于在文字中间插入任何 Widget(比如小图标、标签、图片甚至按钮)。
    • alignment: 决定图标如何与文字对齐(如 middle 居中, baseline 基线对齐)。

GestureDetector 是 Flutter 中最强大的 “手势监听器”。它本身没有任何视觉效果(不画颜色,不占额外空间),它的唯一作用就是包裹一个组件,并捕捉用户在其区域内的各种动作。 [[1](https://www.dhiwise.com/post/an-in-depth-dive-into-flutter-gestures-amplifying-your-ui-ux#:~:text=Flutter’s gesture system%2C wrapped in a non-visual,for gesture detection in your Flutter application.)]

你可以把它理解为 CSS 中的各种事件监听(如 onclick, onmousedown, onscroll),但它支持的维度更广,包括缩放和拖拽。

  1. 常用参数分类

A. 点击类(最常用)

B. 拖拽类

C. 缩放类

  • onScaleUpdate: 监听双指缩放(常用于图片放大缩小)。

  1. 行为参数:behavior (解决点击不到的问题)

这是新手最容易踩坑的地方。有时候 Container 没颜色,点击中间就没反应,这时需要设置 behavior

  • HitTestBehavior.deferToChild (默认):只有子组件被点到了才响应。如果 Container 是透明的,点中间没用。
  • HitTestBehavior.opaque:把整个区域当成实心的,即使没有颜色,点击区域内任何地方都会响应。
  • HitTestBehavior.translucent:半透明模式,自己响应的同时,也允许下方的组件响应。

  1. 代码示例:给一个普通盒子加上点击效果

dart

GestureDetector(
// 1. 设置点击行为(即使 Container 是透明的也能点)
behavior: HitTestBehavior.opaque,
// 2. 各种事件回调
onTap: () {
print("被点击了!");
},
onLongPress: () {
print("长按了!");
},
// 3. 被包裹的 UI
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: const Text("点击我", style: TextStyle(color: Colors.white)),
),
)

请谨慎使用此类代码。


  1. GestureDetector vs InkWell

在 Flutter 中还有另一个点击组件叫 InkWell

特性GestureDetectorInkWell
水波纹效果 (只有逻辑处理) (Material 风格的扩散效果)
功能丰富度极高 (包含缩放、拖拽等)一般 (主攻点击、双击、长按)
性能更轻量略重(需要父级有 Material 组件)

  1. 进阶:获取点击坐标

你可以通过 onTapDown 获取用户点击的具体像素位置: [[1](https://www.dhiwise.com/post/an-in-depth-dive-into-flutter-gestures-amplifying-your-ui-ux#:~:text=onTap The onTap callback is triggered when,between pages%2C update state%2C or trigger animations.)]

dart

onTapDown: (TapDownDetails details) {
print("点击位置: ${details.globalPosition}"); // 相对于整个屏幕
print("相对位置: ${details.localPosition}"); // 相对于组件左上角
},

请谨慎使用此类代码。

建议: 如果你只是想做一个带点击效果的按钮,InkWell 体验更好;如果你要做手势交互(如左划删除、图片缩放),必须用 GestureDetector

你现在是想实现一个 简单的按钮点击,还是在做一个 可以拖动或缩放的复杂交互组件如果涉及多个手势冲突,我们可以聊聊手势竞技场(Gesture Arena)。