Feedback

Android(Compose)

组件概述

MessageList 是一个基于 Jetpack Compose 构建的消息列表组件,主要用于展示和管理聊天消息。该组件提供了完整的消息类型展示功能:
消息类型支持:文本、图片、视频、语音、文件、表情、合并转发、通话记录、系统提示等消息,也支持通过自定义渲染器展示业务自定义消息。
消息操作:支持长按菜单操作,例如复制、转发、引用、多选、删除、撤回、语音转文字、翻译、贴表情,以及自定义操作。
高度可定制:支持多种样式配置(例如对齐方式、头像昵称、间距、列表背景、气泡外观),以及消息过滤、自定义消息渲染和自定义操作项。
聊天消息列表
消息长按菜单



组件集成

MessageList 是属于 TUIKit Compose 的一个组件,集成 TUIKit Compose 即可获取并使用 MessageList,集成方法请参考文档 TUIKit Compose 里的集成步骤。

组件构成

MessageList 组件对外暴露了消息列表的初始化方法,以及用于在外部控制消息列表的 MessageListController,其他逻辑均封装在 MessageList 中。

对外方法

方法名
参数
描述
MessageList
conversationID: String
初始化组件,设置会话 ID。如果是单聊,组成方式为 c2c_userID。如果是群聊,组成方式为 group_groupID。
modifier: Modifier
Jetpack Compose 中用来设置组件的样式、布局、行为和外观。
config: MessageListConfigProtocol
初始化组件,设置消息列表的样式、操作项、消息过滤和自定义渲染规则,默认值为 ChatMessageListConfig()。
customActions: List<MessageCustomAction>
初始化组件,设置自定义消息菜单选项,追加在默认操作项之后。
locateMessage: MessageInfo?
初始化组件,设置定位消息。通常用于从消息搜索结果页跳转消息页同时定位到目标消息。
messageListViewModelFactory: MessageListViewModelFactory?
创建组件内部 MessageListViewModel 的工厂类。通常情况下,您无需手动创建,组件已提供默认实现。
controller: MessageListController?
消息列表控制器,选填。通过 rememberMessageListController() 创建后传入,可在外部退出多选模式、发送“正在输入”状态等,详见下方 MessageListController。
onTypingStatusChanged: (Boolean) -> Unit
对方“正在输入”状态变化回调,true 表示对方正在输入。仅在单聊且 config 的 enableTyping 为 true 时生效。
onMultiSelectStateChanged: (Boolean) -> Unit
多选模式状态变化回调,true 表示进入多选模式,false 表示退出多选模式。
onUserClick: (String) -> Unit
初始化组件,设置点击用户头像跳转响应事件,回调参数为被点击用户的 userID。

MessageListController

通过 rememberMessageListController() 创建 MessageListController,并传给 MessageList 的 controller 参数后,可以在外部控制消息列表:
方法/属性
描述
exitMultiSelectMode()
退出多选模式。
isInMultiSelectMode(): Boolean
当前是否处于多选模式。
sendTypingStatus(isTyping: Boolean)
向对方发送自己的“正在输入”状态,通常与 MessageInput 的 typingStatusSender 配合使用。仅在单聊且 enableTyping 为 true 时生效;为减少无效消息,只有对方在最近 30 秒内给您发过消息时才会发送,并且有频率限制。
typingState: StateFlow<Boolean>
对方是否正在输入,效果与 onTypingStatusChanged 回调相同。

基础用法

MessageList 组件可直接初始化使用,需要提供会话 ID。除此之外,我们还建议您在初始化方法中实现 onUserClick。
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import io.trtc.tuikit.chat.uikit.components.messagelist.ui.MessageList

@Composable
fun MessageListScreen(conversationID: String, onOpenUserProfile: (userID: String) -> Unit) {
Box(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
) {
MessageList(conversationID = conversationID) { userID ->
// 点击用户头像响应事件
onOpenUserProfile(userID)
}
}
}

与 MessageInput 组合使用

MessageList 只负责展示消息,通常与 MessageInput 组合成聊天页面。下面的示例通过 MessageListController 让两个组件协同工作:多选模式下隐藏输入框、按返回键退出多选,以及显示“对方正在输入”提示。
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.trtc.tuikit.chat.uikit.components.messageinput.ui.MessageInput
import io.trtc.tuikit.chat.uikit.components.messagelist.ui.MessageList
import io.trtc.tuikit.chat.uikit.components.messagelist.ui.rememberMessageListController

@Composable
fun ChatScreen(conversationID: String) {
val messageListController = rememberMessageListController()
var isPeerTyping by remember { mutableStateOf(false) }
var isMultiSelectMode by remember { mutableStateOf(false) }

// 多选模式下,按返回键退出多选
BackHandler(enabled = isMultiSelectMode) {
messageListController.exitMultiSelectMode()
}

Column(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
) {
if (isPeerTyping) {
Text(text = "对方正在输入...", modifier = Modifier.padding(16.dp))
}
MessageList(
conversationID = conversationID,
modifier = Modifier.weight(1f),
controller = messageListController,
onTypingStatusChanged = { isTyping -> isPeerTyping = isTyping },
onMultiSelectStateChanged = { isMultiSelect -> isMultiSelectMode = isMultiSelect },
onUserClick = { userID ->
// 点击用户头像响应事件
}
)
// 多选模式下隐藏输入框
if (!isMultiSelectMode) {
MessageInput(
conversationID = conversationID,
typingStatusSender = { isTyping ->
// 把自己的输入状态发送给对方
messageListController.sendTypingStatus(isTyping)
}
)
}
}
}
说明:
示例工程(demo)中提供了基于 ChatPage 封装好的聊天页 ChatActivity(带标题栏、未读角标、群事件处理等),可直接复用或参考其实现进行自定义:ChatActivity.start(context, conversationID)。

自定义组件

您可以自定义消息列表的样式、操作项、需要展示的消息以及自定义消息的渲染方式。方法如下。

自定义样式

推荐使用 ChatMessageListConfig 设置样式。ChatMessageListConfig 的构造参数均为可选的命名参数,未设置的属性使用默认值:
import androidx.compose.ui.unit.dp
import io.trtc.tuikit.chat.uikit.components.config.MessageAlignment
import io.trtc.tuikit.chat.uikit.components.messagelist.config.ChatMessageListConfig
import io.trtc.tuikit.chat.uikit.components.messagelist.ui.MessageList

val messageListConfig = ChatMessageListConfig(
alignment = MessageAlignment.TWO_SIDED, // 自己发送的消息在右侧,其他人的消息在左侧
isShowRightAvatar = false, // 不显示右侧(自己发送的消息)的头像
isShowTimeMessage = false, // 不显示时间分隔
cellSpacing = 16.dp, // 消息之间的间距
horizontalPadding = 12.dp // 消息列表的左右边距
)

MessageList(conversationID = conversationID, config = messageListConfig) { userID ->
// 点击用户头像响应事件
}
ChatMessageListConfig 中与样式相关的属性说明如下,操作项相关的开关请参见“自定义操作项”:
属性名
类型
描述
备注
alignment
MessageAlignment
消息对齐方式
MessageAlignment.LEFT:所有消息左对齐。
MessageAlignment.RIGHT:所有消息右对齐。
MessageAlignment.TWO_SIDED:自己发送的消息右对齐,其他人的消息左对齐。
默认值为 TWO_SIDED。
isShowTimeMessage
Boolean
是否显示时间消息
控制是否在消息之间显示时间分隔。默认值为 true。
isShowLeftAvatar
Boolean
是否显示左侧头像
控制显示在左侧的消息是否显示头像,TWO_SIDED 对齐时即其他人发送的消息。默认值为 true。
isShowLeftNickname
Boolean
是否显示左侧昵称
控制显示在左侧的消息是否显示昵称。默认值为 true。
isShowRightAvatar
Boolean
是否显示右侧头像
控制显示在右侧的消息是否显示头像,TWO_SIDED 对齐时即自己发送的消息。默认值为 true。
isShowRightNickname
Boolean
是否显示右侧昵称
控制显示在右侧的消息是否显示昵称。默认值为 true。
cellSpacing
Dp
消息间距
控制相邻消息之间的间距。默认值为 10.dp。
isShowSystemMessage
Boolean
是否显示系统消息
控制是否显示群提示消息和撤回提示。默认值为 true。
isShowUnsupportMessage
Boolean
是否显示不支持的消息
控制是否显示组件无法识别、只能以默认样式展示的消息。默认值为 true。
horizontalPadding
Dp
水平内边距
控制消息列表的左右内边距。默认值为 16.dp。
avatarSpacing
Dp
头像间距
控制头像与消息内容之间的间距。默认值为 8.dp。
isShowReadReceipt
Boolean
是否显示已读回执
控制自己发送的、需要已读回执的消息是否显示已读状态。消息需要由开启了 enableReadReceipt 的 MessageInput 发送。默认值为 false。
enableTyping
Boolean
是否启用“正在输入”功能
仅单聊生效,需要配合 MessageListController 使用。默认值为 true。
background
MessageListBackground?
消息列表背景
默认值为 null,详见下方“列表背景”。
defaultBubbleAppearance、leftBubbleAppearance、rightBubbleAppearance、ownBubbleAppearance、incomingBubbleAppearance
MessageBubbleAppearance?
消息气泡外观
默认值均为 null,详见下方“气泡外观”。
昵称的显示还受会话类型和对齐方式影响:其他人发送的消息仅在群聊中显示昵称,自己发送的消息仅在 alignment 不为 TWO_SIDED 时显示昵称。

列表背景

通过 setBackgroundColor、setBackgroundGradient、setBackgroundImage 设置消息列表的背景,clearBackground 可清除设置:
import io.trtc.tuikit.chat.uikit.components.messagelist.config.ChatMessageListConfig
import io.trtc.tuikit.chat.uikit.components.messagelist.config.MessageListBackground

// 纯色背景
val colorBackgroundConfig = ChatMessageListConfig()
.setBackgroundColor(0xFFF5F6F8.toInt())

// 渐变背景,至少需要 2 个颜色
val gradientBackgroundConfig = ChatMessageListConfig()
.setBackgroundGradient(
colors = intArrayOf(0xFFE8F0FE.toInt(), 0xFFFFFFFF.toInt()),
direction = MessageListBackground.GradientDirection.TOP_BOTTOM
)

// 图片背景
val imageBackgroundConfig = ChatMessageListConfig()
.setBackgroundImage("https://example.com/chat_background.png")
setBackgroundImage 的参数会交给 Coil 加载,支持网络图片 URL、本地文件、Uri、drawable 资源 ID 等。
渐变方向 GradientDirection 支持 TOP_BOTTOM、BOTTOM_TOP、LEFT_RIGHT、RIGHT_LEFT、TOP_LEFT_BOTTOM_RIGHT、BOTTOM_RIGHT_TOP_LEFT,默认为 TOP_BOTTOM。
背景的生效优先级为:config 中设置的背景 > 用户在聊天设置中为该会话选择的聊天背景 > 默认背景色。

气泡外观

通过 setDefaultBubbleAppearance、setLeftBubbleAppearance、setRightBubbleAppearance、setOwnBubbleAppearance、setIncomingBubbleAppearance 设置消息气泡的外观,对应的 clearXxxBubbleAppearance 方法可清除设置。多个外观按“默认 → 左侧/右侧 → 自己/他人”的顺序逐个字段合并,后者中设置了的字段会覆盖前者,圆角按每个角单独合并:
import io.trtc.tuikit.chat.uikit.components.messagelist.config.ChatMessageListConfig
import io.trtc.tuikit.chat.uikit.components.messagelist.config.MessageBubbleAppearance
import io.trtc.tuikit.chat.uikit.components.messagelist.config.MessageBubbleBackground
import io.trtc.tuikit.chat.uikit.components.messagelist.config.MessageBubbleCornerRadius
import io.trtc.tuikit.chat.uikit.components.messagelist.config.MessageBubbleStroke

val bubbleConfig = ChatMessageListConfig()
// 所有气泡的基础外观:四个角的圆角均为 12dp
.setDefaultBubbleAppearance(
MessageBubbleAppearance(
cornerRadius = MessageBubbleCornerRadius.all(12)
)
)
// 自己发送的消息:渐变背景,右下角为直角,其余圆角沿用基础外观
.setOwnBubbleAppearance(
MessageBubbleAppearance(
background = MessageBubbleBackground.Gradient(
colors = listOf(0xFF4F8BFF.toInt(), 0xFF1C66E5.toInt()),
direction = MessageBubbleBackground.GradientDirection.LEFT_RIGHT
),
cornerRadius = MessageBubbleCornerRadius(bottomRight = 0)
)
)
// 收到的消息:白色背景,1dp 描边
.setIncomingBubbleAppearance(
MessageBubbleAppearance(
background = MessageBubbleBackground.Color(0xFFFFFFFF.toInt()),
stroke = MessageBubbleStroke(width = 1, color = 0xFFE5E6EB.toInt())
)
)
MessageBubbleAppearance 的字段说明如下,尺寸参数的单位均为 dp,颜色参数为 ARGB 格式的 Int 值:
字段
类型
描述
background
MessageBubbleBackground?
气泡背景,支持 Color(纯色)、Gradient(渐变,至少 2 个颜色)、DrawableResource(drawable 资源 ID)、DrawableValue(Drawable 对象)。
cornerRadius
MessageBubbleCornerRadius?
四个角的圆角半径(topLeft、topRight、bottomRight、bottomLeft),可以通过 MessageBubbleCornerRadius.all(radius) 统一设置。
stroke
MessageBubbleStroke?
描边的宽度和颜色。
contentInsets
MessageBubbleInsets?
气泡与消息内容之间的内边距,在消息内容自身边距的基础上额外增加。可以通过 MessageBubbleInsets.all(value)、MessageBubbleInsets.symmetric(horizontal, vertical) 快速创建。
minimumSize
MessageBubbleSize?
气泡的最小宽度和最小高度。
气泡外观仅作用于使用默认气泡的消息,例如文本、语音、文件、通话记录消息,以及 renderConfig.useDefaultBubble 为 true 的自定义渲染器。图片、视频、表情消息默认不带气泡,仅在带有表情回应时包裹一层默认气泡。

自定义操作项

您可以采用多种方式自定义长按消息菜单项。默认操作项依次为:复制、转发、引用、多选、删除、撤回、转文字、翻译、从当前听,菜单末尾还有“贴表情”入口。每个操作项只在适用的消息上展示,具体条件见方式 1 中的表格。

方式 1. 隐藏/显示操作项

初始化 MessageList 时,通过设置 config 中的 isSupportXxx 开关控制操作项是否显示:
import io.trtc.tuikit.chat.uikit.components.messagelist.config.ChatMessageListConfig
import io.trtc.tuikit.chat.uikit.components.messagelist.ui.MessageList

MessageList(
conversationID = conversationID,
config = ChatMessageListConfig(isSupportCopy = false) // 不展示“复制”
) { userID ->
// 点击用户头像响应事件
}
目前支持的操作项开关
开关
对应操作项 ID
描述
isSupportCopy
MessageActionIDs.COPY
复制,仅文本消息展示。默认值取决于全局配置。
isSupportForward
MessageActionIDs.FORWARD
转发,仅发送成功的消息展示。默认值为 true。
isSupportQuote
MessageActionIDs.QUOTE
引用,仅发送成功的消息展示。默认值取决于全局配置。
isSupportMultiSelect
MessageActionIDs.MULTI_SELECT
多选,点击后进入多选模式。默认值为 true。
isSupportDelete
MessageActionIDs.DELETE
删除,点击后会先弹出确认框。默认值取决于全局配置。
isSupportRecall
MessageActionIDs.RECALL
撤回,仅自己发送成功、且发送时间在 2 分钟以内的消息展示。默认值取决于全局配置。
isSupportConvertToText
MessageActionIDs.CONVERT_TO_TEXT
转文字,仅发送成功、且当前未显示转写结果的语音消息展示。默认值为 true。
isSupportTranslate
MessageActionIDs.TRANSLATE
翻译,仅发送成功、且当前未显示译文的文本消息展示。默认值为 true。
isSupportListenFromHere
MessageActionIDs.LISTEN_FROM_HERE
从当前听,从该消息开始依次朗读或播放到最新一条消息。默认值为 true。
isSupportReaction
无
表情回应。开启后,发送成功的消息的长按菜单末尾会展示“贴表情”入口,消息下方会展示表情回应。该入口不属于可编辑的操作项。默认值为 true。

方式 2. 增加操作项

初始化 MessageList 时传入 customActions,MessageList 内部会在默认操作项之后(“贴表情”入口之前)添加您增加的自定义选项:
import io.trtc.tuikit.chat.uikit.components.messagelist.model.MessageCustomAction
import io.trtc.tuikit.chat.uikit.components.messagelist.ui.MessageList

MessageList(
conversationID = conversationID,
customActions = listOf(
MessageCustomAction(
ID = "custom.message.share",
title = "Share",
iconResID = android.R.drawable.ic_menu_share,
action = { message ->
println("Share Message: ${message.msgID}")
}
)
)
)
MessageCustomAction 的字段说明如下。由于 action 不是最后一个参数,请以 action = { } 命名参数的方式传入:
字段
类型
描述
ID
String
操作项的唯一标识,必填。
title
String
操作项标题。
iconResID
Int
操作项图标的 drawable 资源 ID,为 0 时不显示图标。图标会按主题的文字颜色着色。
action
(MessageInfo) -> Unit
点击操作项时的回调,参数为当前长按的消息。
dangerous
Boolean
是否为危险操作,默认为 false。目前不影响长按菜单的显示样式。

方式 3. 局部编辑操作菜单

如需调整操作项的顺序、修改默认操作项,或者根据消息动态增删操作项,可以通过 ChatMessageListConfig 的 customizeActions 编辑长按菜单。每次长按消息时,组件会先根据开关和消息状态生成默认操作项,再执行 customizeActions 中的代码,然后追加 customActions 中的操作项,最后根据 isSupportReaction 追加“贴表情”入口。
在 customizeActions 中,可以通过 editorContext.message 获取当前长按的消息,通过 editorContext.conversationID 获取会话 ID,通过 editorContext.androidContext 获取 Android Context,并调用以下方法编辑操作项:
方法
描述
add(item)
在末尾添加操作项。item 的 ID 为空白或与已有操作项重复时会抛出 IllegalArgumentException。
remove(ID)
移除指定 ID 的操作项,不存在时返回 false。
replace(ID) { item -> ... }
替换指定 ID 的操作项,不存在时返回 false。替换后的操作项必须保持 ID 不变,否则会抛出异常。
insertBefore(anchorID, item) / insertAfter(anchorID, item)
在锚点操作项之前或之后插入操作项。锚点不存在时返回 false,且不会插入。item 的 ID 规则与 add 相同。
moveBefore(ID, anchorID) / moveAfter(ID, anchorID)
将操作项移动到锚点操作项之前或之后,任意一个 ID 不存在时返回 false。
clear()
移除全部操作项。
items
当前操作项列表的只读快照。
示例代码如下:
import io.trtc.tuikit.atomicxcore.api.message.MessageType
import io.trtc.tuikit.chat.uikit.components.messagelist.config.ChatMessageListConfig
import io.trtc.tuikit.chat.uikit.components.messagelist.model.MessageActionIDs
import io.trtc.tuikit.chat.uikit.components.messagelist.model.MessageCustomAction

val editActionsConfig = ChatMessageListConfig().customizeActions {
// 当前长按的消息
val message = editorContext.message
// 移除“多选”
remove(MessageActionIDs.MULTI_SELECT)
// 修改“删除”的标题。ID 保持不变,点击后仍会先弹出确认框
replace(MessageActionIDs.DELETE) { action ->
action.copy(title = "删除消息")
}
// 文本消息增加“收藏”
if (message.messageType == MessageType.TEXT) {
val favoriteAction = MessageCustomAction(
ID = "custom.message.favorite",
title = "收藏",
iconResID = android.R.drawable.star_off,
action = { target ->
// 收藏消息 target
}
)
// 放在“转发”之后;当前消息没有“转发”(例如发送失败)时,追加到末尾
if (!insertAfter(MessageActionIDs.FORWARD, favoriteAction)) {
add(favoriteAction)
}
}
}

MessageList(conversationID = conversationID, config = editActionsConfig)
自定义效果如下图所示:
消息列表操作(默认选项)
消息列表操作(隐藏复制选项)
消息列表操作(新增分享选项)