Как обновить состояние виджета, встроенного в 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,
      )
    );
  });
}
Другие вопросы по тегам