Flutter - 使用 StreamBuilder 从 firebase 获取数据后,如何使用 ScrollController 跳转到列表视图的底部?

Posted

技术标签:

【中文标题】Flutter - 使用 StreamBuilder 从 firebase 获取数据后,如何使用 ScrollController 跳转到列表视图的底部?【英文标题】:Flutter - How to use ScrollController to jumpto the bottom of the listview after fetching data from firebase using StreamBuilder? 【发布时间】:2020-02-27 11:32:18 【问题描述】:

在使用 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 检查我的答案 :) 【参考方案1】:

当然,滚动控制器是让聊天一直到发送的最后一条消息的第一件事。但这不是唯一的方法。

我提供一个我在做聊天系统时找到的解决方案:

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 工作,因为我们在列表视图中反转它【参考方案2】:
scrollController.jumpTo(scrollController.position.maxScrollExtent);

只会将列表视图滚动到maxScrollValue,即,一次可能的最大滚动。 您将不得不使用

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

实现你的目标。

信息的原因[“challengeReplies”].length*1.0: 因为,JumpTo 需要一个双精度值。

【讨论】:

【参考方案3】:

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

就我而言(我在 StreamBuilder 中使用 PageView,但应该与您的类似),

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

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

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

希望对您有所帮助,我希望有人会发布一个更好、更优雅的解决方案,但这个可行(到目前为止)。

【讨论】:

【参考方案4】:

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

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

【讨论】:

以上是关于Flutter - 使用 StreamBuilder 从 firebase 获取数据后,如何使用 ScrollController 跳转到列表视图的底部?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter屏幕像素适配方案 ( flutter_screenutil 插件 )

Flutter - 使用 google_sign_in 库时未找到 <Flutter/Flutter.h>

Flutter——如何使用 html 链接渲染 Flutter 文本 [重复]

flutter系列之:在flutter中使用导航Navigator

Flutter - 无法在flutter web中使用动态链接

无法使用 Flutter 1.22.3 编译 Flutter 应用程序