天天看點

The Key of Widget in FlutterThe Key of Widget in Flutter

The Key of Widget in Flutter

當我們剛開始使用Flutter,我們在繼承StatelessWidget和StatefulWidget時,不會關注Key。整個界面的渲染通常也不會有什麼問題。但當我們想要複用Widget,想要優化我們的渲染性能時,Key就成為了一個繞不過的話題。

一個問題

首先,我們需要明白Flutter的界面渲染原理。深入了解Flutter界面開發這一篇部落格講得非常清楚。當我們需要更新界面時,我們會通過setState的方式,讓StatefulWidget周遊Widget樹進行重建。

有時候,我們會發現,即使我們調用了setState,Widget也沒有進行重建。

它的原因

原來,當我們用一個StatefulWidget渲染界面,其狀态可能被我們儲存在其

State

中。當我們修改Widget樹中ChildWidget的位置,如果修改完成後,整個樹的每一個Widget沒有發生改變,Flutter也不會更新整個樹。

上面一段話,非常繞且難懂。但我們很容易想象,Element樹決定了頁面最終展示到螢幕上的樣子,Flutter會從上到下逐一對比Widget樹和Element樹中的每個節點,如果左右節點的類型一緻,那麼就認為該element仍然是有效的,可用複用。

一個粟子

我們用這樣一個

StatefulWidget

,将其color儲存在State中。

class StatefulColorfulTile extends StatefulWidget {
  StatefulColorfulTile({Key key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => StatefulColorfulTileState();
}

class StatefulColorfulTileState extends State<StatefulColorfulTile> {
  // 将 Color 儲存在 StatefulColorfulTile 的 State StatefulColorfulTileState 中.
  Color color;

  @override
  void initState() {
    super.initState();
    color = UniqueColorGenaretor.getColor();
  }

  @override
  Widget build(BuildContext context) => buildColorfulTile(color);
}           

複制

class PositionedTiles extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => PositionedTilesState();
}

class PositionedTilesState extends State<PositionedTiles> {
  List<Widget> tiles;

  @override
  void initState() {
    super.initState();
    tiles = [
      StatefulColorfulTile(),
      StatefulColorfulTile(),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: SafeArea(
            child: Center(
                child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: tiles))),
        floatingActionButton: FloatingActionButton(
            child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles));
  }
  void swapTiles() {
    setState(() {
      tiles.insert(1, tiles.removeAt(0));
    });
  }
}           

複制

當我們調用

tiles.insert(1, tiles.removeAt(0));

時,界面是不會更新的。因為對于Flutter來說,我們交換了兩個一模一樣的Widget,交換完成後,Widget樹并沒有變化。如果我們把ChildWidget換成

StatelessWidget

,且color資訊儲存在Widget中,界面就會更新,因為Flutter會認為這是兩個完全不同的Widget。

那麼,如果我們一定要用

StatefulWidget

來做ChildWidget時,我們應該怎麼做呢?

很簡單:

void initState() {
  super.initState();
  tiles = [
    // 使用 UniqueKey
    StatefulColorfulTile(key: UniqueKey()),
    StatefulColorfulTile(key: UniqueKey()),
  ];
}           

複制

我們給每一個

StatefulWidget

一個唯一的key。這樣,當我們交換Widget時,Flutter就不會認為這兩個Widget是一模一樣的。

用在哪裡

簡而言之,當我們在一個容器下,放了多個相同的Widget對象時,我們就應該思考,這些Widget它們是否應該有個id,還是說它們本質上沒有差別。

根據業務不同,它們的id,可能是字元(ValueKey),可能是一個複雜的資料結構(ObjectKey)。或者,我們隻是想讓它們彼此唯一(UniqueKey)。

此外,我們知道Element是和State綁定的。是以,當你修改了相同類型的Widget節點位置,又想要保留它的狀态時,你需要使用Key。

GlobalKey

官方文檔

作用

  • 允許widget在應用程式中的任何位置更改其parent而不丢失其狀态。應用場景:在兩個不同的螢幕上顯示相同的widget,并保持狀态相同。
  • 可以在任何地方通過

    key.currentState

    擷取其狀态,甚至可以調用

    changeState()

    改變它的狀态。是以,我們可以認為,用上GlobalKey的Widget,是需要伴随App整個生命周期的。像一個靜态的Widget一樣。

結語

對于key的使用,需要根據我們業務的具體情況,根據Flutter的渲染機制,靈活掌握。其核心就是Element與Widget的綁定關系,是類型綁定,還是key+類型的綁定。