2017-10-09 2 views
1

Je suis en train de créer une barre d'étoiles pour avis des utilisateurs, l'image est du magasin de jeu pour illustrer ce que je suis en train de réaliser:Comment créer correctement une barre d'étoiles?

enter image description here

J'ai été en mesure de réaliser ce qui suit, qui est fonctionnel comme vous pouvez le voir, mais quand je regarde mon code, je sens qu'il doit y avoir une façon plus intelligente de le faire, et la partie vraiment ennuyeuse est que la zone d'impact IconButton est décalée un peu, donc quand vous touchez l'étoile réelle ne s'enregistre pas en tant qu'événement tactile (ie: vous devez viser plus haut que l'endroit où le bouton est positionné pour que votre touche soit enregistrée, ce qui crée un mauvais UX) vous pouvez vérifier ce que je veux dire en gardant un oeil sur le splash effec t lorsque je clique sur l'une des étoiles:

enter image description here

var _myColorOne = Colors.grey; 
    var _myColorTwo = Colors.grey; 
    var _myColorThree = Colors.grey; 
    var _myColorFour = Colors.grey; 
    var _myColorFive = Colors.grey; 


    @override 
    Widget build(BuildContext context) { 
    return new Scaffold(
    appBar: new AppBar(
     title: new Text("My Rating"), 
    ), 
     body: new Center(
     child: new Container(
      height: 10.0, 
      width: 500.0, 
      child: new Row(
       mainAxisAlignment: MainAxisAlignment.center, 
       children: <Widget>[ 
       new IconButton(icon: new Icon(Icons.star), 
        onPressed:()=>setState((){ 
        _myColorOne=Colors.orange; 
        _myColorTwo=null; 
        _myColorThree=null; 
        _myColorFour=null; 
        _myColorFive=null; 
       }),color: _myColorOne,), 
       new IconButton(icon: new Icon(Icons.star), 
        onPressed:()=>setState((){ 
        _myColorOne=Colors.orange; 
        _myColorTwo=Colors.orange; 
        _myColorThree=Colors.grey; 
        _myColorFour=Colors.grey; 
        _myColorFive=Colors.grey; 
       }),color: _myColorTwo,), 
       new IconButton(icon: new Icon(Icons.star), onPressed:()=>setState((){ 
        _myColorOne=Colors.orange; 
        _myColorTwo=Colors.orange; 
        _myColorThree=Colors.orange; 
        _myColorFour=Colors.grey; 
        _myColorFive=Colors.grey; 
       }),color: _myColorThree,), 
       new IconButton(icon: new Icon(Icons.star), onPressed:()=>setState((){ 
        _myColorOne=Colors.orange; 
        _myColorTwo=Colors.orange; 
        _myColorThree=Colors.orange; 
        _myColorFour=Colors.orange; 
        _myColorFive=Colors.grey; 
       }),color: _myColorFour,), 
       new IconButton(icon: new Icon(Icons.star), onPressed:()=>setState((){ 
        _myColorOne=Colors.orange; 
        _myColorTwo=Colors.orange; 
        _myColorThree=Colors.orange; 
        _myColorFour=Colors.orange; 
        _myColorFive=Colors.orange; 
       }),color: _myColorFive,), 
       ], 

     ), 
     ), 
    ), 
    ); 
    } 

Alors quelle est la meilleure approche ici?

Répondre

2

Trop de répétition et de rembourrage! duh

De toute façon, c'est comme ça que je le ferais. Simple, réutilisable. Vous pouvez l'utiliser avec et sans clic (aucun clic ne désactive l'effet d'entraînement). Demi-étoiles aussi. Et utilisez la couleur primaire pour les étoiles remplies si aucune couleur n'est spécifiée.

enter image description here

La couleur étoile "noir" est incorrect dans cette image. Il est gris sur les mobiles, mais en raison d'un problème de flutter il est affiché en noir dans mon émulateur.

typedef void RatingChangeCallback(double rating); 

class StarRating extends StatelessWidget { 
    final int starCount; 
    final double rating; 
    final RatingChangeCallback onRatingChanged; 
    final Color color; 

    StarRating({this.starCount = 5, this.rating = .0, this.onRatingChanged, this.color}); 

    Widget buildStar(BuildContext context, int index) { 
    Icon icon; 
    if (index >= rating) { 
     icon = new Icon(
     Icons.star_border, 
     color: Theme.of(context).buttonColor, 
    ); 
    } 
    else if (index > rating - 1 && index < rating) { 
     icon = new Icon(
     Icons.star_half, 
     color: color ?? Theme.of(context).primaryColor, 
    ); 
    } else { 
     icon = new Icon(
     Icons.star, 
     color: color ?? Theme.of(context).primaryColor, 
    ); 
    } 
    return new InkResponse(
     onTap: onRatingChanged == null ? null :() => onRatingChanged(index + 1.0), 
     child: icon, 
    ); 
    } 

    @override 
    Widget build(BuildContext context) { 
    return new Row(children: new List.generate(starCount, (index) => buildStar(context, index))); 
    } 
} 

Vous pouvez alors l'utiliser comme ceci:

class Test extends StatefulWidget { 
    @override 
    _TestState createState() => new _TestState(); 
} 

class _TestState extends State<Test> { 
    double rating = 3.5; 

    @override 
    Widget build(BuildContext context) { 
    return new StarRating(
     rating: rating, 
     onRatingChanged: (rating) => setState(() => this.rating = rating), 
    ); 
    } 
} 
+0

Je ne suis pas sûr de ce que je devrais définir 'onRatingChanged:' à, afin d'activer la note en cliquant? – aziza

+0

La plupart du temps, un '(rating) => setState (() => this.rating = rating)) 'devrait suffire – Darky

+0

Pouvez-vous clarifier cela s'il vous plaît? n'est pas 'onRatingChanged' devrait être réglé à quelque chose comme ça' onRatingChanged: (RatingChangeCallback) => .... ' – aziza

2

Cela se produit en raison de la hauteur spécifiée pour le widget de ligne. Les gestes sont détectés uniquement à cette hauteur. La suppression de la hauteur mettra la ligne à l'échelle pour s'adapter aux enfants, permettant ainsi de détecter parfaitement les gestes du tap sur IconButton.

Je pense que j'ai fait une solution légèrement meilleure.

Note code du composant:

int _rating = 0; 

void rate(int rating) { 
    //Other actions based on rating such as api calls. 
    setState(() { 
    _rating = rating; 
    }); 
} 

@override 
Widget build(BuildContext context) { 
    return new Scaffold(
    appBar: new AppBar(
     title: new Text("My Rating"), 
    ), 
    body: new Center(
     child: new Container(
     width: 500.0, 
     child: new Row(
      mainAxisAlignment: MainAxisAlignment.center, 
      children: <Widget>[ 
      new GestureDetector(
       child: new Icon(
       Icons.star, 
       color: _rating >= 1 ? Colors.orange : Colors.grey, 
      ), 
       onTap:() => rate(1), 
      ), 
      new GestureDetector(
       child: new Icon(
       Icons.star, 
       color: _rating >= 2 ? Colors.orange : Colors.grey, 
      ), 
       onTap:() => rate(2), 
      ), 
      new GestureDetector(
       child: new Icon(
       Icons.star, 
       color: _rating >= 3 ? Colors.orange : Colors.grey, 
      ), 
       onTap:() => rate(3), 
      ), 
      new GestureDetector(
       child: new Icon(
       Icons.star, 
       color: _rating >= 4 ? Colors.orange : Colors.grey, 
      ), 
       onTap:() => rate(4), 
      ), 
      new GestureDetector(
       child: new Icon(
       Icons.star, 
       color: _rating >= 5 ? Colors.orange : Colors.grey, 
      ), 
       onTap:() => rate(5), 
      ) 
      ], 
     ), 
    ), 
    ), 
); 
} 

Si vous souhaitez utiliser votre code dans la question, puis Remplacer:

child: new Container(
     height: 10.0, 
     width: 500.0, 
     child: new Row(
      mainAxisAlignment: MainAxisAlignment.center, 

Avec:

child: new Container(
     width: 500.0, 
     child: new Row(
      mainAxisAlignment: MainAxisAlignment.center, 

Hope this aidé!

+0

Bien fait avec le> = chèque de la couleur;) –

+0

Que diriez-vous inline la méthode du taux() à onTap:() => setState (() => _rating = 1)? –

+0

La méthode du taux d'incrustation est une bonne idée si aucune action ne doit être déclenchée en fonction de la notation. Je pensais que l'évaluation en ligne pouvait entraîner 5 modifications au cas où une action serait déclenchée en fonction de l'évaluation. Ajout d'un commentaire dans la méthode de taux, j'espère que cela a du sens. –