You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现向上滚动时的无限加载?IM场景下infinite_scroll_pagination适配

解决方案:实现底部新消息+上滚加载历史的IM分页

一、基于现有infinite_scroll_pagination库调整(无需换库)

你当前使用的库完全支持该需求,只需调整数据排序、分页逻辑和列表行为:

  1. 数据排序与分页逻辑

    • 消息数据按时间升序存储/返回,旧消息在前,新消息在后。
    • 以消息ID或时间戳作为分页标识,加载历史消息时,请求比当前列表最旧消息更早的数据。
    • 实现PagingSource时处理**向前加载(prepend)**逻辑:当用户滚动到列表顶部,触发加载更早的消息并插入列表开头。

    核心代码示例:

    class MessagePagingSource extends PagingSource<int, Message> {
      @override
      Future<PagingSourceSnapshot<int, Message>> load(LoadParams<int> params) async {
        try {
          // params.key为当前最旧消息标识,首次加载传null取最新20条
          final messages = await api.getHistoryMessages(
            beforeId: params.key,
            limit: 20,
          );
          final prevKey = messages.isEmpty ? null : messages.first.id;
          return PagingSourceSnapshot.prepend(
            data: messages,
            prevKey: prevKey,
            nextKey: null, // 新消息通过WebSocket推送,无需加载后续页
          );
        } catch (e) {
          return PagingSourceSnapshot.error(error: e);
        }
      }
    }
    
  2. 列表配置与自动滚动

    • 使用默认顺序的PagedListView,无需设置reverse。
    • 页面初始化后自动滚动到列表底部,确保用户看到最新消息:
    final _pagingController = PagingController<int, Message>(initialPageKey: null);
    final _scrollController = ScrollController();
    
    @override
    void initState() {
      super.initState();
      _pagingController.addListener(() {
        if (_pagingController.itemList?.isNotEmpty ?? false) {
          WidgetsBinding.instance.addPostFrameCallback((_) {
            _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
          });
        }
      });
    }
    
    @override
    Widget build(BuildContext context) {
      return PagedListView<int, Message>(
        controller: _scrollController,
        pagingController: _pagingController,
        builderDelegate: PagedChildBuilderDelegate<Message>(
          itemBuilder: (context, message, index) => MessageItem(message: message),
        ),
      );
    }
    
  3. 新消息推送处理
    收到WebSocket推送的新消息时,直接追加到列表末尾并滚动到底部:

    void onNewMessageReceived(Message newMessage) {
      final currentList = _pagingController.itemList ?? [];
      _pagingController.itemList = [...currentList, newMessage];
      WidgetsBinding.instance.addPostFrameCallback((_) {
        _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
      });
    }
    

二、自行实现(无需第三方库)

如果需要完全自定义逻辑,用原生ListView.builder+ScrollController即可:

  1. 核心变量定义

    final _scrollController = ScrollController();
    List<Message> _messages = [];
    bool _isLoading = false;
    int? _oldestMessageId; // 记录当前列表最旧消息标识
    
  2. 滚动监听与加载触发
    在initState中添加监听,滚动到顶部附近时触发历史消息加载:

    @override
    void initState() {
      super.initState();
      _scrollController.addListener(() {
        if (_scrollController.position.pixels <= 100 && !_isLoading && _oldestMessageId != null) {
          _loadHistoryMessages();
        }
      });
      _loadLatestMessages(); // 首次加载最新消息
    }
    
  3. 加载逻辑实现

    Future<void> _loadLatestMessages() async {
      _isLoading = true;
      final messages = await api.getLatestMessages(limit: 20);
      _messages = messages;
      _oldestMessageId = messages.isNotEmpty ? messages.first.id : null;
      _isLoading = false;
      setState(() {});
      // 滚动到底部
      WidgetsBinding.instance.addPostFrameCallback((_) {
        _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
      });
    }
    
    Future<void> _loadHistoryMessages() async {
      _isLoading = true;
      setState(() {});
      final historyMessages = await api.getHistoryMessages(
        beforeId: _oldestMessageId,
        limit: 20,
      );
      if (historyMessages.isNotEmpty) {
        _messages.insertAll(0, historyMessages);
        _oldestMessageId = historyMessages.first.id;
      } else {
        _oldestMessageId = null; // 无更多历史消息
      }
      _isLoading = false;
      setState(() {});
    }
    
  4. 列表构建

    @override
    Widget build(BuildContext context) {
      return ListView.builder(
        controller: _scrollController,
        itemCount: _messages.length + (_isLoading ? 1 : 0),
        itemBuilder: (context, index) {
          if (index == 0 && _isLoading) {
            return const Center(child: CircularProgressIndicator());
          }
          final messageIndex = _isLoading ? index - 1 : index;
          return MessageItem(message: _messages[messageIndex]);
        },
      );
    }
    

内容的提问来源于stack exchange,提问作者Bertuz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 00:43:19