Listview.builder 的 builditem 函数中的 Flutter Setstate 未重新加载

Flutter Setstate inside Listview.builder's builditem function not reloading

我有一个从 firestore 加载信息的 streambuilder。然后我使用 listview.builder 来显示信息。显示的信息正在被函数firstBuildItem 或buildItem 调用。

我想要的功能是,当用户单击用手势检测器包裹的容器时,我希望在主小部件下方展开另一个小部件。我不确定如何用 flutter 实现这一点,因为每条消息都是内部项目 listview.builder,布尔值需要在函数内部是局部的,因为每个函数都有自己的布尔值,用于检查列表项是否被点击。我想了解如何修复我的代码,以便通过展开在列表视图中单击每个项目时做出响应。

ListView.builder(
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) =>
index == 0 ? firstBuildItem(index, snapshot.data.documents[index]) : buildItem(index, snapshot.data.documents[index], snapshot.data.documents[index-1]),
itemCount: snapshot.data.documents.length,
reverse: true,
controller: listScrollController,
),

然后这里是 builditem 函数:

Widget firstBuildItem(int index, DocumentSnapshot mainDocument) {

  ///If the box is expanded
  bool _isExpanded = false;

  ///Toggle the box to expand or collapse
  void _toggleExpand() {
    setState(() {
      _isExpanded = !_isExpanded;
    });
  }


  return GestureDetector(

    onTap: _toggleExpand,

    child: Container(

      color: _isExpanded ? Colors.blueGrey : null,

      child: Column(


        children: <Widget>[

          Column(
            children: <Widget>[

              SizedBox(height: 20.0),

              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[

                  ChatDateHeader(timestamp: mainDocument['timestamp']),

                ],
              ),
            ],
          ),

          Column(

            children: <Widget>[

              Row(
                children: <Widget>[
                  Container(
                    child: Text(
                      mainDocument['content'],
                      style: TextStyle(color: primaryColor),
                    ),
                    padding: EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0),

                  ),
                ],
                mainAxisAlignment: MainAxisAlignment.end,
              ),

              getTime(mainDocument['timestamp'], true),

              ExpandedSection(
                expand: _isExpanded,
                child: getDate(mainDocument['timestamp'], true),
              ),

              SizedBox(height: 20.0),

            ],
            crossAxisAlignment: CrossAxisAlignment.end,
          ),
        ],
      ),
    ),
  );
}

然后是展开的小部件:

class ExpandedSection extends StatefulWidget {

  final Widget child;
  final bool expand;
  ExpandedSection({this.expand = false, this.child});

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

class _ExpandedSectionState extends State<ExpandedSection> with SingleTickerProviderStateMixin {
  AnimationController expandController;
  Animation<double> animation;

  @override
  void initState() {
    super.initState();
    prepareAnimations();
  }

  ///Setting up the animation
  void prepareAnimations() {
    expandController = AnimationController(
        vsync: this,
        duration: Duration(milliseconds: 500)
    );
    Animation curve = CurvedAnimation(
      parent: expandController,
      curve: Curves.fastOutSlowIn,
    );
    animation = Tween(begin: 0.0, end: 1.0).animate(curve)
      ..addListener(() {
        setState(() {

        });
      }
      );
  }

  @override
  void didUpdateWidget(ExpandedSection oldWidget) {
    super.didUpdateWidget(oldWidget);
    if(widget.expand) {
      expandController.forward();
    }
    else {
      expandController.reverse();
    }
  }

  @override
  void dispose() {
    expandController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SizeTransition(
        axisAlignment: 1.0,
        sizeFactor: animation,
        child: widget.child
    );
  }
}

我通过将 firstBuildItem 小部件函数变成它自己的有状态小部件 class 解决了这个问题。然后将一个键传递给该函数。有了这个,setState 就可以工作而无需重建整个 streambuilder。

ListView.builder(
                  padding:    EdgeInsets.all(10.0),
                  itemCount:  snapshot.data.documents.length,
                  reverse:    true,
                  controller: listScrollController,
                  itemBuilder: (context, index) =>

                    index == snapLength ?

                    FirstChatBuildMessageItem(
                      key:          Key('counter-${index}'),
                      id:           id,
                      peerAvatar:   peerAvatar,
                      index:        index,
                      mainDocument: snapshot.data.documents[index],
                      listMessage: listMessage,
                    )
                        :

                    //index == 0 ?

                    ChatBuildMessageItem(
                      key:              Key('counter-${index}'),
                      id:               id,
                      peerAvatar:       peerAvatar,
                      index:            index,
                      mainDocument:     snapshot.data.documents[index],
                      previousDocument: snapshot.data.documents[index + 1],
                      listMessage:      listMessage,
                    ),

                ),