消息列表的基本设置

大约 9 分钟

消息列表的基本设置

MessageList 组件负责消息的展示与管理,支持消息发送、编辑、删除及自定义交互:

  • 消息展示:文本、图片、语音、视频、文件、名片等类型。
  • 消息状态:发送中、已发送、已读、失败等状态实时更新。
  • 消息操作:复制、引用、撤回、编辑、删除、重发。
  • 附件管理:图片预览、语音播放、视频播放、文件下载。
  • 菜单扩展:支持添加或修改长按菜单项。
  • 样式定制:支持自定义消息气泡、头像、时间等样式。

概述

消息列表组件 MessageList 支持设置基本属性,如下表所示:

属性类型是否必需描述
typeConversationDetailModelType消息列表的类型。
- chat:普通聊天页面。
- create_thread:创建话题页面。
- thread:话题聊天页面。
- search:搜索消息页面。
convIdstring会话 ID。
convTypeChatConversationType会话类型。
containerStyleStyleProp<ViewStyle>消息列表容器的样式。详见 设置容器样式
onClickedItem(id, model) => void | boolean点击消息回调。返回 false 可阻止默认行为。详见 点击消息条目
onLongPressItem(id, model) => void | boolean长按消息回调。返回 false 可阻止默认行为。详见 长按消息条目
onClickedItemAvatar(id, model) => void | boolean点击头像的回调。返回 false 可阻止默认行为。详见 点击消息头像
onClickedItemQuote(id, model) => void | boolean点击引用消息的回调。返回 false 可阻止默认行为。详见 点击引用消息
recvMessageAutoScrollboolean收到新消息时是否自动滚动到底部。
- true:自动滚动。
- false(默认):不自动滚动。
详见 自动滚动设置
messageLayoutTypeMessageLayoutType消息布局类型。
- left:全部消息都在左侧。
- right:全部消息都在右侧。
- 不设置(默认):发送的消息在右侧,接收的消息在左侧。
详见 消息布局类型
reportMessageCustomList{key: string, value: string}[]自定义消息举报内容列表。详见 自定义消息上报
listItemRenderPropsMessageListItemRenders & {ListItemRender}自定义消息条目渲染组件。
onInitMenu(initItems) => InitMenuItemsType[]初始化长按菜单回调,可以添加、修改或删除菜单项。
onCopyFinished(content: string) => void复制完成回调。
onNoMoreMessage() => void无更多历史消息回调。
onCreateThread(params) => void请求创建话题时的回调。
onOpenThread(thread) => void打开话题时的回调。
onCreateThreadResult(thread?, firstMessage?) => void创建话题结果的回调。
onClickedEditThreadName(thread) => void点击编辑话题名称时的回调(话题模式下)。
onClickedOpenThreadMemberList(thread) => void点击打开话题成员列表时的回调(话题模式下)。
onClickedLeaveThread(threadId) => void点击离开话题时的回调(话题模式下)。
onClickedDestroyThread(threadId) => void点击销毁话题时的回调(话题模式下)。
onClickedHistoryDetail(item) => void点击历史消息详情时的回调。
generateThreadName(msg: MessageModel) => string生成话题名称的回调函数。
MessageCustomLongPressMenuReact.ForwardRefExoticComponent<...>自定义消息长按菜单组件。
threadChatMessageThread话题对象(话题模式下需要)。
msgIdstring消息 ID(创建话题模式下需要)。
parentIdstring父级 ID(创建话题模式下需要)。
newThreadNamestring新话题名称(创建话题模式下需要)。
firstMessageSendMessageProps第一条消息(话题模式下需要)。

提示

表格中未列出的一些属性(如 onClickedonQuoteMessageForInputonEditMessageForInputonClickedMultiSelectedonChangeMultiItemsonClickedSingleSelectonChangeUnreadCountonChangePinMaskHeightonRequestClosePinMessage 等)主要用于内部组件之间通信,一般用户无需关注。

对象引用的方法如下表所示:

方法描述
addSendMessage发送消息。
addSendMessageToUI仅添加到 UI,不发送服务器。
sendMessageToServer发送消息到服务器。
removeMessage移除消息。
recallMessage撤回消息。
updateMessage更新消息。
loadHistoryMessage加载历史消息。
onInputHeightChange输入组件的高度变化。
editMessageFinished编辑消息完成。
scrollToBottom滚动到底部。
startShowThreadMoreMenu显示底部话题菜单。
cancelMultiSelected取消多选。
removeMultiSelected移除已选的消息。
getMultiSelectedMessages获取已选消息列表。

设置消息列表的背景色

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',
        },
      }}
    />
  );
}

设置消息条目

消息条目的界面效果如下图所示:

设置头像和昵称

MessageList 组件 不内置默认头像和昵称,需通过用户信息提供机制进行配置:

  • 未配置时:头像显示默认占位图,昵称显示用户 ID。
  • 配置方式:通过 UIKitContaineruserInfoProvider 属性提供用户信息。详见 用户自定义信息文档

隐藏左侧消息头像

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,
          },
        },
      }}
    />
  );
}

设置消息气泡

MessageBubble 为消息气泡组件,支持自定义气泡的颜色、圆角、阴影等样式。

import React from 'react';
import { View } from 'react-native';
import { MessageBubble, MessageBubbleProps } from 'react-native-chat-uikit';

export function CustomMessageBubble(props: MessageBubbleProps) {
  const { model, children } = props;

  // 自定义发送消息的气泡样式
  if (model.layoutType === 'right') {
    return (
      <View
        style={{
          backgroundColor: '#4CAF50', // 自定义背景色
          borderRadius: 16,
          padding: 12,
          shadowColor: '#000',
          shadowOffset: { width: 0, height: 2 },
          shadowOpacity: 0.1,
          shadowRadius: 4,
          elevation: 2,
        }}
      >
        {children}
      </View>
    );
  }

  // 接收消息使用默认样式
  return <MessageBubble {...props} />;
}

设置消息时间

初始化时,可设置消息时间格式。

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>
  );
}

设置长按消息菜单

在消息列表中长按任意消息,即可弹出操作菜单,支持复制、回复、转发、置顶、多选、翻译、创建话题等丰富功能。

UIKit 提供三种消息长按菜单风格。你可以通过 UIKitContainermessageMenuStyle 属性进行全局配置:

菜单风格值描述
bottom-sheet(默认)底部弹出菜单:菜单从页面底部弹出。
context类似微信风格菜单:菜单在长按位置附近弹出。
custom自定义菜单:通过用户自定义组件实现,需要遵守 MessageCustomLongPressMenu 约束。
  • (默认)使用底部弹出菜单风格:
<UIKitContainer
  options={options}
  messageMenuStyle="bottom-sheet"
>
  {/* 应用内容 */}
</UIKitContainer>
  • 使用类似微信风格菜单:
<UIKitContainer
  options={options}
  messageMenuStyle="context"
>
  {/* 应用内容 */}
</UIKitContainer>
  • 使用自定义菜单组件:

如果需要完全自定义菜单样式,可以设置 messageMenuStyle="custom",并通过 MessageCustomLongPressMenu 属性提供自定义菜单组件。

提示

建议使用默认的 bottom-sheetcontext 风格,只有在需要完全自定义菜单 UI 时才使用 custom 样式。

  1. 在全局设置属性 Container.messageMenuStylecustom。其他模式不需要用户设置 MessageCustomLongPressMenu
export function App() {
  return (
    <UIKitContainer messageMenuStyle={'custom'}>
      {/* sub component */}
    </UIKitContainer>
  );
}
  1. 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>
  );
}

关于菜单项的添加、删除、显示/隐藏以及样式的设置,详见 消息列表的高级设置说明

设置点击事件

点击消息

通过 onClickedItem 处理消息点击事件:

<ConversationDetail
  type="chat"
  convId={convId}
  convType={convType}
  list={{
    props: {
      onClickedItem: (id, model) => {
        console.log('点击消息:', id);
        console.log('消息模型:', model);

        // 根据消息类型执行不同操作
        if (model.modelType === 'message') {
          const messageModel = model as MessageModel;
          if (messageModel.msg.body.type === ChatMessageType.IMAGE) {
            // 预览图片(默认行为已实现)
            console.log('预览图片');
          } else if (messageModel.msg.body.type === ChatMessageType.VIDEO) {
            // 播放视频(默认行为已实现)
            console.log('播放视频');
          }
        }

        // 返回 false 可阻止默认行为
        // return false;
      },
    },
  }}
  onBack={() => navigation.goBack()}
/>

长按消息

通过 onLongPressItem 处理消息长按事件:

<ConversationDetail
  type="chat"
  convId={convId}
  convType={convType}
  list={{
    props: {
      onLongPressItem: (id, model) => {
        console.log('长按消息:', id, model);

        // 默认会弹出消息上下文菜单
        // 返回 false 可阻止默认菜单弹出
        // return false;
      },
    },
  }}
  onBack={() => navigation.goBack()}
/>

点击头像

通过 onClickedAvatar 处理标题栏头像的点击事件。参数说明如下:

  • convId: 会话 ID。
  • convType: 会话类型。
  • ownerId: 所有者 ID。单聊为消息发送方和接收方的用户 ID,群聊为消息发送方的用户 ID。
<ConversationDetail
  type="chat"
  convId={convId}
  convType={convType}
  onClickedAvatar={(params) => {
    const { convId, convType, ownerId } = params;
    console.log('点击头像:', convId, convType, ownerId);

    // 跳转到用户详情或群组详情页面
    if (convType === ChatConversationType.PeerChat) {
      navigation.navigate('UserDetail', { userId: convId });
    } else {
      navigation.navigate('GroupDetail', { groupId: convId });
    }
  }}
  onBack={() => navigation.goBack()}
/>

点击话题

通过 onClickedThread 处理话题按钮的点击事件(仅群聊有效):

<ConversationDetail
  type="chat"
  convId={convId}
  convType={convType}
  onClickedThread={() => {
    console.log('点击话题按钮');
    // 跳转到话题列表页面
    navigation.navigate('ThreadList', { groupId: convId });
  }}
  onBack={() => navigation.goBack()}
/>

点击音视频通话

通过 onClickedVoiceonClickedVideo 处理音视频通话按钮的点击事件:

<ConversationDetail
  type="chat"
  convId={convId}
  convType={convType}
  onClickedVoice={() => {
    console.log('发起语音通话');
    // 调用语音通话功能
    startVoiceCall(convId);
  }}
  onClickedVideo={() => {
    console.log('发起视频通话');
    // 调用视频通话功能
    startVideoCall(convId);
  }}
  onBack={() => navigation.goBack()}
/>

点击引用消息

通过 onClickedItemQuote 处理引用消息的点击事件:

<ConversationDetail
  type="chat"
  convId={convId}
  convType={convType}
  list={{
    props: {
      onClickedItemQuote: (id, model) => {
        console.log('点击引用消息:', id, model);

        // 默认会滚动到被引用的消息
        // 返回 false 可阻止默认行为
        // return false;
      },
    },
  }}
  onBack={() => navigation.goBack()}
/>

完整实例

自定义消息条目和自定义消息长按菜单的示例如下:

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>
  );
}
上次编辑于: