Flutter - 如何使用ScrollController在使用StreamBuilder从Firebase获取数据后跳转到ListView底部?

8

在使用Firestore查询流通过StreamBuilder渲染数据后,想要滚动到列表底部,最佳方法是如何使用ScrollController?

在哪里使用scrollcontroller.jumpto方法最合适?

// initialisation
ScrollController scrollController=ScrollController();

// inside initstate() and build method with 1000ms delay - not working
scrollController.jumpTo(scrollController.position.maxScrollExtent);

// widget inside build method
Expanded(
                child: StreamBuilder<DocumentSnapshot>(
                    stream: Firestore.instance
                        .collection('Data')
                        .document(widget.dataID)
                        .snapshots(),
                    builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
                      print("Called");
                      if (!snapshot.hasData) {
                        return Text("Loading");
                      }
                      var info= snapshot.data.data;
                      if (info!= null &&
                          info["messages"] != null &&
                          info["messages"].length > 0 &&
                          userList.length > 0) {
                        return ListView.builder(
                          controller: scrollController,
                          itemCount: info["challengeReplies"].length,
                          itemBuilder: (BuildContext context, int index) {

                            return ChallengeReplyListItem(
                                currentUserID: widget.currentUserID,
                                messagesType: info["messages"][index]["messagesType"],
                                messagesContent: info["messages"][index]["messagesContent"],
                          },
                        );
                      } else if (isLoading) {
                        return Center(
                          child: CircularProgressIndicator(),
                        );
                      } else {
                        return Center(child: Text("No Data Found",style: TextStyle(color: Colors.blueGrey,fontSize: 22,fontWeight: FontWeight.w500),));
                      }
                    }),
              ),

有人能提供一种适当的解决方案,在数据正确呈现后处理滚动到页面底部的问题吗?

谢谢。


你有没有想出解决方案? - markturnip
@markturnip,请检查我的答案 :) - Nick
5个回答

3

当然,滚动控制器是让聊天界面自动滚到最后一条消息的首选方法,但这并不是唯一的方法。

我提供一个我在制作聊天系统时发现的解决方案:

StreamBuilder(
      stream: FirebaseFirestore.instance
      .collection('<MessagesColection>')
      .orderBy('<Time field>',descending: true)
      .snapshots(),
      builder: (context,snapshot) {                    
          return ListView.builder(
            //The reversed list will put the list backwards. 
            //The start of the list will start at the bottom.
            reverse: true, 
            controller: scrollController,      
            itemCount: snapshot.data.size,
            itemBuilder: (context, index) {                                                    
              return ChatMessage(snapshot);          
            },
          );
        }
    ),

在之前的代码中,所做的是反转列表,最近的消息将在底部,记录按降序排序,即从最新到最旧。这样最新的消息将位于列表的开头(在本例中为底部),最不新的消息将位于列表的底部(在本例中为顶部)。

不错!descending: true 起作用了,因为我们在 listview 中进行了反转。 - Manas

2

如果snapshot已经有数据,可以使用scrollController.jumpToaddPostFrameCallback函数。

以下是我的代码片段:

StreamBuilder<List<ConversationModel>>(
                            stream:
                                convoService.getConversation(conversationsPath),
                            builder: (context, snapshot) {
                              if (snapshot.hasData) {
                                final List<ConversationModel> convoList =
                                    snapshot.data ?? [];

                                SchedulerBinding.instance
                                    ?.addPostFrameCallback((_) {
                                  scrollController.jumpTo(
                                    scrollController.position.maxScrollExtent,
                                  );
                                });

                                return buildChatList(convoList);
                              } else {
                                return buildLoader(context);
                              }
                            },
                          )

这个效果相当不错。谢谢你。 - David
这个效果相当不错。谢谢你。 - undefined

0

这可能有点hacky,但我找不到其他方法。

在我的情况下(我正在StreamBuilder内使用PageView,但应该类似于您的情况),

我将其提取为单独的有状态小部件,给它一个UniqueKey()并将其ScrollController移动到其状态中,如下所示(请阅读代码中的注释):

  //The extracted scrolling widget 
  class ScrollHeader extends StatefulWidget {
      const ScrollHeader({
        Key key,
        @required this.slides,
        @required this.currentSlide,
      }) : super(key: key);
    
      final List<Widget> slides;
      final int currentSlide;
    
      @override
      _ScrollHeaderState createState() => _ScrollHeaderState();
    }
    
    class _ScrollHeaderState extends State<ScrollHeader> {
      PageController headerController;
    
      @override
      void initState() {
        super.initState();
    
        //Assign initialPage(Or newly received page) in initState.
        headerController = PageController(initialPage: widget.currentSlide);
      }
    
      @override
      Widget build(BuildContext context) {

        //Since PageView doesn't exist on the first render yet, 
        //ScrollController will throw an error, so we need to escape it using IF:
        if (headerController.hasClients) {
          headerController.animateToPage(
            widget.currentSlide,
            duration: Duration(milliseconds: 350),
            curve: Curves.easeIn,
          );
        }

        //If you don't need animation, you can just do this without IFs:
        headerController.jumpToPage(index);
    
        return PageView(
          scrollDirection: Axis.horizontal,
          children: widget.slides,
          controller: headerController,
        );
      }
    }

StreamBuilder 本身将为此滚动小部件提供 initialPage 和子元素:

Widget build(BuildContext context) {

    Key appbarKey = UniqueKey(); //We need Key to not destroy State between StreamBuilder's updates 

    StreamBuilder(
                    stream: appBarState.stream$, //Could be any stream(Like Firestore for example).
                    builder: (context, snap) {
                      if (snap.hasData) {
                        //Just generated children elements
                        List<Widget> slides = List<Widget>.from(
                          List<String>.from(appBarState.current["slides"]).map(
                            (e) => Center(
                              child: Text(
                                e,
                                style: TextStyle(
                                  color: theme.accentColor,
                                  fontSize: 20,
                                ),
                              ),
                            ),
                          ),
                        );
                        print(snap.data);
                        return ScrollHeader(
                          key: appbarKey,
                          slides: slides,
                          currentSlide: snap.data["currentSlide"], //This is the page that pageView will snap/animate to after receiving new data
                        );
                      }
                      return Container();
                    },
                  ),

这使您能够随时更新和动画/捕捉滚动小部件的页面,以及在使用流时更新其子项。

希望这有所帮助,也希望有人发布更好、更优雅的解决方案,但这个解决方案目前可行。


0

试试这个,它会起作用的:

StreamBuilder(
  stream: ...,
  builder: (BuildContext context, AsyncSnapshot snapshot) {
    // Like this:
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (_controller.hasClients) {
        _controller.jumpTo(_controller.position.maxScrollExtent);
      } else {
        setState(() => null);
      }
     });

     return PutYourListViewHere
}),

0
scrollController.jumpTo(scrollController.position.maxScrollExtent);

仅将列表视图滚动到maxScrollValue,即一次滚动的最大值。

您将需要使用

scrollController.jumpTo(info["challengeReplies"].length*1.0)

实现你所尝试的内容。

info["challengeReplies"].length*1.0的原因是: 因为JumpTo需要一个双精度值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接