前言:
这是我参与8月更文挑战的第 11 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战,我准备在本月挑选 31 个以前没有介绍过的组件,进行全面分析和属性介绍。这些文章将来会作为 Flutter 组件集录 的重要素材。希望可以坚持下去,你的支持将是我最大的动力~
| 本系列 | 组件文章 | 列表 |
|---|---|---|
| 1.NotificationListener | 2.Dismissible | 3.Switch |
| 4.Scrollbar | 5.ClipPath | 6.CupertinoActivityIndicator |
| 7.Opacity | 8.FadeTransition | 9. AnimatedOpacity |
| 10. FadeInImage | 11. Offstage[本文] |
一、认识 Offstage 组件
大家可能知道 Offstage 组件可以让 child 组件显示/隐藏,但很少用它。毕竟想让一个组件显示/隐藏,我们有其他的手段。比如通过 if 判断,那 Offstage 组件的价值何在,为什么要有这个组件,它有哪些特性?带着这些问题,我们今天就来详细分析一下 Offstage 组件。
1. Offstage 基本信息
下面是 Offstage 组件类的定义和 构造方法,可以看出它继承自 SingleChildRenderObjectWidget。实例化时可以传入布尔型的 offstage 和 child 组件。

2. Offstage 的使用
Offstage 的使用非常简单,只需给定offstage 的值,就能对 child 组件进行显示或隐藏。其中 offstage:true 表示不在舞台上,即隐藏。源码注释中有个实例,我们就以此来认识 Offstage 的使用。

通过点击按钮切换 _offstage 的状态,来显示或隐藏 buildChild 构建的图标组件。可以看出一点:通过 Offstage 组件隐藏的子组件,不会在屏幕上占据位置。
class OffstageDemo extends StatefulWidget {
@override
_OffstageDemoState createState() => _OffstageDemoState();
}
class _OffstageDemoState extends State<OffstageDemo> {
bool _offstage = true;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
child: const Text('切换显隐'),
onPressed: () {
setState(() {
_offstage = !_offstage;
});
},
),
Offstage(
offstage: _offstage,
child: buildChild(),
),
Text('图标是否隐藏: $_offstage'),
],
);
}
Widget buildChild() => const Padding(
padding: EdgeInsets.all(10),
child: Icon(
Icons.camera_outlined,
color: Colors.green,
size: 50,
),
);
}
复制代码
3.Offstage 的特点
上面一个案例就能说明 Offstage 的使用和作用,很多人也就到这里浅尝辄止。但当我们查看渲染树时,可以发现被隐藏组件对应的渲染对象依旧在树上。也就是说,它虽然不可见,但它还在。

这样我们可以通过 GlobalKey 去获取渲染对象,拿到被隐藏的组件大小。注意:这个操作并不是在说组件尺寸要通过 Offstage 来获取,而是说 Offstage 可以获取隐藏组件大小。任何组件都可以通过 GlobalKey 来拿到渲染对象获取尺寸。这里只是在说明被 Offstage 隐藏的组件,对应的渲染对象依旧在树中。

final GlobalKey _key = GlobalKey();
Size _getSize() {
final RenderBox box = _key.currentContext!.findRenderObject()! as RenderBox;
return box.size;
}
Widget buildChild() => Padding(
key: _key, //<---
padding: const EdgeInsets.all(10),
child: const Icon(
Icons.camera_outlined,
color: Colors.green,
size: 50,
),
);
复制代码
由于这种特性,对于隐藏具有动画效果的组件要格外注意。拿 CupertinoActivityIndicator 举例,它是通过 CustomPaint 组件进行绘制的,其中会维护一个不停运动的动画控制器,用于触发画板的重绘。动画控制器会在 RenderCustomPaint 被监听,触发 markNeedsPaint 。
通过调试可以发现,即使 CupertinoActivityIndicator 被隐藏,但动画器仍会不断运行,RenderCustomPaint 监听动画器改变,也会不断触发 markNeedsPaint,这显然是不友好的。所以我们需要在隐藏 CupertinoActivityIndicator 的同时,关掉动画。那么问题来了,CupertinoActivityIndicator 组件的动画器是维护在组件状态内部的,我们如何控制,这里先按下不表,在后面的 TickerMode 组件一文中进行探讨。

Widget buildChild() => Padding(
key: _key,
padding: const EdgeInsets.all(10),
child:CupertinoActivityIndicator(
radius: 20,
),
);
复制代码
二、 Offstage 的源码实现
1. Offstage 源码分析
它继承自 SingleChildRenderObjectWidget 就说明,该组件需要维护一个 RenderObject 对象的创建及更新。

在 createRenderObject 方法中,创建 RenderOffstage ,offstage 作为构造入参。在 updateRenderObject 中,对 RenderOffstage 对象进行更新。也就是说,组件的显示/隐藏是在 RenderAnimatedOpacity 中进行的。
2. RenderOffstage 源码
RenderOffstage 作为一个 RenderObject ,负责布局与绘制。可以看到在源码的处理中,计算宽高时,当offstage 为 true ,会返回 0 ,这也是为什么在界面上不会显示的原因。

在 performLayout 中,如果 offstage 为 true ,子渲染对象会执行 layout 。说明即使是隐藏,子节点也会进行布局。

3. 点击和绘制
hitTest 用于点击处理,可以看出,如果 offstage 为 true 是不会响应点击的。paint 用于绘制,如果 offstage 为 true 直接返回,不会进行绘制。也就是说 offstage 为 true ,即使 CupertinoActivityIndicator 不停触发 markNeedsPaint ,也不会执行子渲染对象的绘制操作。

Offstage 的使用方式到这里就介绍完毕,那本文到这里就结束了,谢谢观看,明天见~



















![[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)