前言:
这是我参与8月更文挑战的第 11 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战
,我准备在本月挑选 31
个以前没有介绍过的组件,进行全面分析和属性介绍。这些文章将来会作为 Flutter 组件集录
的重要素材。希望可以坚持下去,你的支持将是我最大的动力~
一、认识 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
的使用方式到这里就介绍完毕,那本文到这里就结束了,谢谢观看,明天见~