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) {}
}
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,
),
);
}
}
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();
},
);
}
}