来自StatefulWidget对象的抖动更改状态

时间:2019-01-13 09:23:38

标签: dart flutter state

像标题状态一样,如何从StatefulWidget访问StatefulWidget的状态。

背景: 我有一个星级评分小部件,该部件连续包含5个“ StarWidget”。 StarWidget类只是一个包裹有检测器的Icon(不使用IconButton,因为它的尺寸很大)。 StarWidget将是否选择它存储在相应的State对象中,并相应显示一个实心或轮廓图标。

在主窗口小部件中,我可以访问StatefulWidget对象,并希望配置其状态。

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) {}
}

2 个答案:

答案 0 :(得分:1)

Flutter的方法是在必要时重建小部件。不要害怕构建窗口小部件,它们对于SDK而言是便宜的,在这种情况下,对于简单的星星尤其如此。

访问其他窗口小部件状态需要的工作不仅仅是重建它。要访问状态,您应该使用键,或者应该在小部件本身中添加特殊方法。

在这种情况下,无论如何重建星形,使用普通的无状态小部件甚至更好,更简单,因为选定的状态可以在重建时由父级提供。

并且由于状态存储在父窗口小部件中,所以我认为最好不要将其作为墙存储在每个单独的星星中。

接下来是遵循这个想法的非常简单的解决方案。但是,是的,它仍然可以重建星星。

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

答案 1 :(得分:0)

我写了自己的例子,类似于您的例子。我在这里做的是:

因为数组从0开始,初始星速是-1;)我创建具有位置,当前星速和回调函数的星。我们将使用此回调函数来更新ScreenOne中的值。

Star小部件中,我们有一个局部布尔值selected,其默认值为false,并根据星号的位置和当前速率在构建函数内为其分配一个值。我们有setSelected()函数,该函数运行回调函数并用星标位置值更新currentRate

查看视频示例here

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