文章

Offstage和TickerMode详解

Offstage 和 TickerMode 详解

Offstage和TickerMode详解

Offstage 和 TickerMode 详解

Offstage

什么是 Offstage?

Offstage 是一个布局 widget,它可以控制子 widget 是否在视觉上显示,但子 widget 仍然存在于 widget 树中

核心属性

1
2
3
4
5
Offstage({
  Key? key,
  bool offstage = true,  // 关键属性:true 表示隐藏,false 表示显示
  Widget? child,
})

工作原理

  • **offstage = true**:子 widget 不会被渲染到屏幕上,但仍然存在于 widget 树中,保持其状态
  • **offstage = false**:子 widget 正常显示

实际效果

1
2
3
4
5
6
7
8
9
10
11
// 这个 Text 不会显示,但它的状态被保持
Offstage(
  offstage: true,
  child: Text('我是隐藏的内容'),
)

  // 这个 Text 正常显示
  Offstage(
  offstage: false,
  child: Text('我是可见的内容'),
)

在 Tab 切换中的应用

1
2
3
4
5
6
7
8
9
10
11
12
Stack(
  children: [
    Offstage(
      offstage: _currentIndex != 0,  // 不是当前页就隐藏
      child: HomePage(),  // 但 HomePage 的状态被保持
    ),
    Offstage(
      offstage: _currentIndex != 1,
      child: ProfilePage(),
    ),
  ],
)

TickerMode

什么是 TickerMode?

TickerMode 控制子 widget 中的动画是否运行。它可以启用或禁用子 widget 树中的所有动画。

核心属性

1
2
3
4
5
TickerMode({
  Key? key,
  required bool enabled,  // true: 动画运行,false: 动画暂停
  Widget? child,
})

工作原理

  • **enabled = true**:子 widget 中的动画正常运行
  • **enabled = false**:子 widget 中的动画暂停,但动画状态被保持

实际效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 这个动画会运行
TickerMode(
  enabled: true,
  child: RotationTransition(
    turns: _controller,
    child: Icon(Icons.refresh),
  ),
)

  // 这个动画会暂停
  TickerMode(
  enabled: false,
  child: RotationTransition(
    turns: _controller,
    child: Icon(Icons.refresh),
  ),
)

Offstage + TickerMode 组合使用

为什么需要组合?

  • Offstage 单独使用:隐藏页面但动画仍在后台运行,消耗资源
  • TickerMode 单独使用:暂停动画但页面仍然可见
  • 组合使用:既隐藏页面又暂停动画,完美优化

典型用法

1
2
3
4
5
6
7
Offstage(
  offstage: !isVisible,  // 控制显示/隐藏
  child: TickerMode(
    enabled: isVisible,   // 控制动画运行/暂停
    child: YourPage(),    // 你的页面内容
  ),
)

在 Tab 应用中的完整示例

优化后的 Tab 控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class TabController extends StatefulWidget {
  @override
  _TabControllerState createState() => _TabControllerState();
}

class _TabControllerState extends State<TabController> {
  int _currentIndex = 0;
  final List<bool> _pageInitialized = [false, false, false, false];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          // 页面 0
          _buildPage(0, HomePage()),
          // 页面 1
          _buildPage(1, ProfilePage()),
          // 页面 2
          _buildPage(2, SettingsPage()),
          // 页面 3
          _buildPage(3, MessagesPage()),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            // 标记页面为已初始化
            if (!_pageInitialized[index]) {
              _pageInitialized[index] = true;
            }
            _currentIndex = index;
          });
        },
        items: [...],
      ),
    );
  }

  Widget _buildPage(int index, Widget page) {
    if (!_pageInitialized[index]) {
      return SizedBox.shrink(); // 未初始化的页面不渲染
    }

    return Offstage(
      offstage: _currentIndex != index, // 隐藏非当前页面
      child: TickerMode(
        enabled: _currentIndex == index, // 只允许当前页面运行动画
        child: page,
      ),
    );
  }
}

性能优势

内存优化

  • 未访问的页面:不初始化,不占用内存
  • 访问过的页面:状态被保持,但动画暂停

CPU 优化

  • 隐藏的页面:不参与渲染,节省 CPU
  • 暂停的动画:不消耗动画计算资源

用户体验

  • 快速切换:已访问过的页面立即显示,无需重新加载
  • 状态保持:滚动位置、表单数据等都被保持

注意事项

  1. 内存使用:虽然优化了,但多个页面的状态仍然占用内存
  2. 初始化时机:根据需要决定何时初始化页面(首次访问或预加载)
  3. 复杂页面:对于特别复杂的页面,考虑手动管理资源释放

替代方案比较

方案状态保持内存使用CPU 使用实现复杂度
IndexedStack✅ 很好⚠️ 较高⚠️ 较高简单
Offstage + TickerMode✅ 很好✅ 较低✅ 较低中等
PageView✅ 很好⚠️ 中等⚠️ 中等简单
手动销毁重建❌ 无✅ 最低✅ 最低复杂

Offstage + TickerMode 在状态保持和性能之间提供了最佳平衡。

本文由作者按照 CC BY 4.0 进行授权