为什么我的StatefulWidget丢失状态?

时间:2019-12-14 18:56:44

标签: flutter

我正在构建一种类似火种的刷卡机制,其中在Stack小部件中有一系列小部件。第一张卡包裹在Dismissible小部件中,该小部件在关闭时会关闭顶部的卡,并在底部添加新的卡。

我的问题是,当第一张卡片被撤消而第二张卡片成为第一张卡片时,该卡片暂时被处置,initState被再次召唤

但是,正如Linus Torvalds曾经说过的,

  

“对话很便宜,请告诉我代码”

所以我举了一个例子:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Container(color: Colors.white, child: CardStack()),
    );
  }
}

class CardStack extends StatefulWidget {
  @override
  _CardStackState createState() => _CardStackState();
}

class _CardStackState extends State<CardStack> {
  final List<String> _postIds = ["1", "2", "3"];
  int _nextId = 4;

  _updatePosts(DismissDirection dir) {
    setState(() {
      _postIds.removeAt(0);
      _postIds.add((_nextId++).toString());
    });
  }

  List<Widget> _buildCards() {
    List<Widget> cards = [];
    for (int i = 0; i < _postIds.length; ++i) {
      if (i == 0) {
        cards.insert(
            0,
            Dismissible(
              key: UniqueKey(),
              onDismissed: _updatePosts,
              child: Card(key: ValueKey(_postIds[i]), postId: _postIds[i]),
            ));
      } else {
        cards.insert(0, Card(key: ValueKey(_postIds[i]), postId: _postIds[i]));
      }
    }
    return cards;
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      children: _buildCards(),
    );
  }
}

class Card extends StatefulWidget {
  Card({Key key, this.postId}) : super(key: key);

  final String postId;

  @override
  _CardState createState() => _CardState();
}

class _CardState extends State<Card> {
  @override
  void initState() {
    super.initState();
    print("Init state card with id ${widget.postId}");
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 200,
      width: 200,
      color: Colors.blueAccent,
      child: Center(
        child: Text(widget.postId),
      ),
    );
  }
}

如果我运行它会记录日志

I/flutter (26740): Init state card with id 3
I/flutter (26740): Init state card with id 2
I/flutter (26740): Init state card with id 1
// swipe
I/flutter (26740): Init state card with id 4
I/flutter (26740): Init state card with id 2 <- unwanted rebuild

我认为重建第一张卡片的原因是,该卡片现在已包裹在Dismissible小部件内,因此flutter不知道如何重用基础的Card小部件。

有没有办法防止这种不必要的重建?

2 个答案:

答案 0 :(得分:2)

问题是,StatefulWidget的位置在小部件树中正在更改。

您的StatefulWidget可能有条件地包装在特定的小部件内,这样您的小部件树会在以下范围内变化:

SomeWidget
  MyStatefulWidget

和:

SomeWidget
  AnotherWidget
    MyStatefulWidget

默认情况下,Flutter无法理解小部件树中的MyStatefulWidget已移动。

相反,它会认为先前的MyStatefulWidget被删除了,而新的dispose与原来的完全无关。

这将导致在先前的State上调用initState,并在新的State上调用ListView


为避免这种情况,您有一些解决方案。

请勿有条件地将小部件包装在另一个小包装中

如果可以的话,这是最好的解决方案: 重构您的布局,使小部件树始终保持相同。

例如,如果您只希望将ListView.builder( itemBuilder: (_, index) { final child = MyStatefulWidget(); if (index == 0) { return Dismissible(key: ValueKey(index), onDismissed: ..., child: child); } } ) 中的第一个窗口小部件删除,则改为:

ListView.builder(
  itemBuilder: (_, index) {
    return Dismissible(
      key: ValueKey(someValue),
      onDismissed: ...,
      confirmDismiss: (_) async => index == 0, // only first item can be dismissed
      child: MyStatefulWidget();
    );
  }
)

这样做:

GlobalKey

这样,树不会有条件地改变,但是行为仍然是有条件的

使用GlobalKey

价格昂贵,但灵活性更高,因此不理想。

您可以使用Widget build(BuildContext context) { if (something) { return SomeTree( child: MyStatefulWidget(key: GlobalObjectKey('my widget')); ); } else { return AnotherTree( child: MyStatefulWidget(key: GlobalObjectKey('my widget')); ); } } 告诉Flutter,特定的小部件可以从一个位置移动到另一个位置。

这样,我们将能够编写:

SomeTree

这样,从AnotherTreeMyStatefulWidget过渡时,小部件NAND(X,X) == NOT(X)将保留其状态。

提醒一下,请尽可能避免。这是相对昂贵的。

答案 1 :(得分:0)

您的flutter非常明智地管理其状态。在重新构建时,如果小部件处于相同状态,则不会重新构建,但是您已经写好了Card(),因此在关闭小部件“ 2”状态后,变化和重建。 您可以一次构建列表,然后再应用条件。如Linus Torvalds所说的;-)

List<Widget> _buildCards() {
    List<Widget> cards = [];
    for (int i = 0; i < _postIds.length; ++i) {
        cards.insert(0, Card(key: ValueKey(_postIds[i]), postId: _postIds[i]));
    }
    cards[0]=Dismissible(
              key: UniqueKey(),
              onDismissed: _updatePosts,
              child: cards[0]
            ));
    return cards;
  }