Как обновить состояние виджета, встроенного в FutureBuilder
Раньше я использовал ListBuilder
чтобы сгенерировать список из 70 номеров, и это сработало, но потребовалось много времени, чтобы сгенерировать 70 номеров в пользовательские виджеты, а также, когда я нажал номер, просто чтобы изменить состояние цвета фона, потребовалось несколько миллисекунд, прежде чем состояние было изменено.
Теперь я использую FutureBuilder
чтобы иметь возможность загрузить экран, ожидая сгенерированных 70 целых чисел. Но при нажатии на номер шарика цвет фона не обновляется... Это какsetState()
не работает в Future ListBuilder.
Этот вопрос: " Flutter - Как обновить состояние (или значение?) Future/List, используемого для создания ListView (через FutureBuilder) " очень похож, но он не решил мою проблему.
Вот код, который у меня есть в методе сборки
Flexible(
child:FutureBuilder<List<Widget>>(
future: ballNumbers,
builder: (context, snapshot){
if(snapshot.connectionState != ConnectionState.done){
return Center(child: CircularProgressIndicator());
}
if(snapshot.hasError){
return Center(child: Text("An error has occured"));
}
List<Widget> balls = snapshot.data ?? [];
return GridView.count(
crossAxisCount: 9,
children: balls,
);
}
)
Вот как я запускаю состояние функции:
Future<List<Widget>> ballNumbers;
List<int> picks = [];
@override
void initState() {
ballNumbers = getBallNumbers();
});
Future<List<Widget>> getBallNumbers() async {
return List.generate(limitBallNumber,(number){
number = number + 1;
return Padding(
padding:EdgeInsets.all(2.5),
child:Ball(
number : number,
size: ballWidth,
textColor:(picks.contains(number)) ? Colors.black : Colors.white,
ballColor: (picks.contains(number)) ? Style.selectedBallColor : Style.ballColor,
onTap:(){
setState((){
picks.contains(number) ? picks.remove(number) : picks.add(number);
});
}
)
);
});
}
ОБНОВЛЕНО: вот класс Ball
виджет
class Ball extends StatelessWidget {
final Color ballColor;
final Color textColor;
final double size;
final double fontSize;
final int number;
final VoidCallback onTap;
Ball({Key key, @required this.number,
this.textColor,
this.ballColor,
this.onTap,
this.size = 55.0,
this.fontSize = 14,
}) : super(key : key);
@override
Widget build(BuildContext context) {
return Container(
height: size,
width: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [
Style.secondaryColor,
ballColor != null ? ballColor : Style.ballColor,
],
begin: Alignment.bottomLeft,
end: Alignment.topRight
)
),
child: FlatButton(
padding: EdgeInsets.all(0),
child: Container(
child: Text(
number.toString().length > 1 ? number.toString() : "0" + number.toString(),
style: TextStyle(
fontSize: fontSize,
color: textColor != null ? textColor : Colors.white
),
),
padding: const EdgeInsets.all(4.0),
decoration:BoxDecoration(
color: Colors.transparent,
border: Border.all(color: textColor != null ? textColor : Colors.white, width: 1),
borderRadius: BorderRadius.circular(32),
)
),
color: Colors.transparent,
onPressed: onTap,
),
);
}
}
1 ответ
Проблема в том, что getBallNumbers
вызывается только один раз в initState
, так когда picks
обновляется, это не имеет значения, потому что getBallNumbers
не вызывается снова, чтобы обновить цвета, передаваемые в Ball
виджеты.
Простое решение - позвонить getBallNumbers
в твоем build
с участием future: getBallNumbers()
, но это приведет к CircularProgressIndicator
отображается при каждом нажатии как List
регенерирует.
Однако в идеале вы должны обрабатывать все изменения цвета в состоянии каждого Ball
чтобы вас не заставляли перестраивать List
на каждый щелчок. И поддерживатьList
выбранных номеров в родительском виджете State
, вы должны передать обратный вызов каждому шару, который добавляет и удаляет их номер из List
в родительском.
Грубый пример:
Класс Ball (изменен для сохранения состояния и удалены параметры, которые стали ненужными; активное состояние теперь сохраняется внутри шара, а не только в родительском):
class Ball extends StatefulWidget {
final double size;
final double fontSize;
final int number;
final VoidCallback toggleBall;
final bool initialActiveState;
Ball({Key key, @required this.number,
this.toggleBall,
this.size = 55.0,
this.fontSize = 14,
this.initialActiveState,
}) : super(key : key);
_BallState createState() => _BallState();
}
class _BallState extends State<Ball> {
bool isActive;
@override
void initState() {
super.initState();
isActive = widget.initialActiveState;
}
@override
Widget build(BuildContext context) {
return Container(
height: widget.size,
width: widget.size,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [
Style.secondaryColor,
isActive ? Style.selectedBallColor : Style.ballColor,
],
begin: Alignment.bottomLeft,
end: Alignment.topRight
)
),
child: FlatButton(
padding: EdgeInsets.all(0),
child: Container(
child: Text(
widget.number.toString().length > 1 ? widget.number.toString() : "0" + widget.number.toString(),
style: TextStyle(
fontSize: widget.fontSize,
color: isActive ? Colors.black : Colors.white,
),
),
padding: const EdgeInsets.all(4.0),
decoration:BoxDecoration(
color: Colors.transparent,
border: Border.all(color: isActive ? Colors.black : Colors.white, width: 1),
borderRadius: BorderRadius.circular(32),
)
),
color: Colors.transparent,
onPressed: () {
if(!isActive && widget.activeBallList.length >= 7) {
return;
}
setState(() {
isActive = !isActive;
});
widget.activeBallList.contains(widget.number) ? widget.activeBallList.remove(widget.number) : widget.activeBallList.add(widget.number);
},
),
);
}
}
Родительский класс (единственная часть, которую нужно изменить, - это параметры для Ball
):
Future<List<Widget>> getBallNumbers() async {
return List.generate(limitBallNumber,(number){
number = number + 1;
return Padding(
padding:EdgeInsets.all(2.5),
child: Ball(
number: number,
size: ballWidth,
initialActiveState: picks.contains(number),
activeBallList: picks,
)
);
});
}