【Flutter 组件集录】Offstage| 8月更文挑战

2,129 阅读3分钟
前言:

这是我参与8月更文挑战的第 11 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战,我准备在本月挑选 31 个以前没有介绍过的组件,进行全面分析和属性介绍。这些文章将来会作为 Flutter 组件集录 的重要素材。希望可以坚持下去,你的支持将是我最大的动力~

本系列组件文章列表
1.NotificationListener2.Dismissible3.Switch
4.Scrollbar5.ClipPath6.CupertinoActivityIndicator
7.Opacity8.FadeTransition9. AnimatedOpacity
10. FadeInImage11. Offstage12. TickerMode
13. Visibility14. Padding15. AnimatedContainer
16.CircleAvatar17.PhysicalShape18.Divider
19.Flexible、Expanded 和 Spacer 20.Card

一、认识 Offstage 组件

大家可能知道 Offstage 组件可以让 child 组件显示/隐藏,但很少用它。毕竟想让一个组件显示/隐藏,我们有其他的手段。比如通过 if 判断,那 Offstage 组件的价值何在,为什么要有这个组件,它有哪些特性?带着这些问题,我们今天就来详细分析一下 Offstage 组件。


1. Offstage 基本信息

下面是 Offstage 组件类的定义构造方法,可以看出它继承自 SingleChildRenderObjectWidget。实例化时可以传入布尔型的 offstagechild 组件。


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 方法中,创建 RenderOffstageoffstage 作为构造入参。在 updateRenderObject 中,对 RenderOffstage 对象进行更新。也就是说,组件的显示/隐藏是在 RenderAnimatedOpacity 中进行的。


2. RenderOffstage 源码

RenderOffstage 作为一个 RenderObject ,负责布局与绘制。可以看到在源码的处理中,计算宽高时,当offstagetrue ,会返回 0 ,这也是为什么在界面上不会显示的原因。

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


3. 点击和绘制

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


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