Flutter:setState((){} 无法重绘 DropdownButton 值
Flutter: setState((){} fails to redraw DropdownButton value
我是 Flutter 新手,正在尝试编写手游代码。到目前为止,我已经能够解决之前在论坛、youtube 教程和示例应用程序上提出的问题。但是我遇到了我无法解决的问题。
我的 UI 中的许多功能应该根据用户行为而改变,但不会更新,我使用这个 DropdownButton 作为我的例子,但这是我在代码其他地方遇到的问题出色地。当用户从 DropdownButton 中进行选择时,它应该更新按钮的值,但只显示提示。我已经能够通过打印语句确定选择已注册。
根据我目前所了解的情况,我怀疑这个问题与我的小部件的有状态性或缺乏状态有关。我发现了一些建议使用 StatefulBuilder 的类似问题,但是当我尝试实现它时,代码有错误或者复制了我的整个 ListTile 组。我还看到了关于创建有状态小部件的建议,但是这些说明太模糊了,我无法遵循和实施而不会出错。我包括下面的代码片段,我犹豫要不要粘贴整个代码,因为此时该应用程序将近 2000 行。如果需要任何进一步的信息或代码,请告诉我。
这是有问题的代码,请参阅下面的上下文代码。
DropdownButton(
value: skillChoice,
items: listDrop,
hint: Text("Choose Skill"),
onChanged: (value) {
setState(() {
skillChoice = value;
});
},
),
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hunters Guild',
theme: ThemeData(
primaryColor: Colors.grey,
),
home: Main(),
);
}
}
final List<Combatant> _combatants = List<Combatant>();
//combatant is a class that holds stats like HP which may change during a battle
//they also have a Fighter and a Monster and one of those will be null and the other useful
//depending if faction = "good" or "evil
//I am aware that this may not be the most elegant implementation but that is a problem for another day.
class MainState extends State<Main> {
final List<Fighter> _squad = List<Fighter>();
//Fighter is a class which stores info like HP, name, skills, etc for game character,
//this List is populated in the page previous to _fight
//where the user picks which characters they are using in a given battle
void _fight(Quest theQuest){
for(Fighter guy in _squad){
_combatants.add(Combatant("good", guy, null));
}
_combatants.add(Combatant("evil", null, theQuest.boss));
//quests are a class which store a boss Monster, later on I will add other things like rewards and prerequisites
final tiles = _combatants.map((Combatant comba){ //this structure is from the build your first app codelab
List<DropdownMenuItem<String>> listDrop = [];
String skillChoice = null;
if (comba.faction == "good"){
for (Skill skill in comba.guy.skills){
listDrop.add((DropdownMenuItem(child: Text(skill.name), value: skill.name)));
}
}
else if (comba.faction == "evil"){
for (Skill skill in comba.boss.skills){
listDrop.add((DropdownMenuItem(child: Text(skill.name), value: skill.name)));
}
}
//^this code populates each ListTile (one for each combatant) with a drop down button of all their combat skills
return ListTile(
leading: comba.port,
title: Text(comba.info),
onTap: () {
if(comba.faction == "good"){
_fighterFightInfo(comba.guy, theQuest.boss); //separate page with more detailed info, not finished
}
else{
_monsterFightInfo(theQuest.boss); //same but for monsters
}
},
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
DropdownButton( //HERE IS WHERE THE ERROR LIES
value: skillChoice,
items: listDrop,
hint: Text("Choose Skill"),
onChanged: (value) {
setState(() {
skillChoice = value;
});
},
),
IconButton(icon: Icon(Icons.check), onPressed: (){
//eventually this button will be used to execute the user's skill choice
})
],
),
subtitle: Text(comba.hp.toString()),
);
},
);
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
//I have tried moving most of the above code into here,
//and also implementing StatelessBuilders here and other places in the below code to no effect
final divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
return Scaffold(
appBar: AppBar(
title: Text("Slay "+theQuest.boss.name+" "+theQuest.boss.level.toString()+" "+theQuest.boss.type.name, style: TextStyle(fontSize: 14),),
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: () {
_squadSelect(theQuest);
}),
),
body: ListView(children: divided),
);
},
),
);
}
//there are a lot more methods here which I haven't included
@override
Widget build(BuildContext context) {
//I can show this if you need it but there's a lot of UI building on the main page
//Also might not be the most efficiently implemented
//what you need to know is that there are buttons which call _fight and go to that page
}
class Main extends StatefulWidget {
@override
MainState createState() => MainState();
}
感谢您提供的任何帮助或建议。
有点难以理解这里发生了什么,所以如果可能的话,将问题区域添加到新的小部件中,然后 post 在此处添加。
无论如何,目前看来您的 skillShare 变量是本地变量,因此每次设置状态时它都不会更新。尝试这样的事情:
class MainState extends State<Main> {
String skillShare = ""; //i.e make skillShare a global variable
final List<Fighter> _squad = List<Fighter>();
下面的问题有帮助我找到答案的信息。不同的问题,但解决方案适用。谢谢 user:4576996 斯文。基本上我需要重新格式化,从在 void _fight 中构建到一个新的有状态页面。这对于使用入门教程作为框架来构建其应用程序的其他人可能有用。
我是 Flutter 新手,正在尝试编写手游代码。到目前为止,我已经能够解决之前在论坛、youtube 教程和示例应用程序上提出的问题。但是我遇到了我无法解决的问题。
我的 UI 中的许多功能应该根据用户行为而改变,但不会更新,我使用这个 DropdownButton 作为我的例子,但这是我在代码其他地方遇到的问题出色地。当用户从 DropdownButton 中进行选择时,它应该更新按钮的值,但只显示提示。我已经能够通过打印语句确定选择已注册。
根据我目前所了解的情况,我怀疑这个问题与我的小部件的有状态性或缺乏状态有关。我发现了一些建议使用 StatefulBuilder 的类似问题,但是当我尝试实现它时,代码有错误或者复制了我的整个 ListTile 组。我还看到了关于创建有状态小部件的建议,但是这些说明太模糊了,我无法遵循和实施而不会出错。我包括下面的代码片段,我犹豫要不要粘贴整个代码,因为此时该应用程序将近 2000 行。如果需要任何进一步的信息或代码,请告诉我。 这是有问题的代码,请参阅下面的上下文代码。
DropdownButton(
value: skillChoice,
items: listDrop,
hint: Text("Choose Skill"),
onChanged: (value) {
setState(() {
skillChoice = value;
});
},
),
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hunters Guild',
theme: ThemeData(
primaryColor: Colors.grey,
),
home: Main(),
);
}
}
final List<Combatant> _combatants = List<Combatant>();
//combatant is a class that holds stats like HP which may change during a battle
//they also have a Fighter and a Monster and one of those will be null and the other useful
//depending if faction = "good" or "evil
//I am aware that this may not be the most elegant implementation but that is a problem for another day.
class MainState extends State<Main> {
final List<Fighter> _squad = List<Fighter>();
//Fighter is a class which stores info like HP, name, skills, etc for game character,
//this List is populated in the page previous to _fight
//where the user picks which characters they are using in a given battle
void _fight(Quest theQuest){
for(Fighter guy in _squad){
_combatants.add(Combatant("good", guy, null));
}
_combatants.add(Combatant("evil", null, theQuest.boss));
//quests are a class which store a boss Monster, later on I will add other things like rewards and prerequisites
final tiles = _combatants.map((Combatant comba){ //this structure is from the build your first app codelab
List<DropdownMenuItem<String>> listDrop = [];
String skillChoice = null;
if (comba.faction == "good"){
for (Skill skill in comba.guy.skills){
listDrop.add((DropdownMenuItem(child: Text(skill.name), value: skill.name)));
}
}
else if (comba.faction == "evil"){
for (Skill skill in comba.boss.skills){
listDrop.add((DropdownMenuItem(child: Text(skill.name), value: skill.name)));
}
}
//^this code populates each ListTile (one for each combatant) with a drop down button of all their combat skills
return ListTile(
leading: comba.port,
title: Text(comba.info),
onTap: () {
if(comba.faction == "good"){
_fighterFightInfo(comba.guy, theQuest.boss); //separate page with more detailed info, not finished
}
else{
_monsterFightInfo(theQuest.boss); //same but for monsters
}
},
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
DropdownButton( //HERE IS WHERE THE ERROR LIES
value: skillChoice,
items: listDrop,
hint: Text("Choose Skill"),
onChanged: (value) {
setState(() {
skillChoice = value;
});
},
),
IconButton(icon: Icon(Icons.check), onPressed: (){
//eventually this button will be used to execute the user's skill choice
})
],
),
subtitle: Text(comba.hp.toString()),
);
},
);
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
//I have tried moving most of the above code into here,
//and also implementing StatelessBuilders here and other places in the below code to no effect
final divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
return Scaffold(
appBar: AppBar(
title: Text("Slay "+theQuest.boss.name+" "+theQuest.boss.level.toString()+" "+theQuest.boss.type.name, style: TextStyle(fontSize: 14),),
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: () {
_squadSelect(theQuest);
}),
),
body: ListView(children: divided),
);
},
),
);
}
//there are a lot more methods here which I haven't included
@override
Widget build(BuildContext context) {
//I can show this if you need it but there's a lot of UI building on the main page
//Also might not be the most efficiently implemented
//what you need to know is that there are buttons which call _fight and go to that page
}
class Main extends StatefulWidget {
@override
MainState createState() => MainState();
}
感谢您提供的任何帮助或建议。
有点难以理解这里发生了什么,所以如果可能的话,将问题区域添加到新的小部件中,然后 post 在此处添加。
无论如何,目前看来您的 skillShare 变量是本地变量,因此每次设置状态时它都不会更新。尝试这样的事情:
class MainState extends State<Main> {
String skillShare = ""; //i.e make skillShare a global variable
final List<Fighter> _squad = List<Fighter>();
下面的问题有帮助我找到答案的信息。不同的问题,但解决方案适用。谢谢 user:4576996 斯文。基本上我需要重新格式化,从在 void _fight 中构建到一个新的有状态页面。这对于使用入门教程作为框架来构建其应用程序的其他人可能有用。