在 Flutter 开发中,当我们需要控制某些 widget 的可见度时,常常会用到 Offstage 和 Visibility。甚至有一句流传得很广的话:"只要 widget 不可见,它就不会消耗性能。"
但事实真的这么简单吗?本文带你从原理、性能行为到实际适用场景做一个系统性的分析。
1. Offstage 与 Visibility 到底做了什么?

Offstage
Offstage 的作用是:将 widget 隐藏起来,但仍然保留在 widget 树中,并且不进行绘制,而且它不占用布局空间。
具体来说:
- 子 widget 仍然参与 widget 树和 render 树的构建;
- 在绘制阶段不会 paint 这个 widget;
- 但一些内部机制(如动画、状态更新等)依然在运行。
换句话说:Offstage 并不是把 widget 移除,而是让它"不在视野内"
不再 layout / paint
Visibility
Visibility 更像一个更高级、更灵活的控制器。它可以根据各种配置控制 widget 是否显示:
- 你可以选择隐藏但 仍保留原本的空间;
- 也可以选择隐藏并 保留状态但不占空间;
- 或者不保留状态、直接让 widget 不存在。
Visibility 的内部机制其实是:
根据属性不同组合 Offstage, Opacity, IgnorePointer, SizedBox 替换等多个 widget。
这意味着 Visibility 的行为其实比我们想象得要复杂一些。
2. "不可见就不消耗性能"------这句话常常被误解
现在来回答核心问题:
widget 不可见了,就真的不再消耗性能了吗?
答案是:不一定。
Offstage 的性能行为
虽然 Offstage 会跳过布局和绘制,但:
它仍然会 构建 widget 树 和 维护状态。
还在执行状态变更、动画、计时器等逻辑。
换而言之:如果你用 Offstage 隐藏的 widget 里面仍有动画、Timer、StreamBuilder 等逻辑,它们仍然会消耗 CPU 甚至电量。
这可能和你预想的"不消耗性能"完全不一样。
Visibility 的情况更复杂
Visibility 是一个封装组件,不同设置会导致不同结果:
如果你只是让 visible: false,并不维护状态,widget 会被替换成一个 SizedBox。
如果你用了 maintainState: true(特定场景下手动设置),这个 widget 仍旧会构建、layout、甚至可能 repaint(取决于 Flutter 管线和状态)。
所以:不可见不等于"停止构建或停止响应逻辑"。这句话成立的条件只有在 widget 真正从树中
被移除
3. 性能常见误区 & 如何正确判断
误区 1:Visibility(visible: false)总是性能最优
这是大家最容易犯的错误。
虽然让 widget 不显示了,但:
你可能还在强制保留尺寸空间
你可能还保留状态/动画
你可能让整个 widget 仍然 build
这些都会带来性能开销。
实际上:
在性能敏感场景下,最轻量的方案可能是直接用条件判断把 widget 从树上移除:
if (condition) widget else SizedBox()
这种方式不会产生额外绘制和渲染逻辑。
误区 2:Offstage 就是最轻量的不消耗性能
如前面说的,Offstage 只跳过绘制,但不会停止构建和状态逻辑。
如果内部有复杂子树、状态逻辑仍执行,它依然会消耗 CPU。
4. 在性能优化中什么时候使用 Offstage / Visibility
推荐使用 Offstage 的场景
你需要隐藏 widget 且 不占空间
widget 里面没有需要停止的逻辑(例如动画、计时器等)Offstage 本身不会停止动画的 Ticker。如果你隐藏了一个带 CircularProgressIndicator 的组件,虽然你看不到它,但 CPU 依然在每一帧计算旋转角度。
less
import 'package:flutter/material.dart';
class PerformanceOptimizationDemo extends StatefulWidget {
@override
_PerformanceOptimizationDemoState createState() => _PerformanceOptimizationDemoState();
}
class _PerformanceOptimizationDemoState extends State<PerformanceOptimizationDemo> {
bool _isOffstage = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("TickerMode 性能优化示例")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 使用 TickerMode 包裹 Offstage
TickerMode(
enabled: !_isOffstage, // 当 offstage 为 true 时,禁用 Ticker
child: Offstage(
offstage: _isOffstage,
child: Column(
children: [
CircularProgressIndicator(),
SizedBox(height: 10),
Text("动画正在运行..."),
],
),
),
),
SizedBox(height: 50),
ElevatedButton(
onPressed: () {
setState(() {
_isOffstage = !_isOffstage;
});
},
child: Text(_isOffstage ? "显示并恢复动画" : "隐藏并冻结动画"),
),
],
),
),
);
}
}
想保持 widget 状态但不显示它(在搭配 TickerMode 时更有意义)
比如:切换 tab 时暂时隐藏某个 tab 的内容。
注意:如果隐藏的 widget 有动画或逻辑依然在执行,可以考虑配合 TickerMode(enabled: false) 一起抑制执行。
推荐使用 Visibility 的场景
控制显隐同时希望:
- 保持位置空间
- 保持状态
- 可控交互行为
想实现更高级的显隐策略,例如:
- 只隐藏内容但保持布局占位
- 隐藏时维持某些状态继续有效
这些在 UI 设计层面上确实有使用价值。
不建议仅靠它们"提升性能"的场景
如果你只希望 "减少重建与计算",
简单的显隐本身不一定能达到目的。
此时更好的方式可能是:
- 懒加载条件渲染
- 使用 provider / Riverpod / GetX 精细控制 rebuild
- 结合 keys / const / StatelessWidget 重用机制
如何衡量显隐策略的性能影响
Flutter DevTools & Performance View
- 使用 Performance View 看帧时间、CPU 占用
- 使用 Repaint Rainbow / Rendering Info 看是否被绘制
这些工具能帮你验证隐藏策略是否真的减少了开销。
仅仅widget 不可见了,渲染管线可能依然在执行。所以需要可视化数据做判断。
总结:不可见 ≠ 不消耗性能
| 机制 | 是否 layout | 是否 paint | 是否构建 | 状态 & 动画运行 |
|---|---|---|---|---|
| Offstage (offstage: true) | ❌ | ❌ | ✅ | ✅ (仍运行) |
| Visibility (visible: false, no maintain) | ❌ | ❌ | ❌ | ❌ |
| Visibility (visible: false, maintainState:true) | ❌ | ❌ | ✅ | 可能继续运行 |
| 条件移除 (if/else) | ❌ | ❌ | ❌ | ❌ |