这是我参与更文挑战的第14天,活动详情查看: 更文挑战
前言
上篇我们聊了 Stack 及其子项精细化布局 Widget Positioned、Align、Center 的详细使用,这篇我们来聊聊一个非常实用但你可能不知道的 Widget IndexedStack,我们不光要聊他的使用,还要聊聊怎么优化它,这是实践的经验希望可以分享给你。
看效果
先看看下面的效果一般是 App 的简单主页骨架,你知道他怎么实现的吗?
IndexedStack(索引栈布局)
上一篇我们聊 Stack 时,子项的显示层级会根据子项在 children 列表中的顺序排列,位置越靠后,显示越靠前。
而这篇我们要聊的 IndexedStack 则不会根据顺序来决定显示顺序,而是只会显示指定索引的子项 Widget。
小实验
为了验证上面的说法,我们来做一个小实验,如下代码你猜会显示什么颜色?
// 索引栈布局
IndexedStack(
// 居中对齐,可以看上篇各种对齐方式详解
alignment: Alignment.center,
// 这里设置索引是 0
index: 0,
children: [
// 黄色 300
Container(
width: 300,
height: 300,
color: Colors.orange,
),
// 红色 200
Container(
width: 200,
height: 200,
color: Colors.red,
),
// 蓝色 100
Container(
width: 100,
height: 100,
color: Colors.blue,
),
],
)
复制代码
| index:0 | index:1 | index:2 |
|---|---|---|
![]() |
![]() |
![]() |
上面我们调整不同的 index 只显示出来了一个子项 Widget,如果我们把 IndexedStack 换成 Stack 则会显示成如下效果。
IndexedStack 源码
- alignment 对齐方式
- sizing 填充方式
- index 显示子项索引
- children 子项集合
本篇主要聊 index 和 children ,其他的 alignment、sizing 可以稍后转看专栏中 Stack 详解篇,我们开始看看源码吧!
我们可以看到 IndexedStack 其实就是 Stack 的子类,对齐方式和填充方式都是直接传递到父类的,所以我们这篇只关心 index 即可。
效果实现
主体内容
我们先开始主体部分的布局,这里使用 IndexedStack ,添加一个 currentIndex 来记录当前子项索引,当我们改变 currentIndex 值后刷新即可实现切换页面效果啦。
/// IndexedStack 页面
class IndexedStackPage extends StatefulWidget {
IndexedStackPage({Key? key}) : super(key: key);
@override
_IndexedStackPageState createState() => _IndexedStackPageState();
}
class _IndexedStackPageState extends State<IndexedStackPage> {
// 当前子项索引
int currentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('IndexedStack - ZeroFlutter'),
),
body: IndexedStack(
alignment: Alignment.center,
// 设置当前索引
index: currentIndex,
children: [
PageDetails(title: '首页'),
PageDetails(title: '消息'),
PageDetails(title: '我的'),
],
),
bottomNavigationBar: [下面聊到],
);
}
}
复制代码
子项内容
这里我们简单使用一个 StatelessWidget 然后再脚手架中添加一个居中的 Text 用来显示一个页面 title。
这里的
/// 页面详情
class PageDetails extends StatelessWidget {
const PageDetails({Key? key, required this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
// 这里的打印可以记录一下,后面会用到
print('PageDetails build title:$title');
return Scaffold(
body: Center(
child: Text(title),
),
);
}
}
复制代码
底部导航
我们使用 BottomNavigationBar 做个简单的底部切换的导航,之后会详细聊聊这个 Widget,记得关注我得专栏《Flutter Widgets》不光是简单介绍Widget,各种实用技法、源码解析、Widget 关系链梳理都会有。
Scaffold(
appBar:[...],
body: IndexedStack(),
// 底部导航栏
bottomNavigationBar: BottomNavigationBar(
// 当前索引和 IndexedStack 使用同一个变量
currentIndex: currentIndex,
// 导航子项集
items: [
// 导航子项
BottomNavigationBarItem(
// 图标
icon: Icon(Icons.home),
// 文字内容
label: '首页',
),
BottomNavigationBarItem(
icon: Icon(Icons.message_rounded),
label: '消息',
),
BottomNavigationBarItem(
icon: Icon(Icons.people),
label: '我的',
),
],
onTap: (value) {
// 点击事件,用于改变当前索引,然后刷新
currentIndex = value;
setState(() {});
},
),
)
复制代码
效果展示
这样我们就实现了展示的效果
精进优化
还记得我们上面在子项详情页打印的内容吗?再看一遍他
/// 页面详情
class PageDetails extends StatelessWidget {
const PageDetails({Key? key, required this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
// 这里的打印可以记录一下,后面会用到
print('PageDetails build title:$title');
return Scaffold(
body: Center(
child: Text(title),
),
);
}
}
复制代码
现在当我们切换导航的时候,看看控制台的打印是什么?
首次进来打印了 1 次,之后切换了 2 次又分别打印了 2 次。我们这里做了一个简单的主页,实际项目中这4个页面承担了很多,也是用户接触最多的页面,要是切换就 build 一次这谁顶得住,我们必须优化。
如何优化?
分析原因
这里我们为了简单 PageDetails 是继承自 StatelessWidget 的,那么 StatelessWidget 发生 build 无外乎以下 3 种情况(源码注释中有写到)
- 1、第一次插入到树?中
- 2、Widget 父类改变了配置
- 3、依赖
InheritedWidget改变了他
那么到底是谁改变了他呢?我们来一起分析一下。
- 第 1 项没有什么疑问
- 第 3 项我们没有依赖于
InheritedWidget - 那就只有第 2 项了
还记得记录当前索引的变量
currentIndex和BottomNavigationBar的onTap方法吗?
BottomNavigationBar(
// 当前索引和 IndexedStack 使用同一个变量
currentIndex: currentIndex,
onTap: (value) {
// 点击事件,用于改变当前索引,然后刷新
currentIndex = value;
setState(() {});
},
)
复制代码
在回到现象,当我们切换的时候会重新build,这里我们只有这里改变了 currentIndex 的值并且调用了 setState 刷新,然后父类(IndexedStackPage)重新 build 了,所以子项的详情页面也会跟着 build。
调整优化
我们现在分析出了原因,那么接下来怎么去优化代码呢?我这里提供 2 种方式
- 添加
const关键字修饰PageDetails - 将子项集初始化放到
initState当中
IndexedStack(
alignment: Alignment.center,
index: currentIndex,
children: [
// 这里添加 const 关键字修饰
const PageDetails(title: '首页'),
const PageDetails(title: '消息'),
const PageDetails(title: '我的'),
],
)
复制代码
// 当前子项索引
int currentIndex = 0;
// 子项集
// 注意空安全的 late 字段修饰哦,因为我们需要延迟初始化
late List<Widget> children;
@override
void initState() {
super.initState();
// 初始化子项集合
children = [
PageDetails(title: '首页'),
PageDetails(title: '消息'),
PageDetails(title: '我的'),
];
}
IndexedStack(
alignment: Alignment.center,
index: currentIndex,
// 设置子项集
children: children,
)
复制代码
优化效果

这里看到我们切换到了「我的」页面但是没有发生重复 build 的现象。
到此这篇就完了,你有收获吗?如果有记得点赞哦
源码仓库
基于 Flutter ? 最新版本
参考链接
关注专栏
- 此文章已收录到下面? 的专栏,可以直接关注
- 更多文章继续阅读|系列文章持续更新
? 欢迎点赞➕收藏➕关注,有任何问题随时在下面?评论,我会第一时间回复哦






















![[02/27][官改] Simplicity@MIX2 ROM更新-一一网](https://www.proyy.com/wp-content/uploads/2020/02/3168457341.jpg)


![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)