Skip to content

3、聊天会话

LinXunFeng edited this page May 20, 2024 · 5 revisions

3.1、基本使用

只需要三个步骤即可实现聊天会话页的列表效果

  • 1、聊天数据不满一屏时,顶部显示所有聊天数据
  • 2、插入消息时
    • 如果最新消息紧靠列表底部时,则插入消息会使列表向上推
    • 如果不是紧靠列表底部,则固定到当前聊天位置

步骤一:初始化必要的 ListObserverControllerChatScrollObserver

/// 初始化 ListObserverController
observerController = ListObserverController(controller: scrollController)
  ..cacheJumpIndexOffset = false;

/// 初始化 ChatScrollObserver
chatObserver = ChatScrollObserver(observerController)
  // 超过该偏移量才会触发保持当前聊天位置的功能
  ..fixedPositionOffset = 5
  ..toRebuildScrollViewCallback = () {
    // 这里可以重建指定的滚动视图即可
    setState(() {});
  };

步骤二:按如下配置 ListView 并使用 ListViewObserver 将其包裹

Widget _buildListView() {
  Widget resultWidget = ListView.builder(
    physics: ChatObserverClampinScrollPhysics(observer: chatObserver),
    shrinkWrap: chatObserver.isShrinkWrap,
    reverse: true,
    controller: scrollController,
    ...
  );

  resultWidget = ListViewObserver(
    controller: observerController,
    child: resultWidget,
  );
  return resultWidget;
}

步骤三:插入或删除消息前,调用 ChatScrollObserverstandby 方法

onPressed: () {
  chatObserver.standby();
  setState(() {
    chatModels.insert(0, ChatDataHelper.createChatModel());
  });
},
...
onRemove: () {
  chatObserver.standby(isRemove: true);
  setState(() {
    chatModels.removeAt(index);
  });
},

默认只处理插入一条消息的情况,如果你需要一次性插入多条,那 standby 需要传入 changeCount 参数

_addMessage(int count) {
  chatObserver.standby(changeCount: count);
  setState(() {
    needIncrementUnreadMsgCount = true;
    for (var i = 0; i < count; i++) {
      chatModels.insert(0, ChatDataHelper.createChatModel());
    }
  });
}

注:该功能依赖被插入消息前的最新消息视图做为参照去计算偏移量,所以如果一次性插入的消息数太多,导致该参照消息视图无法得到渲染,则该功能会失效,需要你自己去对 ScrollViewcacheExtent 设置合理的值来尽量避免这个问题!

cacheExtent 的大小计算可以参照如下公式: cacheExtent = item的最大高度 * 一次性插入的消息数量 + 200

3.2、聊天消息位置的处理回调

chatObserver = ChatScrollObserver(observerController)
  ..onHandlePositionResultCallback = (result) {
    switch (result.type) {
      case ChatScrollObserverHandlePositionType.keepPosition:
        // 保持当前聊天消息位置
        // updateUnreadMsgCount(changeCount: result.changeCount);
        break;
      case ChatScrollObserverHandlePositionType.none:
        // 对聊天消息位置不做处理
        // updateUnreadMsgCount(isReset: true);
        break;
    }
  };

该回调的主要作用:在新增聊天消息时,处理新消息未读数气泡的展示

3.3、生成式消息保持位置

ChatGPT 那样不断变化的生成式消息,在翻看旧消息时也需要保持消息位置,你只需要在 standby 方法中调整一下处理模式即可

chatObserver.standby(
  mode: ChatScrollObserverHandleMode.generative,
  // changeCount: 1,
);

注: 内部会根据 changeCount 来决定参照的 item,且仅支持生成式消息为连续的情况。

3.4、指定参照的消息

如果你的生成式消息是不连续的,或者同一时间内即有生成式消息更新,又有增加与删除消息的行为,在这种复杂的情况下,则需要你自己指定参照 item,且这个处理模式更具有灵活性。

chatObserver.standby(
  mode: ChatScrollObserverHandleMode.specified,
  refIndexType: ChatScrollObserverRefIndexType.relativeIndexStartFromCacheExtent,
  refItemIndex: 2,
  refItemIndexAfterUpdate: 2,
);
  1. 设置 mode.specified
  2. 设置更新 的参照 item 的相对下标
  3. 设置更新 的参照 item 的相对下标

注: refItemIndexrefItemIndexAfterUpdate 的作用是根据 refIndexType 的值而有所不同,如下所示

enum ChatScrollObserverRefIndexType {
  ///     relativeIndex        trailing
  ///
  ///           6         |     item16    | cacheExtent
  ///   ----------------- -----------------
  ///           5         |     item15    |
  ///           4         |     item14    |
  ///           3         |     item13    | displaying
  ///           2         |     item12    |
  ///           1         |     item11    |
  ///   ----------------- -----------------
  ///           0         |     item10    | cacheExtent <---- start
  ///
  ///                          leading
  relativeIndexStartFromCacheExtent,

  ///     relativeIndex        trailing
  ///
  ///           5         |     item16    | cacheExtent
  ///   ----------------- -----------------
  ///           4         |     item15    |
  ///           3         |     item14    |
  ///           2         |     item13    | displaying
  ///           1         |     item12    |
  ///           0         |     item11    |             <---- start
  ///   ----------------- -----------------
  ///          -1         |     item10    | cacheExtent
  ///
  ///                          leading
  relativeIndexStartFromDisplaying,

  /// 直接指定 item 的下标.
  itemIndex,
}

请记住,你的 refItemIndexrefItemIndexAfterUpdate 不论你如何设置,它都应该是指向同一个消息对象!