web-dev-qa-db-fra.com

Flutter change d'état depuis l'objet StatefulWidget

Comme les états de titre, comment peut-on accéder à l'état d'un StatefulWidget à partir du StatefulWidget.

Contexte: J'ai un widget de classement par étoiles composé de 5 "StarWidget" consécutifs. La classe StarWidget n'est qu'une icône avec un détecteur enroulé autour d'elle (n'utilisant pas IconButton car elle a une très grande taille). Le StarWidget stocke s'il est sélectionné ou non dans un objet State correspondant et affiche en conséquence une icône pleine ou contour.

Dans mon widget principal, j'ai accès aux objets StatefulWidget et je souhaite configurer leurs états.

import 'package:flutter/material.Dart';

import 'package:font_awesome_flutter/font_awesome_flutter.Dart';

class StarRatingWidget extends StatefulWidget {
  @override
  _StarRatingWidgetState createState() {
    return _StarRatingWidgetState();
  }
}

class _StarRatingWidgetState extends State<StarRatingWidget>
    implements StarSelectionInterface {
  //Properties
  int _currentRating = 0;
  List<RatingStarWidget> starWidgets = [];

  //Methods
  @override
  void initState() {
    super.initState();
    starWidgets.add(
      RatingStarWidget(
        starSelectionInterface: this,
        starPosition: 0,
      ),
    );
    starWidgets.add(
      RatingStarWidget(
        starSelectionInterface: this,
        starPosition: 1,
      ),
    );
    starWidgets.add(
      RatingStarWidget(
        starSelectionInterface: this,
        starPosition: 2,
      ),
    );
    starWidgets.add(
      RatingStarWidget(
        starSelectionInterface: this,
        starPosition: 3,
      ),
    );
    starWidgets.add(
      RatingStarWidget(
        starSelectionInterface: this,
        starPosition: 4,
      ),
    );
  }

  @override
  Widget build(BuildContext buildContext) {
    return Row(
      children: starWidgets,
    );
  }

  //Star Selection Interface Methods
  void onStarSelected(_RatingStarWidgetState starWidgetState) {
    print("listener: star selected ${starWidgetState._starPosition}");

    //a new, rating has been selected, update rating
    if (_currentRating != starWidgetState._starPosition) {
      _currentRating = (starWidgetState._starPosition + 1);
    }

    //same star as rating has been selected, set rating to 0
    else {
      _currentRating = 0;
    }

    //update stars according to rating
    for(int i = 1; i <= 5; i++) {
      //what should I do here?!
    }
  }
}

class RatingStarWidget extends StatefulWidget {
  //Properties
  final int starPosition;
  final StarSelectionInterface starSelectionInterface;

  //Constructors
  RatingStarWidget({this.starSelectionInterface, this.starPosition});

  //Methods
  @override
  _RatingStarWidgetState createState() {
    return _RatingStarWidgetState(starSelectionInterface, starPosition);
  }
}

class _RatingStarWidgetState extends State<RatingStarWidget> {
  //Properties
  int _starPosition;
  bool _isSelected = false;
  StarSelectionInterface selectionListener;

  //Constructors
  _RatingStarWidgetState(this.selectionListener, this._starPosition);

  //Methods
  @override
  Widget build(BuildContext buildContext) {
    return AnimatedCrossFade(
      firstChild: GestureDetector(
        child: Icon(
          FontAwesomeIcons.star,
          size: 14,
        ),
        onTap: () {
          print("star: selected");
          selectionListener.onStarSelected(this);
        },
      ),
      secondChild: GestureDetector(
        child: Icon(
          FontAwesomeIcons.solidStar,
          size: 14,
        ),
        onTap: () {
          selectionListener.onStarSelected(this);
        },
      ),
      duration: Duration(milliseconds: 300),
      crossFadeState:
          _isSelected ? CrossFadeState.showSecond : CrossFadeState.showFirst,
    );
  }
}

class StarSelectionInterface {
  void onStarSelected(_RatingStarWidgetState starWidgetState) {}
}
10
krishnakeshan

La méthode Flutter consiste à reconstruire les widgets chaque fois que cela est nécessaire. N'ayez pas peur de créer des widgets, ils sont bon marché pour le SDK, spécialement dans ce cas pour les étoiles simples.

Accéder à un autre état de widget nécessite plus de travail que de le reconstruire. Pour accéder à l'état, vous devez utiliser des clés ou vous devez ajouter des méthodes spéciales dans le widget lui-même.

Dans ce cas, où l'étoile est reconstruite, quoi qu'il en soit, il est encore plus simple et plus simple d'utiliser des widgets sans état car l'état sélectionné peut être fourni par le parent au moment de la reconstruction.

Et puisque l'état est stocké dans le widget parent, je pense qu'il vaut mieux ne pas le stocker comme mur dans chacune des étoiles individuelles.

Voici une solution très simple qui suit cette idée. Mais oui, cela reconstruit toujours les étoiles.

import 'package:flutter/material.Dart';

import 'package:font_awesome_flutter/font_awesome_flutter.Dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(body: Center(child: StarRatingWidget())),
    );
  }
}

class StarRatingWidget extends StatefulWidget {
  @override
  _StarRatingWidgetState createState() {
    return _StarRatingWidgetState();
  }
}

class _StarRatingWidgetState extends State<StarRatingWidget> {
  int _currentRating = 0;

  List<Widget> buildStars() {
    List<RatingStarWidget> starWidgets = [];
    for (int i = 0; i < 5; i++) {
      starWidgets.add(
        RatingStarWidget(
          clickCallback: () => setState(() {
                _currentRating = i + 1;
              }),
          highlighted: _currentRating > i,
        ),
      );
    }
    return starWidgets;
  }

  @override
  Widget build(BuildContext buildContext) {
    return Row(
      children: buildStars(),
    );
  }
}

class RatingStarWidget extends StatelessWidget {
  //Properties
  final VoidCallback clickCallback;
  final bool highlighted;

  //Constructors
  RatingStarWidget({this.clickCallback, this.highlighted});

  @override
  StatelessElement createElement() {
    print("Element created");
    return super.createElement();
  }

  //Methods
  @override
  Widget build(BuildContext buildContext) {
    return GestureDetector(
      onTap: () {
        clickCallback();
      },
      child: AnimatedCrossFade(
        firstChild: Icon(
          FontAwesomeIcons.star,
          size: 14,
        ),
        secondChild: Icon(
          FontAwesomeIcons.solidStar,
          size: 14,
        ),
        duration: Duration(milliseconds: 300),
        crossFadeState:
            highlighted ? CrossFadeState.showSecond : CrossFadeState.showFirst,
      ),
    );
  }
}
5
chemamolins

J'ai écrit mon propre exemple similaire au vôtre. Ce que je fais ici, c'est:

Le taux d'étoiles initial est -1 car les tableaux commencent à 0;) et je crée des étoiles avec la position, le taux d'étoiles actuel et la fonction de rappel. Nous utiliserons cette fonction de rappel pour mettre à jour la valeur dans ScreenOne.

Dans le widget Star, nous avons un bool local selected avec la valeur par défaut false et nous lui attribuons une valeur à l'intérieur de la fonction de construction en fonction de la position de l'étoile et du taux actuel. Et nous avons la fonction setSelected() qui exécute la fonction de rappel et met à jour currentRate avec la valeur de la position de l'étoile.

Vérifiez l'exemple vidéo ici .

class ScreenOne extends StatefulWidget {
  @override
  _ScreenOneState createState() => _ScreenOneState();
}

class _ScreenOneState extends State<ScreenOne> {
  int currentRate = -1; //since array starts from 0, set non-selected as -1
  List<Star> starList = []; //empty list
  @override
  void initState() {
    super.initState();
    buildStars(); //build starts here on initial load
  }

  Widget buildStars() {
    starList = [];
    for (var i = 0; i < 5; i++) {
      starList.add(Star(
        position: i,
        current: currentRate,
        updateParent: refresh, //this is callback
      ));
    }
  }

  refresh(int index) {
    setState(() {
      currentRate = index; //update the currentRate
    });
    buildStars(); //build stars again
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text("Test page 1"),
      ),
      body: Container(
        child: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: starList,
          ),
        ),
      ),
    );
  }
}






class Star extends StatefulWidget {
  final Function(int index) updateParent; //callback
  final int position; //position of star
  final int current; //current selected star from parent

  const Star({Key key, this.position, this.updateParent, this.current})
      : super(key: key);

  @override
  _StarState createState() => _StarState();
}

class _StarState extends State<Star> {
  bool selected = false;

  void setSelected() {
    widget.updateParent(widget.position);
  }

  @override
  Widget build(BuildContext context) {
    if (widget.current >= widget.position) {
      selected = true;
    } else {
      selected = false;
    }
    return GestureDetector(
      child: AnimatedCrossFade(
        firstChild: Icon(Icons.star_border),
        secondChild: Icon(Icons.star),
        crossFadeState:
            selected ? CrossFadeState.showSecond : CrossFadeState.showFirst,
        duration: Duration(milliseconds: 300),
      ),
      onTap: () {
        setSelected();
      },
    );
  }
}
0
westdabestdb