54 lines
1.9 KiB
Dart
54 lines
1.9 KiB
Dart
import 'package:flutter/material.dart';
|
||
|
||
/// 懒加载 + 保活的 IndexedStack(底部 tab 内容页标准写法)
|
||
///
|
||
/// 没进过的 tab 用空盒占位,首次切到才真正 build;进过一次就一直留在树里,
|
||
/// 切走只是不 paint,state / 滚动位置 / 播放器全部保留。
|
||
/// 相比 PageView:不需要 AutomaticKeepAlive 保活,也不会预建相邻页。
|
||
///
|
||
/// 非当前 tab 用 TickerMode(enabled: false) 关掉:IndexedStack 只是不 paint,
|
||
/// 离屏页的动画/定时器照样在跑(跑马灯、Shimmer、轮播),看不见还空转。
|
||
class LazyIndexedStack extends StatefulWidget {
|
||
final int index;
|
||
final List<Widget> children;
|
||
|
||
const LazyIndexedStack({super.key, required this.index, required this.children});
|
||
|
||
@override
|
||
State<LazyIndexedStack> createState() => _LazyIndexedStackState();
|
||
}
|
||
|
||
class _LazyIndexedStackState extends State<LazyIndexedStack> {
|
||
//已建过的下标:进过一次就永久保活,不再退回占位
|
||
final _loaded = <int>{};
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_loaded.add(widget.index);
|
||
}
|
||
|
||
@override
|
||
void didUpdateWidget(covariant LazyIndexedStack oldWidget) {
|
||
super.didUpdateWidget(oldWidget);
|
||
_loaded.add(widget.index);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return IndexedStack(
|
||
// 必须 expand:默认的 loose 会让子页拿到非 tight 约束,从而不再是 relayout boundary,
|
||
// 离屏页一脏就把整个 stack 拖着重新布局(PageView 原来给的是 tight,每页各自隔离)。
|
||
// 顺带保证「根节点自身不撑满」的页也能满屏,不依赖各页自觉
|
||
sizing: StackFit.expand,
|
||
index: widget.index,
|
||
children: List.generate(
|
||
widget.children.length,
|
||
(i) => _loaded.contains(i)
|
||
? TickerMode(enabled: i == widget.index, child: widget.children[i])
|
||
: const SizedBox.shrink(),
|
||
),
|
||
);
|
||
}
|
||
}
|