我正在构建一种类似火种的刷卡机制,其中在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小部件。
有没有办法防止这种不必要的重建?
答案 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
这样,树不会有条件地改变,但是行为仍然是有条件的
价格昂贵,但灵活性更高,因此不理想。
您可以使用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
这样,从AnotherTree
到MyStatefulWidget
过渡时,小部件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;
}