跨多个屏幕使用的 Flutter Stateful Widget 正在重建
Posted
技术标签:
【中文标题】跨多个屏幕使用的 Flutter Stateful Widget 正在重建【英文标题】:Flutter Stateful Widget used across Multiple Screens getting Rebuilt 【发布时间】:2020-07-02 06:09:32 【问题描述】:我创建了下面的 Multiselect Chip 小部件,它使用 Provider 并监听列表的变化
小部件创建一个选择筹码列表,允许选择多项选择筹码
class MultiSelectChip extends StatefulWidget
final Function(List<String>) onSelectionChanged;
MultiSelectChip(this.onSelectionChanged);
@override
_MultiSelectChipState createState() => _MultiSelectChipState();
class _MultiSelectChipState extends State<MultiSelectChip>
List<String> selected = List();
List<Clinic> clinicList = List();
@override
void didChangeDependencies()
final list = Provider.of<ClinicProvider>(context).clinics;
final clinic = Clinic(
id: null,
name: "All Clinics",
city: null,
suburb: null,
postcode: null,
prate: null,
udarate: null,
goal: null,
uid: null);
clinicList.add(clinic);
selected.add(clinicList[0].name);
list.forEach((clinic) => clinicList.add(clinic));
super.didChangeDependencies();
_buildList()
List<Widget> choices = List();
clinicList.forEach((item)
choices.add(Padding(
padding: const EdgeInsets.only(left: 5.0, right: 5.0),
child: ChoiceChip(
key: Key("$item.name"),
shape: selected.contains(item.name)
? RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(0),
),
)
: RoundedRectangleBorder(
side: BorderSide(
color: Color.fromRGBO(46, 54, 143, 1), width: 1.0),
borderRadius: BorderRadius.circular(0.0),
),
label: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(item.name),
),
onSelected: (value)
setState(()
selected.contains(item.name)
? selected.remove(item.name)
: selected.add(item.name);
widget.onSelectionChanged(selected);
);
,
selected: selected.contains(item.name),
selectedColor: Color.fromRGBO(46, 54, 143, 1),
labelStyle:
selected.contains(item.name) ? kChipActive : kChipInActive,
backgroundColor: Colors.transparent,
),
));
);
return choices;
@override
Widget build(BuildContext context)
return Padding(
padding: const EdgeInsets.only(left: 8.0, top: 5.0, bottom: 5.0),
child: SizedBox(
height: 50,
width: double.infinity,
child: ListView(
scrollDirection: Axis.horizontal,
children: _buildList(),
),
),
);
当我点击此日志屏幕并转到 NewLog 屏幕并弹回日志屏幕时
class LogScreen extends StatefulWidget
static const String id = 'logscreen';
@override
_LogScreenState createState() => _LogScreenState();
class _LogScreenState extends State<LogScreen>
MonthSelector selectedMonth;
List<String> selectedItems = List();
static DateTime now = DateTime.now();
static DateTime end = DateTime(now.year, now.month + 1, 0);
static DateTime start = DateTime(now.year, now.month, 1);
MonthSelector currentMonth = MonthSelector(
monthName: DateFormat("MMMM").format(now),
monthStart: start,
monthEnd: end);
void refreshData(MonthSelector selector) async
await Provider.of<LogProvider>(context, listen: false)
.getLogs(selector.monthStart, selector.monthEnd);
await Provider.of<LogProvider>(context, listen: false)
.loadTreatments(selector.monthStart, selector.monthEnd);
@override
Widget build(BuildContext context)
final List<LogSummary> list = Provider.of<LogProvider>(context).summary;
final List<FlSpot> chartData = Provider.of<LogProvider>(context).spots;
return Container(
color: Color.fromRGBO(246, 246, 246, 1),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 15,
),
RawMaterialButton(
onPressed: ()
Navigator.pushNamed(context, NewLogScreen.id);
,
constraints: BoxConstraints.tight(Size(60, 60)),
child: Icon(
Icons.add,
color: Color.fromRGBO(255, 255, 255, 1),
size: 30,
),
shape: CircleBorder(),
fillColor: Color.fromRGBO(46, 54, 143, 1),
padding: EdgeInsets.all(15.0),
elevation: 1,
),
SizedBox(
height: 10,
),
Text(
'Add log',
style: kAddLogLabel,
)
],
),
),
]),
list.isEmpty || chartData.isEmpty
? Expanded(
child: Center(
child: Text("No Log Data.."),
),
)
: Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
height: 150,
alignment: Alignment.center,
child: LineChartWidget(
list: chartData,
isDollar: true,
),
),
SizedBox(
height: 10,
),
MultiSelectChip(
onSelectionChanged: (selectedList) async
setState(()
selectedItems = selectedList;
);
await Provider.of<LogProvider>(context, listen: false)
.filterLogList(selectedItems);
,
),
MonthSelect(Color.fromRGBO(246, 246, 246, 1),
onMonthSelectionChanged: (selected)
setState(()
selectedMonth = selected;
);
selectedMonth == null
? refreshData(currentMonth)
: refreshData(selectedMonth);
),
Padding(
padding:
const EdgeInsets.only(top: 10, left: 0, right: 0),
child: Container(
width: double.infinity,
height: 1.0,
color: kDividerColor,
),
),
我看到的是 Multiselect Chip 有相同的项目列表被重绘/添加到列表视图 3 次,每次我进入 NewLog 屏幕时,列表都会不断增长
我目前在 4 个不同的屏幕上使用相同的小部件,但由于某种原因,当我导航到另一个屏幕时,列表会重置并显示原始项目,而重复的项目消失
在离开屏幕时,我可以做些什么来防止重绘
谢谢
【问题讨论】:
【参考方案1】:您是否尝试过在Provider.of()
中指定listen: false
在didChangeDependencies()
中使用?或许能解决问题。
但是,仍然存在风险。我怀疑在那里初始化某些东西是安全的,因为当/每当 State 对象的依赖项发生变化时调用 didChangeDependencies()
,正如其 document 中所写的那样。在initState()
中执行此操作会更安全,或者仅在外部执行一次并将其结果传递给 MultiSelectChip。
【讨论】:
我有另一个管理 clinincList 提供程序类的屏幕,所以当我将另一个项目添加到列表中时,小部件会更新并将额外的项目添加到列表中,所以我需要保持监听为真,我尝试将列表传递到小部件中 传入列表并在 initState 上加载已解决此问题谢谢!以上是关于跨多个屏幕使用的 Flutter Stateful Widget 正在重建的主要内容,如果未能解决你的问题,请参考以下文章
Flutter 中 stateless 和 stateful widget 的区别[Flutter专题60]
Flutter控件篇(Stateful widget)——ListView
从 Flutter 中的 Stateful Widget 返回数据
Flutter Stateful Widget 状态未初始化