如何在 Flutter listview streambuilder 中停止自动滚动?

Posted

技术标签:

【中文标题】如何在 Flutter listview streambuilder 中停止自动滚动?【英文标题】:How to stop auto scrolliing in Flutter listview stream builder? 【发布时间】:2021-07-06 16:51:22 【问题描述】:

当消息流在列表视图中不断流动时,在阅读特定消息时继续向下滚动会干扰。有没有办法在阅读一些已经可用的流消息时避免自动滚动? 这是我试图在列表视图中显示流消息的代码

 List<Comment> comments = <Comment>[];
  StreamSubscription<Comment>? sub;
  Future<void> redditmain() async 
    SharedPreferences preferences = await SharedPreferences.getInstance();

 
    Reddit reddit = await Reddit.createScriptInstance(
      clientId: cId,
      clientSecret: cSecret,
      userAgent: uAgent,
      username: uname,
      password: upassword, // Fake
    );
   
    sub = reddit.subreddit('cricket').stream.comments().listen((comment) 
      if (comment != null) 
   
        setState(() 
          comments.insert(0, comment);
        );
       
      
    ) as StreamSubscription<Comment>?;
  

  @override
  Future<void> didChangeDependencies() async 
    super.didChangeDependencies();
   
    redditmain();
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      
        body:Center(
                child: Container(
                  child: Scrollbar(
                    child: ListView.builder(
                      scrollDirection: Axis.vertical,
                      itemCount: comments.length,
                      itemBuilder: (context, index) 
                        if (comments.isNotEmpty) 
                          final Comment comment = comments[index];
                          return Card(
                            elevation: 3,
                            semanticContainer: true,
                            clipBehavior: Clip.antiAliasWithSaveLayer,
                            color: getMyColor(comment
                                    .data!["author_flair_richtext"].isNotEmpty
                                ? comment.data!['author_flair_richtext'][0]['a']
                                : ''),
                            child: ListTile(
                              onTap: () 
                                _launchURL(comment.permalink);
                              ,
                              trailing: Text(
                                comment.author,
                                style: TextStyle(color: text_color),
                              ),
                              title: Text(
                                comment.body ?? '',
                                style: TextStyle(color: text_color),
                              ),
                              subtitle: Text(comment.data?['link_title'] ?? '',
                                  style: TextStyle(color: text_color)),
                              leading: Image.network(comment
                                      .data!["author_flair_richtext"].isNotEmpty
                                  ? comment.data!['author_flair_richtext'][0]
                                          ['u'] ??
                                      "https://png.pngtree.com/png-clipart/20190927/ourmid/pngtree-cricket-stumps-and-ball-png-image_1733735.jpg"
                                  : "https://png.pngtree.com/png-clipart/20190927/ourmid/pngtree-cricket-stumps-and-ball-png-image_1733735.jpg"),
                              // Text(comment.data?['author_flair_css_class'] ?? ''),
                            ),
                          );
                         else
                          return Theme(
                            data: Theme.of(context)
                                .copyWith(accentColor: Colors.red),
                            child: new CircularProgressIndicator(),
                          );
                      ,
                    ),
                  ),
                ),
              ));
  

这使得流以更高的速率连续流动,因此当我向下滚动阅读特定消息时,它会随着新的流消息进入而继续移动。 我想在不从传入流向下滚动页面的情况下阅读消息有没有办法实现这一点?对此的任何建议都会非常有帮助,期待您的解决方案。

编辑我尝试了@downgrade 给出的解决方案,并更新了我的代码,如下所示。最初,它加载消息流并滚动到屏幕底部显示。当我滚动到顶部时,所有列表视图都会变成一个圆形指示器,如 所示。

List<Comment> comments = <Comment>[];
  StreamSubscription<Comment>? sub;
  late StreamQueue<Comment?> commentQueue;
  static const int commentLimit = 100;
  Future<void> redditmain() async 



    Reddit reddit = await Reddit.createScriptInstance(
      clientId: cId,
      clientSecret: cSecret,
      userAgent: uAgent,
      username: uname,
      password: upassword, // Fake
    );
    setState(() 
      commentQueue = StreamQueue<Comment?>(
          reddit.subreddit('cricket').stream.comments(limit: commentLimit));
    );


  

  @override
  Future<void> didChangeDependencies() async 
    super.didChangeDependencies();
    // getEmail();
    redditmain();
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
     
      body: cId == null
          ? Container(
              color: Colors.transparent,
              child: Center(
                child: CircularProgressIndicator(
                  valueColor:
                      AlwaysStoppedAnimation<Color>(Colors.deepPurpleAccent),
                ),
              ),
            )
          : Center(
              child: Container(
                child: Scrollbar(
                  child: ListView.builder(
                    reverse: true,
                    scrollDirection: Axis.vertical,
                    itemCount: commentLimit,
                    itemBuilder: (context, index) => FutureBuilder<Comment?>(
                      future: commentQueue.next,
                      builder:
                          (BuildContext ctx, AsyncSnapshot<Comment?> snap) 
                        final Comment? comment = snap.data;
                        if (comment == null) 
                          return Card(
                            elevation: 3,
                            semanticContainer: true,
                            clipBehavior: Clip.antiAliasWithSaveLayer,
                            child: ListTile(
                              title: CircularProgressIndicator(),
                            ),
                          );
                        
                        return Card(
                          elevation: 3,
                          // color: (index % 2 == 0) ? Colors.yellow : Colors.white,
                          semanticContainer: true,
                          clipBehavior: Clip.antiAliasWithSaveLayer,
                          // child:Image.network('https://placeimg.com/640/480/any',fit:BoxFit.fill),
                          color: getMyColor(comment!
                                  .data!["author_flair_richtext"].isNotEmpty
                              ? comment!.data!['author_flair_richtext'][0]['a']
                              : ''),
                          child: ListTile(
                            onTap: () 
                              _launchURL(comment.permalink);
                            ,
                            trailing: Text(
                              comment.author,
                              style: TextStyle(color: text_color),
                            ),
                            title: Text(
                              comment.body ?? '',
                              style: TextStyle(color: text_color),
                            ),
                            subtitle: Text(comment.data?['link_title'] ?? '',
                                style: TextStyle(color: text_color)),
                            leading: Image.network(comment
                                    .data!["author_flair_richtext"].isNotEmpty
                                ? comment.data!['author_flair_richtext'][0]
                                        ['u'] ??
                                    "https://png.pngtree.com/png-clipart/20190927/ourmid/pngtree-cricket-stumps-and-ball-png-image_1733735.jpg"
                                : "https://png.pngtree.com/png-clipart/20190927/ourmid/pngtree-cricket-stumps-and-ball-png-image_1733735.jpg"),
                            // Text(comment.data?['author_flair_css_class'] ?? ''),
                          ),
                        );
                      ,
                    ),
                  ),
                ),
              ),
            ),
             );
  

【问题讨论】:

【参考方案1】:

编辑:我误解了这个问题。看起来 OP 希望有一个反向列表,当到达列表边缘时,它会获取队列中的下一条评论。这是使用 async 包的一种可能方法:

late StreamQueue<Comment?> commentQueue;
static const int commentLimit = 100;

Future<void> redditmain() async 
  Reddit reddit = await Reddit.createScriptInstance(
    clientId: cId,
    clientSecret: cSecret,
    userAgent: uAgent,
    username: uname,
    password: upassword, // Fake
  );

  commentQueue = StreamQueue<Comment?>(
      reddit.subreddit('cricket').stream.comments(limit: commentLimit));


@override
Future<void> didChangeDependencies() async 
  super.didChangeDependencies();

  redditmain();


@override
Widget build(BuildContext context) 
  return Scaffold(
      body: Center(
    child: Container(
      child: Scrollbar(
        child: ListView.builder(
          reverse: true,
          scrollDirection: Axis.vertical,
          itemCount: commentLimit,
          itemBuilder: (context, index) => FutureBuilder<Comment?>(
              future: commentQueue.next,
              builder: (BuildContext ctx, AsyncSnapshot<Comment?> snap) 
                final Comment? comment = snap.data;
                if (comment == null) 
                  return ListTile(
                    title: CircularProgressIndicator(),
                  );
                
                return ListTile(
                  title: Text(comment.body ?? ''),
                );
              ),
        ),
      ),
    ),
  ));

这利用了异步包的 StreamQueue 通过 next() 方法将流从推送行为转换为拉取行为。然后我们将 next() 传递给 ListView.builder 中的未来构建器。

此外,我们不是反向构建一个列表然后从中构建一个列表视图,而是使用 ListView 上的反向可选标志来告诉它使用反向滚动控制器。

请注意,一些改进方法可能包括检查 StreamQueue 的 hasNext() 方法,以防止在没有评论时尝试获取其他评论。目前,如果您用完评论列表或超过 100 cmets,您最终可能会看到一个无限期地显示圆形进度指示器的列表图块。

还可以改进加载状态的 UI。此示例应仅作为概念验证,用于在需要时拉取流事件,而不是等待它们推送。

原答案

为了延迟流,可以使用 pause() 方法。对您的问题最直接的回答是修改您的 .listen 回调,如下所示:

if (comment != null) 
  setState(() 
    comments.insert(0, comment);
  );
  
  // Delay next item for one second
  sub.pause(Future.delayed(Duration(seconds: 1))); 

但是,您可能希望立即加载一定数量的 cmets,然后在显示第一批后延迟:

List<Comment> comments = <Comment>[];
StreamSubscription<Comment>? sub;
var firstBatch = 10; // How many comments should we load first?

Future<void> redditmain() async 
  Reddit reddit = await Reddit.createScriptInstance(
    clientId: cId,
    clientSecret: cSecret,
    userAgent: uAgent,
    username: uname,
    password: upassword, // Fake
  );

  sub = reddit.subreddit('cricket').stream.comments().listen((comment) 
    if (comment != null) 
      setState(() 
        comments.insert(0, comment);
      );
      if (firstBatch == 0) 
        // First load is done, delay all remaining
        sub.pause(Future.delayed(Duration(seconds: 1)));
       else 
        // First load is pending, decrement counter
        firstBatch--;
      
    
  ) as StreamSubscription<Comment>?;

一旦屏幕启动并且流可用,这将加载 10 个 cmets,之后的所有 cmets 将每秒加载一个。

【讨论】:

【参考方案2】:

仅使用ListView 即可,添加ListView.builder 将使其可滚动,因为ListView.builder 用于动态列表。

【讨论】:

我尝试仅更改为 listview,但它没有显示流消息所必需的所有属性,如 itemcount、itembuilder

以上是关于如何在 Flutter listview streambuilder 中停止自动滚动?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Flutter 的 ListView 中添加列?

Flutter - 如何在 Listview 构建器顶部添加项目?

如何在 Flutter 中将 Hero 动画添加到我的 ListView?

Flutter:如何在 Listview 中删除行之间的空间

如何在 Flutter 中手动分配 Listview.builder 的第一个元素?

Flutter中如何根据视口高度约束ListView