聊天页面
聊天页面
聊天页面组件 ConversationDetail 主要由输入组件、消息列表组件、菜单组件和导航栏组件组成。该组件支持以下功能:
- 发送和接收消息, 包括文本、表情、图片、语音、视频、文件和名片消息。
 - 对消息进行复制、引用、撤回、删除、编辑、重新发送和审核。
 - 清除本地消息。
 - 消息翻译。
 - 消息表情回复。
 - 消息话题。
 - 消息转发。
 
该组件支持多种模式:包括聊天模式、搜索结果显示模式、创建话题模式、话题模式。 通过 ConversationDetailProps.type 属性来区分。详见 ConversationDetailModelType 类型。
消息相关功能,详见功能介绍文档。

示例代码如下:
import type { NativeStackScreenProps } from "@react-navigation/native-stack";
import type { RootParamsName, RootScreenParamsList } from "../routes";
type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { route } = props;
  const name = route.name as RootParamsName;
  // 必须填写的参数
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;
  // 搜索模式
  const messageId = ((route.params as any)?.params as any)?.messageId;
  // 是否是多选模式
  const selectType = ((route.params as any)?.params as any)?.selectType;
  // 话题模式的参数
  const thread = ((route.params as any)?.params as any)?.thread;
  const firstMessage = ((route.params as any)?.params as any)?.firstMessage;
  // 组件模式
  const comType = React.useRef<ConversationDetailModelType>(
    name === "ConversationDetail"
      ? "chat"
      : name === "MessageThreadDetail"
      ? "thread"
      : name === "MessageHistory"
      ? "search"
      : "create_thread"
  ).current;
  return (
    <SafeAreaView
      style={{
        flex: 1,
      }}
    >
      <ConversationDetail
        type={comType}
        convId={convId}
        convType={convType}
        msgId={messageId}
        thread={thread}
        firstMessage={firstMessage}
        selectType={selectType}
      />
    </SafeAreaView>
  );
}
聊天页面的核心属性
ConversationDetail 组件的核心属性介绍如下:
| 属性 | 类型 | 是否必选 | 描述 | 
|---|---|---|---|
| type | ConversationDetailModelType | 是 | 组件模式。包括聊天模式、搜索模式、创建话题模式和话题模式。 | 
| convId | string | 是 | 会话 ID。 | 
| convType | ChatConversationType | 是 | 会话类型。 | 
| convName | string | 否 | 会话名称。 | 
| containerStyle | object | 否 | 修改组件样式。 | 
| input | object | 否 | 输入组件属性、引用属性、自定义组件。详见输入组件介绍。 | 
| list | object | 否 | 消息列表组件属性、引用属性、自定义组件。详见列表组件介绍。 | 
| onClickedAvatar | function | 否 | 点击会话头像的回调。 | 
| NavigationBar | function | 否 | 自定义导航栏组件。 | 
| enableNavigationBar | boolean | 否 | 是否激活导航栏。如果为 false,则不显示导航栏。 | 
| selectType | ConversationSelectModeType | 否 | 选择模式。包括普通模式和多选模式。 | 
| thread | ChatMessageThread | 否 | 话题模式参数。话题对象。 | 
| firstMessage | ChatMessageThread | 否 | 话题模式参数。话题消息对象。 | 
| msgId | string | 否 | 创建话题模式或者搜索模式的参数。话题消息的 ID,或者是搜索模式的消息关键字。 | 
| parentId | string | 否 | 创建话题模式参数。该话题的所在群组 ID。 | 
| newThreadName | string | 否 | 创建话题模式参数。该话题的名称。 | 
| onCreateThreadResult | string | 否 | 创建话题模式参数。创建话题的结果回调通知。 | 
| onClickedThread | function | 否 | 点击消息气泡,打开话题页面的回调通知。例如,进行路由跳转。 | 
| onClickedVoice | function | 否 | 点击导航栏音频按钮的回调通知。例如,进行路由跳转。 | 
| onClickedVideo | function | 否 | 点击导航栏视频按钮的回调通知。例如,进行路由跳转。 | 
| onThreadDestroyed | function | 否 | 话题销毁的回调通知。例如,进行路由跳转。 | 
| onThreadKicked | function | 否 | 离开话题的回调通知。例如,进行路由跳转。 | 
| onForwardMessage | function | 否 | 转发消息的回调通知。例如,进行路由跳转。 | 
自定义导航栏
导航栏组件为通用组件,布局为左中右。自定义方式和方法与会话列表类似,详见会话列表页面的自定义导航栏部分。
输入组件
MessageInput 组件主要发送消息。默认支持发送文本、表情、图片、语音、视频、文件、自定义等消息。还支持发送复合类型消息,例如:引用消息、修改消息、名片消息等。
自定义输入组件
MessageInput 组件提供自定义菜单,可以添加自定义项,实现发送自定义消息。
核心属性如下:
| 属性 | 类型 | 是否必选 | 描述 | 
|---|---|---|---|
| convId | string | 是 | 会话 ID。 | 
| convType | ChatConversationType | 是 | 会话类型。 | 
| selectType | ConversationSelectModeType | 否 | 选择模式。包括普通模式和多选模式。 | 
| top | number | 否 | 若使用了 SafeAreaView 组件,需要设置 top。 | 
| bottom | number | 否 | 若使用了 SafeAreaView 组件,需要设置 bottom。 | 
| numberOfLines | number | 否 | 输入文本组件的最大行数。默认 4 行。 | 
| onClickedSend | function | 否 | 点击发送按钮的回调。 | 
| onEditMessageFinished | function | 否 | 编辑消息完成的回调。 | 
| onClickedCardMenu | function | 否 | 点击名片消息的回调。 | 
| onInitMenu | function | 否 | 注册初始化菜单的回调。 | 
| emojiList | string[] | 否 | 自定义表情列表。如果不设置,使用默认列表。 | 
| multiSelectCount | number | 否 | 多选模式下的选择消息数量。 | 
| onClickedMultiSelectDeleteButton | function | 否 | 点击删除按钮的回调通知。 内部跳转。 | 
| onClickedMultiSelectShareButton | function | 否 | 点击转发按钮的回调通知。内部路由。 | 
| unreadCount | number | 否 | 消息未读数。 | 
| onClickedUnreadCount | function | 否 | 消息未读数的回调通知。 | 
引用对象的核心方法如下:
| 方法 | 描述 | 
|---|---|
| close | 关闭输入组件键盘,切换到一般模式。 | 
| quoteMessage | 回复消息。 | 
| editMessage | 编辑消息。 | 
| showMultiSelect | 打开多选模式。消息列表出现复选框。 | 
| hideMultiSelect | 取消多选模式。消息列表隐藏复选框。 | 
消息列表组件
MessageList 组件主要显示和管理消息列表,支持添加、编辑和删除消息列表项。
- 发送消息会显示在消息列表,消息发送到服务器会修改消息状态,对方已读会修改状态。
 - 发送消息后,在规定时间内可以撤销、编辑和回复。
 - 接收的消息可以设置已读状态,附件类型消息支持附件下载。
 - 消息菜单支持更新或者添加自定义项。
 - 消息列表项支持修改样式、布局和显示隐藏。
 
自定义消息列表组件
核心属性如下:
| 属性 | 类型 | 是否必选 | 描述 | 
|---|---|---|---|
| convId | string | 是 | 会话 ID。 | 
| convType | ChatConversationType | 是 | 会话类型。 | 
| selectType | ConversationSelectModeType | 否 | 选择模式。包括普通模式和多选模式。 | 
| containerStyle | object | 否 | 修改组件样式。 | 
| thread | ChatMessageThread | 否 | 话题模式参数。话题对象。 | 
| msgId | string | 否 | 创建话题模式或者搜索模式的参数。话题消息的 ID,或者是搜索模式的消息关键字。 | 
| parentId | string | 否 | 创建话题模式参数。该话题的所在群组 ID。 | 
| newThreadName | string | 否 | 创建话题模式参数。该话题的名称。 | 
| firstMessage | ChatMessageThread | 否 | 话题模式参数。话题消息对象。 | 
| onClicked | function | 否 | 点击消息列表的回调。 | 
| onClickedItem | function | 否 | 点击消息的回调。 | 
| onLongPressItem | function | 否 | 长按消息的回调。 | 
| onClickedItemAvatar | function | 否 | 点击消息头像的回调。 | 
| onClickedItemQuote | function | 否 | 点击消息的回复的回调。 | 
| onQuoteMessageForInput | function | 否 | 回复消息的回调。 | 
| onEditMessageForInput | function | 否 | 修改消息的回调。 | 
| reportMessageCustomList | array | 否 | 自定义消息举报的否的列表项。 | 
| listItemRenderProps | MessageListItemRenders | 否 | 消息列表项的组件的自定义。还支持内部组件的自定义。 | 
| recvMessageAutoScroll | boolean | 否 | 收到消息是否滚动到列表最下面。 | 
| messageLayoutType | MessageLayoutType | 否 | 消息布局靠左还是靠右。 | 
| onInitMenu | function | 否 | 注册初始化菜单的回调。 | 
| onCopyFinished | function | 否 | 复制完成的回调。 | 
| recvMessageAutoScroll | boolean | 否 | 收到新消息是否自动滚动屏幕消息列表。 | 
| messageLayoutType | MessageLayoutType | 否 | 消息列表是居左还是居右。默认接收消息在左边,发送消息在右边。 | 
| messageLayoutType | MessageLayoutType | 否 | 消息列表是居左还是居右。默认接收消息在左边,发送消息在右边。 | 
| onNoMoreMessage | function | 否 | 已经没有更多消息的回调通知。可能多次通知注意去重。 | 
| onCreateThread | function | 否 | 请求创建话题的回调通知。例如,进行路由跳转。 | 
| onOpenThread | function | 否 | 打开话题的回调通知。例如,进行路由跳转。 | 
| onCreateThreadResult | function | 否 | 创建话题结果的回调通知。例如,进行路由跳转。 | 
| onClickedEditThreadName | function | 否 | 编辑话题名称的回调通知。例如,进行路由跳转。 | 
| onClickedOpenThreadMemberList | function | 否 | 查看话题成员列表的回调通知。例如,进行路由跳转。 | 
| onClickedLeaveThread | function | 否 | 离开话题的回调通知。例如,进行路由跳转。 | 
| onClickedDestroyThread | function | 否 | 销毁话题的回调通知。例如,进行路由跳转。 | 
| onClickedMultiSelected | function | 否 | 点击多选菜单的回调通知。 | 
| onChangeMultiItems | function | 否 | 多选结果的回调通知。 | 
| onClickedSingleSelect | function | 否 | 点击转发的回调通知。 | 
| onClickedHistoryDetail | function | 否 | 点击历史消息的回调通知。例如,进行路由跳转。 | 
| onChangeUnreadCount | function | 否 | 未读数发生变更的回调通知。例如,进行路由跳转。 | 
对象引用的方法如下:
| 方法 | 描述 | 
|---|---|
| addSendMessage | 发送消息。 | 
| addSendMessageToUI | 发送消息到 UI,不会发送到服务器。 | 
| sendMessageToServer | 发送到服务器。 | 
| removeMessage | 移除消息。 | 
| recallMessage | 撤回消息。 | 
| updateMessage | 更新消息。 | 
| loadHistoryMessage | 加载历史消息。 | 
| onInputHeightChange | 输入组件的高度变化。 | 
| editMessageFinished | 编辑消息完成。 | 
| scrollToBottom | 滚动到消息底部。 | 
| startShowThreadMoreMenu | 显示底部话题菜单。 | 
| cancelMultiSelected | 取消多选。 | 
| removeMultiSelected | 移除已经选择的消息。 | 
| getMultiSelectedMessages | 获取已经选择的消息列表。 | 
消息列表的头像和昵称
MessageList 组件内部并没有头像和昵称的默认值,需要用户提供。若未提供,则展示默认头像和用户 ID。
可通过以下方式提供头像和昵称:
- 注册回调:使用 
Container组件的onRequestMultiData属性实现。 - 主动调用:使用 
ChatService.updateDataList方法实现。调用该方法会触发内部事件分发,刷新已加载的组件页面。 - 消息携带:优先使用消息携带的头像和昵称。
 
设置消息列表的背景颜色
type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { route } = props;
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;
  return (
    <ConversationDetail
      type={'chat'}
      convId={convId}
      convType={convType}
      list={{
        props: {
          containerStyle: { backgroundColor: 'red' },
        },
      }}
    />
  );
}
设置消息列表的背景图片
type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { route } = props;
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;
  return (
    <ConversationDetail
      type={'chat'}
      convId={convId}
      convType={convType}
      list={{
        props: {
          backgroundImage: 'https://img.yzcdn.cn/vant/cat.jpeg',
        },
      }}
    />
  );
}
自定义消息时间戳
设置消息气泡下面的时间戳,需要在初始化部分进行。 示例代码如下:
export function App() {
  const { getOptions } = useApp();
  return (
    <UIKitContainer
      options={getOptions()}
      formatTime={{
        locale: enAU,
        conversationDetailCallback(timestamp, enAU) {
          return format(timestamp, 'yyyy-MM-dd HH:mm:ss', { locale: enAU });
        },
      }}
    >
      {/* sub component */}
    </UIKitContainer>
  );
}
自定义消息列表项样式
对于消息列表项,可以设置头像、昵称、气泡布局、样式、事件等。
export function MyMessageContent(props: MessageContentProps) {
  const { msg, layoutType, isSupport, contentMaxWidth } = props;
  if (msg.body.type === ChatMessageType.TXT) {
    // todo: 如果是文本类型消息,则使用该样式进行显示。
    return (
      <MessageText
        msg={msg}
        layoutType={layoutType}
        isSupport={isSupport}
        maxWidth={contentMaxWidth}
      />
    );
  }
  return <MessageContent {...props} />;
}
type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { route } = props;
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;
  return (
    <ConversationDetail
      type={'chat'}
      convId={convId}
      convType={convType}
      list={{
        props: {
          listItemRenderProps: {
            MessageContent: MyMessageContent,
          },
        },
      }}
    />
  );
}
自定义消息列表项,例如:隐藏左边消息的头像。
其它自定义的内容,可以参考 MessageViewProps 属性。
export function MyMessageView(props: MessageViewProps) {
  if (props.model.layoutType === 'left') {
    // todo: 如果是左边的消息,则不显示头像
    return <MessageView {...props} avatarIsVisible={false} />;
  }
  return MessageView(props);
}
type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { route } = props;
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;
  return (
    <ConversationDetail
      type={'chat'}
      convId={convId}
      convType={convType}
      list={{
        props: {
          listItemRenderProps: {
            MessageView: MyMessageView,
          },
        },
      }}
    />
  );
}
自定义消息上下文菜单样式
messageMenuStyle 支持三种模式: bottom-sheet、context 和 custom。bottom-sheet 模式通过页面组件底部弹出菜单,context 模式类似微信,通过消息位置和点击位置弹出菜单,custom 模式通过用户自定组件实现,需要遵守 MessageCustomLongPressMenu 约束。
custom 模式示例代码如下:
- 在全局设置属性 
Container.messageMenuStyle为custom。其他模式不需要用户设置MessageCustomLongPressMenu。 
export function App() {
  return (
    <UIKitContainer messageMenuStyle={'custom'}>
      {/* sub component */}
    </UIKitContainer>
  );
}
- 在 
ConversationDetail组件中设置属性MessageCustomLongPressMenu。 
export const MyMessageContextNameMenu = React.forwardRef<
  ContextNameMenuRef,
  ContextNameMenuProps
>(function (
  props: ContextNameMenuProps,
  ref?: React.ForwardedRef<ContextNameMenuRef>
) {
  const {} = props;
  React.useImperativeHandle(
    ref,
    () => {
      return {
        startShow: () => {},
        startHide: (_onFinished?: () => void) => {},
        startShowWithInit: (_initItems: InitMenuItemsType[], _?: any) => {},
        startShowWithProps: (_props: ContextNameMenuProps) => {},
        getData: () => {
          return undefined;
        },
      };
    },
    []
  );
  ref;
  return <View style={{ width: 100, height: 44, backgroundColor: 'red' }} />;
});
type Props = NativeStackScreenProps<RootScreenParamsList>;
export function MyConversationDetailScreen(props: Props) {
  const { route } = props;
  return (
    <SafeAreaViewFragment>
      <ConversationDetail
        MessageCustomLongPressMenu={MyMessageContextNameMenu}
      />
    </SafeAreaViewFragment>
  );
}


自定义发送消息附件菜单样式
messageInputBarStyle 支持两种模式: bottom-sheet 和 extension。bottom-sheet 模式通过页面组件底部弹出菜单, extension 模式通过布局组件实现。
在全局通过设置属性 Container.messageInputBarStyle 属性决定菜单样式。
export function App() {
  return (
    <UIKitContainer messageInputBarStyle={'extension'}>
      {/* sub component */}
    </UIKitContainer>
  );
}


事件通知
事件通知在列表中已经实现,收到对应事件会更新列表。通常情况下,不需要开发者关注。
示例应用
实现的核心是,自定义输入组件的菜单,点击自定义项发送自定义消息,自定义渲染组件来显示自定义消息。
示例如下:
export function MyMessageContent(props: MessageContentProps) {
  const { msg } = props;
  if (msg.body.type === ChatMessageType.CUSTOM) {
    return (
      <View>
        <Text>{(msg.body as ChatCustomMessageBody).params?.test}</Text>
      </View>
    );
  }
  return <MessageContent {...props} />;
}
type Props = NativeStackScreenProps<RootScreenParamsList>;
export function ConversationDetailScreen(props: Props) {
  const { navigation, route } = props;
  const convId = ((route.params as any)?.params as any)?.convId;
  const convType = ((route.params as any)?.params as any)?.convType;
  const listRef = React.useRef<MessageListRef>({} as any);
  const inputRef = React.useRef<MessageInputRef>({} as any);
  const { top, bottom } = useSafeAreaInsets();
  const im = useChatContext();
  return (
    <SafeAreaView
      style={{
        flex: 1,
      }}
    >
      <ConversationDetail
        type={"chat"}
        convId={convId}
        convType={convType}
        input={{
          ref: inputRef,
          props: {
            top,
            bottom,
            onInitMenu: (menu) => {
              return [
                ...menu,
                {
                  name: "test",
                  isHigh: false,
                  icon: "bell",
                  onClicked: () => {
                    listRef.current?.addSendMessage({
                      type: "custom",
                      msg: ChatMessage.createCustomMessage(convId, "test", 1, {
                        params: { test: "111" },
                      }),
                    });
                  },
                },
              ];
            },
          },
        }}
        list={{
          ref: listRef,
          props: {
            listItemRenderProps: {
              MessageContent: MyMessageContent,
            },
          },
        }}
      />
    </SafeAreaView>
  );
}
