-
-
Notifications
You must be signed in to change notification settings - Fork 46
3、聊天会话
只需要三个步骤即可实现聊天会话页的列表效果
- 1、聊天数据不满一屏时,顶部显示所有聊天数据
- 2、插入消息时
- 如果最新消息紧靠列表底部时,则插入消息会使列表向上推
- 如果不是紧靠列表底部,则固定到当前聊天位置
步骤一:初始化必要的 ListObserverController
和 ChatScrollObserver
/// 初始化 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;
}
步骤三:插入或删除消息前,调用 ChatScrollObserver
的 standby
方法
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());
}
});
}
注:该功能依赖被插入消息前的最新消息视图做为参照去计算偏移量,所以如果一次性插入的消息数太多,导致该参照消息视图无法得到渲染,则该功能会失效,需要你自己去对 ScrollView
的 cacheExtent
设置合理的值来尽量避免这个问题!
cacheExtent
的大小计算可以参照如下公式: cacheExtent = item的最大高度 * 一次性插入的消息数量 + 200
chatObserver = ChatScrollObserver(observerController)
..onHandlePositionResultCallback = (result) {
switch (result.type) {
case ChatScrollObserverHandlePositionType.keepPosition:
// 保持当前聊天消息位置
// updateUnreadMsgCount(changeCount: result.changeCount);
break;
case ChatScrollObserverHandlePositionType.none:
// 对聊天消息位置不做处理
// updateUnreadMsgCount(isReset: true);
break;
}
};
该回调的主要作用:在新增聊天消息时,处理新消息未读数气泡的展示
像 ChatGPT
那样不断变化的生成式消息,在翻看旧消息时也需要保持消息位置,你只需要在 standby
方法中调整一下处理模式即可
chatObserver.standby(
mode: ChatScrollObserverHandleMode.generative,
// changeCount: 1,
);
注: 内部会根据 changeCount
来决定参照的 item
,且仅支持生成式消息为连续的情况。
如果你的生成式消息是不连续的,或者同一时间内即有生成式消息更新,又有增加与删除消息的行为,在这种复杂的情况下,则需要你自己指定参照 item
,且这个处理模式更具有灵活性。
chatObserver.standby(
mode: ChatScrollObserverHandleMode.specified,
refIndexType: ChatScrollObserverRefIndexType.relativeIndexStartFromCacheExtent,
refItemIndex: 2,
refItemIndexAfterUpdate: 2,
);
- 设置
mode
为.specified
- 设置更新
前
的参照item
的相对下标 - 设置更新
后
的参照item
的相对下标
注: refItemIndex
与 refItemIndexAfterUpdate
的作用是根据 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,
}
请记住,你的 refItemIndex
和 refItemIndexAfterUpdate
不论你如何设置,它都应该是指向同一个消息对象!