上个月一个做传统 Android 开发的朋友转 Flutter,问了我一个特别基础的问题:“我就想点一个按钮跳到另一个页面,再把名字带过去,为什么网上教程一会儿 Navigator.push,一会儿 MaterialPageRoute,一会儿又冒出个 await 回来?”这个问题听着简单,但把 Flutter 按钮、事件、路由跳转传值串在一起,刚好是新手入门的第一道分水岭。这篇内容我不会按官方文档的目录顺序念,而是从“实现一个能点、能跳、能传值的页面”这个真实需求出发,把按钮、事件和路由这三件事揉开讲透,顺便把我带新人时常踩的坑也一并列出来。
1. 先把“按钮”和“事件”放在一起理解
1.1 按钮组件家族:别再盯着 RaisedButton 不放了
很多刚接触 Flutter 的人会先搜到一个叫 RaisedButton 的组件,写起来也简单。但只要你把 Flutter SDK 升到 3.x,编辑器就会划一条删除线,告诉你这个类已经废弃。现在官方推荐的是 Material 3 语义化按钮:
ElevatedButton:带背景色和阴影的强调按钮,适合页面主操作。TextButton:纯文字按钮,适合表格操作行、次要动作。OutlinedButton:有边框但没有背景,适合次强调的中间态操作。IconButton:只放一个图标,常用于 AppBar 工具栏。FloatingActionButton:悬浮按钮,通常一个页面只有一个,展示核心动作。
我见过不少从老教程入手的同学,项目里还留着 RaisedButton,虽然现在运行也不会报错,但在高版本 SDK 下控制台会有 deprecated 警告,更麻烦的是 Material 2 和 Material 3 的主题结构不一样,混用容易让视觉风格出现割裂感。所以新项目直接养成习惯,用新的按钮组件。
按钮的本质并不复杂:它就是一个能接收手指点按的组件,然后把“被点了”这件事通过回调函数告诉我们。回调函数叫 onPressed,它不一定每次都执行,当你不传或传 null 时,按钮会显示为禁用态。这是 Flutter 事件设计里很典型的一个思路:把“外观状态”和“行为回调”绑在一起。
dart复制ElevatedButton(
onPressed: () {
print('按钮被点击了');
},
child: const Text('点我'),
)
1.2 onPressed 到底是怎么触发的:理解回调思维
初学 Flutter 的人容易卡在一个地方:为什么按钮点击后的代码要写在 onPressed 的回调里,而不是写在按钮下面顺序执行?
这里必须转变一个思维:移动端 UI 是事件驱动的。你写 onPressed: () {},只是告诉 Flutter:“当这个按钮被按下并被识别为点击时,请调用这个函数。”Flutter 本身会通过原始触摸事件、命中测试、手势竞技场等一系列机制,最终把一次触摸评定为点击。整个过程是异步的、由系统回调的,不是像脚本那样从上到下一行行执行。
把 onPressed 理解成一个“插槽”,你就明白为什么可以用命名函数,也可以用匿名函数,甚至可以从外部传入一个方法:
dart复制void _handleLogin() {
// 处理登录逻辑
}
ElevatedButton(
onPressed: _handleLogin,
child: const Text('登录'),
)
当 _handleLogin 方法内部逻辑发生变化时,按钮本身不需要跟着改。这种解耦在小项目里看不出多大优势,但页面一多、事件一复杂,好处就非常明显。
1.3 按钮之外:InkWell 和 GestureDetector 是另外两个“事件入口”
不是所有可点击区域都是按钮。比如一个卡片想整体可点击,或者列表项想响应双击,直接套按钮会很奇怪。Flutter 里更通用的是 InkWell 和 GestureDetector。
GestureDetector 是个“万能触摸感应器”,它不关心组件长什么样,只负责监听手势。你可以让一个 Container、一张图片,甚至一段文字响应点击、长按、双击、拖动。但它也有一个副作用:默认没有水波纹反馈,也没有按钮那种视觉按压效果。所以如果只是“点击一下有点击反馈”,优先考虑 InkWell。InkWell 是 Material 设计组件,点击时会在 Material 层绘制一个涟漪效果,但使用它时,父级需要有一个 Material 祖先,否则会报错。
dart复制Card(
child: InkWell(
onTap: () {
print('卡片被点击');
},
child: Padding(
padding: const EdgeInsets.all(16),
child: Text('这是一张卡片'),
),
),
)
很多新手看到 InkWell 报错“No Material widget found”就懵了,其实只要外面套一个 Material,或者 Scaffold 里面自然就有 Material 环境,所以卡片放在页面 body 里一般没问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 路由跳转传值:从“能跳”到“会传”
2.1 为什么页面跳转不直接 new 一个 Widget
Flutter 里所有页面其实都是 Widget,你确实可以直接在 build 里写字面量的 DetailPage(),但它只会覆盖当前页面,而且没有系统返回键、没有转场动画、没有状态保留逻辑。真实 App 里我们需要一个“页面栈”,所以 Flutter 提供了 Navigator。
Navigator 管理一个栈结构:push 把新页面压入栈顶,pop 把当前页面弹出。这种设计很像浏览器历史记录,你点返回,就是执行一次 pop。因此“路由跳转传值”这个需求,在 Flutter 里本质上就是“向新页面组件传参”和“从新页面回传结果”。
2.2 最直观的传值方式:构造函数传参
页面本身是
