【Flutter 元件集錄】Offstage| 8月更文挑戰

張風捷特烈發表於2021-08-11
前言:

這是我參與8月更文挑戰的第 11 天,活動詳情檢視:8月更文挑戰。為應掘金的八月更文挑戰,我準備在本月挑選 31 個以前沒有介紹過的元件,進行全面分析和屬性介紹。這些文章將來會作為 Flutter 元件集錄 的重要素材。希望可以堅持下去,你的支援將是我最大的動力~

本系列元件文章列表
1.NotificationListener2.Dismissible3.Switch
4.Scrollbar5.ClipPath6.CupertinoActivityIndicator
7.Opacity8.FadeTransition9. AnimatedOpacity
10. FadeInImage11. Offstage[本文]

一、認識 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 的使用方式到這裡就介紹完畢,那本文到這裡就結束了,謝謝觀看,明天見~

相關文章