EaseIMKit 使用指南

在您阅读此文档时,我们假定您已经具备了基础的 Android 应用开发经验,并能够理解相关基础概念。此文档是针对导入EaseIMKit库的快速集成文档,如果只是导入SDK去集成使用,请移步 Android SDK集成

EaseIMKit 是什么?

EaseIMKit 是 EaseUI 的升级版,是基于环信 IM SDK 的一款 UI 组件库,它提供了一些通用的 UI 组件,例如‘会话列表’、‘聊天界面’和‘联系人列表’等,开发者可根据实际业务需求通过该组件库快速地搭建自定义 IM 应用。EaseIMKit 中的组件在实现 UI 功能的同时,调用 IM SDK 相应的接口实现 IM 相关逻辑和数据的处理,因而开发者在使用EaseIMKit 时只需关注自身业务或个性化扩展即可。

EaseIMKit 源码地址

使用EaseIMKIt的环信IM APP 源码地址:

如果您使用的是EaseUI库,查看使用说明及快速集成,请点击下列链接跳转:

开发环境要求

  • Android Studio 3.2以上
  • Gradle4.6以上
  • targetVersion 26以上
  • Android SDK API 19以上
  • Java JDK 1.8以上

集成说明

EaseIMKit支持Gradle接入和 Module源码集成

Gradle接入集成

重大变动:远程仓库统一由JCenter迁移到MavenCentral,依赖库的域名由“com.hyphenate”修改为“io.hyphenate”,详见Android SDK 介绍及导入

implementation 'io.hyphenate:ease-im-kit:xxx版本'
implementation 'io.hyphenate:hyphenate-chat:xxx版本'

EaseIMKit必须依赖环信IM SDK,因而在使用EaseIMKit时必须同时添加环信IM SDK依赖。
注意:
(1)IM SDK 3.8.0版本以后,远程依赖的artifactId修改为hyphenate-chat,且该版本以后中不再包含音视频相关逻辑。
(2)IM SDK 3.8.0以下,远程依赖,包含音视频的artifactId为hyphenate-sdk,不包含音视频的artifactId为hyphenate-sdk-lite。如果想使用不包含音视频通话的sdk,用implementation 'io.hyphenate:hyphenate-sdk-lite:xxx版本'。
版本号参考Release Note

Module源码集成

implementation project(':ease-im-kit')

依赖的第三方库

  • Glide: 图片处理库,显示用户头像时用到。
  • BaiduLBS_Android.jar: 百度地图定位库。

关于位置消息说明

EaseIMKit中位置消息使用的是百度地图定位jar包,为了防止出现百度地图类冲突的问题,EaseIMKit库打包时对百度地图定位jar包采用了只编译的方式,这就要求如果开发者想要使用位置消息,需要在自己的项目中添加百度地图定位的jar包及其so文件。

初始化

在application的onCreate下调用初始化EaseIMKit的方法。
:EaseIMKit初始化里已包含SDK的初始化,不需要再去调用SDK的初始化。

public class DemoApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        //EaseIM初始化 
        if(EaseIM.getInstance().init(context, options)){ 
            //在做打包混淆时,关闭debug模式,避免消耗不必要的资源 
            EMClient.getInstance().setDebugMode(true); 
            //EaseIM初始化成功之后再去调用注册消息监听的代码 ... 
        }
    }
}

EaseIMKit封装了常用IM功能,提供了会话,聊天及联系人等基本的fragment,旨在帮助开发者快速集成环信SDK。

创建会话列表界面

EaseIMKit提供了EaseConversationListFragment,添加到Activity中即可用。

注:要实现自定义头像及昵称,请参考设置头像和昵称

创建聊天界面

EaseIMKit提供了EaseChatFragment,添加到Activity中并传递相应的参数即可用。
必须向EaseChatFragment传递的参数为:

  • “conversationId”——会话id;
  • “chatType”——聊天类型,整型,分别为单聊(1)、群聊(2)和聊天室(3);

可选传递参数为:

  • “history_msg_id”——消息id,用于查询历史记录时的定位消息id;
  • “isRoaming”——是否开启漫游,布尔类型,用于标记是否优先从服务器拉取消息。
public class ChatActivity extends BaseActivity {
    private EaseChatFragment chatFragment;

    @Override
    protected void onCreate(Bundle arg0) {
        super.onCreate(arg0);
        setContentView(R.layout.em_activity_chat);
        //use EaseChatFratFragment
        chatFragment = new EaseChatFragment();
        //pass parameters to chat fragment
        chatFragment.setArguments(getIntent().getExtras());
        getSupportFragmentManager().beginTransaction().add(R.id.container, chatFragment).commit();
    }
}

添加联系人界面

EaseIMKit提供了EaseContactListFragment,添加到Activity中即可用。

设置标题栏

EaseIMKit提供了自定义的标题栏控件EaseTitleBar。

标题栏除了做为View所具有的属性功能外,还可以设置标题的位置等。
xml中设置如下:

<com.hyphenate.easeui.widget.EaseTitleBar
    android:id="@+id/title_bar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/em_common_title_bar_height"
    app:titleBarTitle="@string/em_chat_group_detail_title"
    android:background="@color/white"
    app:titleBarDisplayHomeAsUpEnabled="true"/>

其中titleBarDisplayHomeAsUpEnabled属性为设置返回按钮是否可见,设置标题位置可设置titleBarTitlePosition,可选值为center,left和right。

也可进行代码设置,如下:

EaseTitleBar titleBarMessage = findViewById(R.id.title_bar_message);
//设置右侧菜单图标
titleBarMessage.setRightImageResource(R.drawable.chat_user_info);
//设置标题
titleBarMessage.setTitle("标题");
//设置标题位置
titleBarMessage.setTitlePosition(EaseTitleBar.TitlePosition.Left);
//设置右侧菜单图标的点击事件
titleBarMessage.setOnRightClickListener(this);
//设置返回按钮的点击事件
titleBarMessage.setOnBackPressListener(this);

当然设置右侧菜单,您也可以通过Android提供的添加menu xml的形式实现。修改按钮图标,可以调用titleBarMenuResource属性进行设置。

设置会话列表

会话列表可以修改如下样式:

  • 头像:头像大小,头像形状(方形,带圆角的方形,圆形),描边
  • 标题、内容、时间等文字:字体大小,字体颜色
  • 未读消息:可设置是否展示,展示位置(左式和右式)

在EaseConversationListFragment及其子类中可以直接获取到EaseConversationListLayout这个控件,然后通过这个控件进行设置。
代码如下:

//设置头像尺寸
conversationListLayout.setAvatarSize(EaseCommonUtils.dip2px(mContext, 50));
//设置头像样式:0为默认,1为圆形,2为方形(设置方形时,需要配合设置avatarRadius,默认的avatarRadius为50dp)
conversationListLayout.setAvatarShapeType(2);
//设置圆角半径
conversationListLayout.setAvatarRadius((int) EaseCommonUtils.dip2px(mContext, 5));
//设置标题字体的颜色
conversationListLayout.setTitleTextColor(ContextCompat.getColor(mContext, R.color.red));
//设置是否隐藏未读消息数,默认为不隐藏
conversationListLayout.hideUnreadDot(false);
//设置未读消息数展示位置,默认为左侧
conversationListLayout.showUnreadDotPosition(EaseConversationSetStyle.UnreadDotPosition.LEFT);

效果如下图:

更多样式请参考EaseContactListLayout控件。

增加长按菜单项

EaseConversationListLayout提供了增加菜单项的api,开发者可方便的增加更多的菜单功能。
示例代码如下:

@Override
public void initView(Bundle savedInstanceState) {
   super.initView(savedInstanceState);
   ......
   conversationListLayout.addItemMenu(Menu.NONE, R.id.action_con_delete, 4, getString(R.string.ease_conversation_menu_delete));
}    

......

@Override
public boolean onMenuItemClick(MenuItem item, int position) {
    EaseConversationInfo info = conversationListLayout.getItem(position);
    Object object = info.getInfo();
    if(object instanceof EMConversation) {
        switch (item.getItemId()) {
            case R.id.action_con_make_top :
                conversationListLayout.makeConversationTop(position, info);
                return true;
            case R.id.action_con_cancel_top :
                conversationListLayout.cancelConversationTop(position, info);
                return true;
            case R.id.action_con_delete :
                showDeleteDialog(position, info);
                return true;
        }
    }
    return super.onMenuItemClick(item, position);
}

设置聊天窗口

聊天窗口包括标题栏(不包含在EaseChatFragment中),聊天区,输入区及扩展展示区,如下图所示:

标题区EaseTitleBar的具体布局及实现不在EaseIMKit库的聊天控件及fragment中,需要开发者自己去实现。
开发者可以在EaseChatFragment中获取到EaseChatLayout这个控件,然后通过这个控件进一步获取到获取其他控件,代码如下:

//获取到聊天列表控件
EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout();
//获取到菜单输入父控件
EaseChatInputMenu chatInputMenu = chatLayout.getChatInputMenu();
//获取到菜单输入控件
IChatPrimaryMenu primaryMenu = chatInputMenu.getPrimaryMenu();
//获取到扩展区域控件
IChatExtendMenu chatExtendMenu = chatInputMenu.getChatExtendMenu();
//获取到表情区域控件
IChatEmojiconMenu emojiconMenu = chatInputMenu.getEmojiconMenu();

修改聊天列表样式

聊天列表区域可以修改背景,文字,气泡,是否展示昵称及聊天展示样式等,更多设置请参考IChatMessageItemSet。

修改聊天列表背景

//获取到聊天列表控件
EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout();
//设置聊天列表背景
messageListLayout.setBackground(new ColorDrawable(Color.parseColor("#DA5A4D")));

效果如下图:

修改头像属性

开发者可以设置默认头像和头像形状。

//获取到聊天列表控件
EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout();
//设置默认头像
messageListLayout.setAvatarDefaultSrc(ContextCompat.getDrawable(mContext, R.drawable.ease_default_avatar));
//设置头像形状:0为默认,1为圆形,2为方形
messageListLayout.setAvatarShapeType(1);

效果如下图:

修改聊天文本

开发者可以修改聊天文本的字体大小及字体颜色,发送方及接收方需保持一致。

//获取到聊天列表控件
EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout();
//设置文本字体大小
messageListLayout.setItemTextSize((int) EaseCommonUtils.sp2px(mContext, 18));
//设置文本字体颜色
messageListLayout.setItemTextColor(ContextCompat.getColor(mContext, R.color.red));

效果如下图:

修改时间线样式

开发者可以修改时间线的背景,文字的大小及颜色。

//获取到聊天列表控件
EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout();
//设置时间线的背景
messageListLayout.setTimeBackground(ContextCompat.getDrawable(mContext, R.color.gray_normal));
//设置时间线的文本大小
messageListLayout.setTimeTextSize((int) EaseCommonUtils.sp2px(mContext, 18));
//设置时间线的文本颜色
messageListLayout.setTimeTextColor(ContextCompat.getColor(mContext, R.color.black));

效果如下图:

修改聊天列表展示样式

开发者可以设置聊天列表的样式,发送方和接收方位于两侧还是位于一侧。

//获取到聊天列表控件
EaseChatMessageListLayout messageListLayout = chatLayout.getChatMessageListLayout();
//设置聊天列表样式:两侧及均位于左侧
messageListLayout.setItemShowType(EaseChatMessageListLayout.ShowType.LEFT);

效果如下图:

修改输入区样式

输入区控件为EaseChatInputMenu,它由输入控件EaseChatPrimaryMenu,扩展控件EaseChatExtendMenu和表情控件EaseEmojiconMenu组成。

//获取到菜单输入父控件
EaseChatInputMenu chatInputMenu = chatLayout.getChatInputMenu();
//获取到菜单输入控件
IChatPrimaryMenu primaryMenu = chatInputMenu.getPrimaryMenu();
//获取到扩展区域控件
IChatExtendMenu chatExtendMenu = chatInputMenu.getChatExtendMenu();
//获取到表情区域控件
IChatEmojiconMenu emojiconMenu = chatInputMenu.getEmojiconMenu();

开发者可以修改菜单输入控件的样式,其有5中模式,即完整模式,不可用语音模式,不可用表情模式,不可用语音和表情模式和只有文本输入模式。

//获取到菜单输入父控件
EaseChatInputMenu chatInputMenu = chatLayout.getChatInputMenu();
//获取到菜单输入控件
IChatPrimaryMenu primaryMenu = chatInputMenu.getPrimaryMenu();
if(primaryMenu != null) {
    //设置菜单样式为不可用语音模式
    primaryMenu.setMenuShowType(EaseInputMenuStyle.DISABLE_VOICE);
}

效果(EaseInputMenuStyle.DISABLE_VOICE)如下图:

其他样式为:

完整模式(EaseInputMenuStyle.All):

不可用表情模式(EaseInputMenuStyle.DISABLE_EMOJICON):

不可用语音和表情模式(EaseInputMenuStyle.DISABLE_VOICE_EMOJICON):

只有文本输入模式(EaseInputMenuStyle.ONLY_TEXT):

增加自定义消息类型及其布局

EaseIMKit中已经为八种基本消息类型文本,表情,图片,视频,语音,文件,定位及Custom提供了基本的布局,ViewHolder及Delegate,开发者可以直接使用。但是这些类型很可能不能满足开发者的需求,那么就需要添加新的消息类型及其布局和逻辑。
使用EaseIMKit只需按照以下5步即可快速添加自定义消息类型。
下面我们以自定一个新的文本消息为例:

1、新建ChatTxtNewAdapterDelegate继承EaseMessageAdapterDelegate。

public class ChatTxtNewAdapterDelegate extends EaseMessageAdapterDelegate <EMMessage, EaseChatRowViewHolder> {

    @Override
    protected EaseChatRow getEaseChatRow(ViewGroup parent, boolean isSender) {
        return null;
    }

    @Override
    protected EaseChatRowViewHolder createViewHolder(View view, MessageListItemClickListener itemClickListener) {
        return null;
    }
}

2、新建ChatRowTxtNew继承EaseChatRow并实现相关方法

public class ChatRowTxtNew extends EaseChatRow {
    private TextView contentView;

    public ChatRowTxtNew(Context context, boolean isSender) {
        super(context, isSender);
    }

    @Override
    protected void onInflateView() {
        inflater.inflate(isSender ? R.layout.ease_row_sent_txt_new : R.layout.ease_row_received_txt_new, this);
    }

    @Override
    protected void onFindViewById() {
        contentView = (TextView) findViewById(com.hyphenate.easeui.R.id.tv_chatcontent);
    }

    @Override
    protected void onSetUpView() {
        EMTextMessageBody txtBody = (EMTextMessageBody) message.getBody();
        contentView.setText(txtBody.getMessage());
    }
}

布局文件以R.layout.ease_row_sent_txt_new为例,如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingTop="13dp">

    <TextView
        android:id="@+id/timestamp"
        style="@style/chat_text_date_style"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_chat_activity" >

        <com.hyphenate.easeui.widget.EaseImageView
            android:id="@+id/iv_userhead"
            style="@style/ease_row_sent_iv_userhead_style"/>

        <RelativeLayout
            android:id="@+id/bubble"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="@dimen/margin_chat_activity"
            android:layout_toLeftOf="@id/iv_userhead"
            android:minWidth="30dp"
            android:padding="8dp"
            android:layout_marginTop="2dp"
            android:background="@drawable/ease_chat_bubble_send_bg"
            android:layout_below="@id/tv_userid">

            <TextView
                android:id="@+id/tv_chatcontent"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:autoLink="web"
                android:gravity="center|left"
                android:lineSpacingExtra="2dp"
                android:maxWidth="225.0dip"
                android:minHeight="@dimen/ease_chat_text_min_height"
                android:textColor="#000000"
                tools:text="环信"
                android:textSize="15sp" />

        </RelativeLayout>

        <ImageView
            android:id="@+id/msg_status"
            android:layout_toLeftOf="@id/bubble"
            style="@style/ease_row_sent_iv_fail_style"/>

        <TextView
            android:id="@+id/tv_ack"
            style="@style/chat_text_name_style"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@id/bubble"
            android:layout_marginRight="@dimen/ease_chat_ack_margin_bubble"
            android:text="@string/text_ack_msg"
            android:textSize="12sp"
            android:visibility="invisible" />

        <TextView
            android:id="@+id/tv_delivered"
            style="@style/chat_text_name_style"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@id/bubble"
            android:layout_marginRight="@dimen/ease_chat_ack_margin_bubble"
            android:text="@string/text_delivered_msg"
            android:textSize="12sp"
            android:visibility="invisible" />

        <ProgressBar
            android:id="@+id/progress_bar"
            style="?android:attr/progressBarStyle"
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:indeterminateDrawable="@drawable/ease_chat_loading_progress_bar"
            android:layout_toLeftOf="@id/bubble"
            android:visibility="invisible" />

        <TextView
            android:id="@+id/tv_userid"
            style="@style/chat_text_name_style"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="@dimen/chat_nick_margin_left"
            android:textSize="@dimen/chat_nick_text_size"
            android:layout_toLeftOf="@id/iv_userhead"
            android:visibility="gone" />

    </RelativeLayout>

</LinearLayout>

其中id为bubble控件外的控件为发送端布局的基本组成,需要开发者拷贝进去。一般而言,开发者需要添加的控件,放在bubble控件内即可。如上所示,开发者只需在onFindViewById()方法中找到自己添加的控件,并在onSetUpView()方法内处理展示逻辑即可。

3、新建ChatTxtNewViewHolder继承EaseChatRowViewHolder并实现相关方法

public class ChatTxtNewViewHolder extends EaseChatRowViewHolder {

    public ChatTxtNewViewHolder(@NonNull View itemView, MessageListItemClickListener itemClickListener) {
        super(itemView, itemClickListener);
    }

    @Override
    public void onBubbleClick(EMMessage message) {
        super.onBubbleClick(message);
        //实现相关点击方法
    }

}

自定义的ChatTxtNewViewHolder可以复写实现onBubbleClick(EMMessage message)及onResendClick(EMMessage message)方法。需要注意的是,如果在其他地方设置了MessageListItemClickListener监听,并将相应的方法实现并返回true以后,ViewHolder中的这两个方法会被拦截。
自定义的ViewHolder可以复写onDetachedFromWindow()方法,可以做一些释放资源的处理。
自定义的ViewHolder需要根据消息的方向(发送发或者接收方)对消息进行处理,可以分别复写handleSendMessage(final EMMessage message)或者handleReceiveMessage(EMMessage message)。

4、补全ChatTxtNewAdapterDelegate并重写isForViewType方法

public class ChatTxtNewAdapterDelegate extends EaseMessageAdapterDelegate <EMMessage, EaseChatRowViewHolder> {

    @Override
    public boolean isForViewType(EMMessage item, int position) {
        return item.getType() == EMMessage.Type.TXT && item.getBooleanAttribute(EaseConstant.MESSAGE_ATTR_IS_TXT_NEW, false);
    }

    @Override
    protected EaseChatRow getEaseChatRow(ViewGroup parent, boolean isSender) {
        return new ChatRowTxtNew(parent.getContext(), isSender);
    }

    @Override
    protected EaseChatRowViewHolder createViewHolder(View view, MessageListItemClickListener itemClickListener) {
        return new ChatTxtNewViewHolder(view, itemClickListener);
    }
}

注:
(1)相同的消息类型(比如例子中消息类型是EMMessage.Type.TXT)且通过标记判断类型时,在第5步注册对话类型时,应将该对话类型注册于基类的对话类型之前(即ChatTxtNewAdapterDelegate注册应在EaseTextAdapterDelegate之前)。
(2)对于item.getBooleanAttribute(EaseConstant.MESSAGE_ATTR_IS_TXT_NEW, false)可以理解为一种标记,在发送消息时设置,如下;

/**
 * 发送新文本消息
 * @param content
 */
public void sendTxtNewMessage(String content) {
    EMMessage message = EMMessage.createTxtSendMessage(content, toChatUsername);
    message.setAttribute(DemoConstant.MESSAGE_ATTR_IS_TXT_NEW, true);
    EMClient.getInstance().chatManager().sendMessage(message);
}

其中DemoConstant.MESSAGE_ATTR_IS_TXT_NEW为定义的一个常量,通常为字符串类型。

5、注册ChatTxtNewAdapterDelegate对话类型(通过EaseMessageTypeSetManager进行注册)

/**
 *注册对话类型
 */
private void registerConversationType() {
    EaseMessageTypeSetManager.getInstance()
            .addConversationType(EaseExpressionAdapterDelegate.class)       //自定义表情
            .addConversationType(EaseFileAdapterDelegate.class)             //文件
            ......
            .addConversationType(ChatTxtNewAdapterDelegate.class)           //新文本消息
            .setDefaultConversionType(EaseTextAdapterDelegate.class);       //文本
} 

需要开发者注意的是,自定义的消息类型需要注册到EaseMessageTypeSetManager中,具体用法可以参考环信App中的DemoHelper类中的registerConversationType()方法,并在初始化时调用registerConversationType()方法。
开发者在注册消息类型时,一定要在最后设置默认项(即调用setDefaultConversionType()),并建议将EaseTextAdapterDelegate设置默认项。如果没有符合的消息类型,EaseIMKit会选择默认的消息类型进行展示(注:需要展示的消息类型也需要符合默认消息的消息类型,否则会造成EMMessageBody强转时报错)。

增加长按菜单项

EaseChatLayout提供了增加菜单项的api,开发者可方便的增加更多的菜单功能。
示例代码如下:

@Override
public void initView(Bundle savedInstanceState) {
    super.initView(savedInstanceState);
    ......
    //构建菜单项model并通过EaseChatLayout添加
    MenuItemBean itemMenu = new MenuItemBean(0, R.id.action_chat_forward, 11, getString(R.string.action_forward));
    itemMenu.setResourceId(R.drawable.ease_chat_item_menu_forward);
    chatLayout.addItemMenu(itemMenu );
}    

......

@Override
public boolean onMenuItemClick(MenuItemBean item, EMMessage message) {
    switch (item.getItemId()) {
        case R.id.action_chat_forward :
            //增加实现逻辑,并返回true
            return true;
    }
    return false;
}

增加更多扩展功能

EaseIMKit提供了常用的一些扩展功能,比如发送图片,发送文件,发送定位等,但是实际开发中可能满足不了开发者的需求,EaseIMKit提供了增加扩展功能的接口。
示例代码如下:

private void resetChatExtendMenu() {
    //获取到扩展功能控件
    IChatExtendMenu chatExtendMenu = chatLayout.getChatInputMenu().getChatExtendMenu();
    if(chatExtendMenu == null) {
        return;
    }
    //清除所有的扩展项
    chatExtendMenu.clear();
    //添加自己需要的扩展功能
    chatExtendMenu.registerMenuItem(R.string.attach_picture, R.drawable.ease_chat_image_selector, EaseChatExtendMenu.ITEM_PICTURE);
    chatExtendMenu.registerMenuItem(R.string.attach_take_pic, R.drawable.ease_chat_takepic_selector, EaseChatExtendMenu.ITEM_TAKE_PICTURE);
    //根据消息类型添加不同的扩展功能
    if(chatType == EaseConstant.CHATTYPE_SINGLE){
        chatExtendMenu.registerMenuItem(R.string.attach_media_call, R.drawable.em_chat_video_call_selector, ITEM_VIDEO_CALL);
    }
    if (chatType == EaseConstant.CHATTYPE_GROUP) { // 音视频会议
        chatExtendMenu.registerMenuItem(R.string.voice_and_video_conference, R.drawable.em_chat_video_call_selector, ITEM_CONFERENCE_CALL);
    }
}

......

@Override
public void onChatExtendMenuItemClick(View view, int itemId) {
    super.onChatExtendMenuItemClick(view, itemId);
    //只需实现不满足需求或者开发者自己添加的扩展功能
    switch (itemId) {
        case ITEM_VIDEO_CALL:
            //实现条目点击事件
            break;
        case ITEM_CONFERENCE_CALL:
            //实现条目点击事件
            break;
    }
}

增加自定义表情

EaseIMKit也提供了增加自定义表情的接口,开发者可仿照EmojiconExampleGroupData进行定义类型的表情类,然后调用相应接口加入即可。
代码如下:

//添加扩展表情
 chatLayout.getChatInputMenu().getEmojiconMenu().addEmojiconGroup(EmojiconExampleGroupData.getData());

设置联系人列表

通讯录列表界面可以设置如下样式:

  • 展示模式:分为简洁模式和常规模式
  • 条目:设置条目背景,条目高度及header背景
  • 头像:设置默认头像,头像样式及头像大小等

在EaseContactListFragment及其子类中可以直接获取到EaseContactLayout这个控件,然后通过这个控件进行设置。

代码如下:

//获取列表控件
EaseContactListLayout contactList = contactLayout.getContactList();
//设置条目高度
contactList.setItemHeight((int) EaseCommonUtils.dip2px(mContext, 80));
//设置条目背景
contactList.setItemBackGround(ContextCompat.getDrawable(mContext, R.color.gray));
//设置头像样式:0为默认,1为圆形,2为方形(设置方形时,需要配合设置avatarRadius,默认的avatarRadius为50dp)
contactList.setAvatarShapeType(2);
//设置头像圆角
contactList.setAvatarRadius((int) EaseCommonUtils.dip2px(mContext, 5));
//设置header背景
contactList.setHeaderBackGround(ContextCompat.getDrawable(mContext, R.color.white));

效果如图:

设置简洁模式

//设置为简洁模式
contactLayout.showSimple();

效果如图:

增加长按菜单项

EaseContactListLayout提供了增加菜单项的api,开发者可方便的增加更多的菜单功能。
示例代码如下:

//通过增加OnPopupMenuPreShowListener监听,并在onMenuPreShow中增加菜单项更简单
@Override
public void onMenuPreShow(EasePopupMenuHelper menuHelper, int position) {
    super.onMenuPreShow(menuHelper, position);
    //增加需要的菜单项,其中传入的第三项order决定了菜单项的位置
    menuHelper.addItemMenu(1, R.id.action_friend_block, 2, getString(R.string.em_friends_move_into_the_blacklist_new));
    menuHelper.addItemMenu(1, R.id.action_friend_delete, 1, getString(R.string.ease_friends_delete_the_contact));
}

......

@Override
public boolean onMenuItemClick(MenuItem item, int position) {
    EaseUser user = contactLayout.getContactList().getItem(position);
    switch (item.getItemId()) {
        case R.id.action_friend_block :
            //增加处理逻辑并返回true
            return true;
        case R.id.action_friend_delete:
            //增加处理逻辑并返回true
            return true;
    }
    return super.onMenuItemClick(item, position);
}

增加头布局

EaseIMKit默认是不再通讯录列表之前增加头布局的,但是内部预设了添加头布局的逻辑,开发者可通过EaseContactListLayout提供的api快速的增加一个或者多个头布局。
示例代码如下:

/**
 * 添加头布局
 */
public void addHeader() {
    //增加多个头布局
    contactLayout.getContactList().addCustomItem(CUSTOM_NEW_CHAT, R.drawable.em_friends_new_chat, getString(R.string.em_friends_new_chat));
    contactLayout.getContactList().addCustomItem(CUSTOM_GROUP_LIST, R.drawable.em_friends_group_chat, getString(R.string.em_friends_group_chat));
    contactLayout.getContactList().addCustomItem(CUSTOM_CHAT_ROOM_LIST, R.drawable.em_friends_chat_room, getString(R.string.em_friends_chat_room));
}

......

@Override
public void initListener() {
    super.initListener();
    ......
    contactLayout.getContactList().setOnCustomItemClickListener(new OnItemClickListener() {
        @Override
        public void onItemClick(View view, int position) {
            EaseContactCustomBean item = contactLayout.getContactList().getCustomAdapter().getItem(position);
            switch (item.getId()) {
                case CUSTOM_NEW_CHAT :
                    //增加实现逻辑
                    break;
                case CUSTOM_GROUP_LIST :
                    //增加实现逻辑
                    break;
                case CUSTOM_CHAT_ROOM_LIST :
                    //增加实现逻辑
                    break;
            }
        }
    });
}

设置用户头像和昵称

设置头像和昵称

环信IM SDK不做用户信息存储,如果用户想要展示自定义的头像及昵称,可以通过EaseUserProfileProvider进行提供。
首先需要在合适的时机去设置EaseUserProfileProvider,例如:

EaseIM.getInstance().setUserProvider(new EaseUserProfileProvider() {
    @Override
    public EaseUser getUser(String username) {
        //根据username,从数据库中或者内存中取出之前保存的用户信息,如从数据库中取出的用户对象为DemoUserBean
        DemoUserBean bean = getUserFromDbOrMemery(username);
        EaseUser user = new EaseUser(username);
        ......
        //设置用户昵称
        user.setNickname(bean.getNickname());
        //设置头像地址
        user.setAvatar(bean.getAvatar());
        //最后返回构建的EaseUser对象
        return user;
    }
});

EaseIMKit中会话列表,聊天列表及联系人列表,内部已经添加EaseUserProfileProvider的判断,当展示数据时优先从EaseUserProfileProvider获取头像和昵称数据,如果有则展示,如果没有头像采用默认头像,昵称展示为环信id。
建议方案:开发者先将相关用户信息从服务器中获取并存储到数据库中,在getUser(String username)方法调用时,从数据库中根据username(环信id)取出相应的用户数据,生成EaseUser对象user,并给user赋值nickname及avatar属性,最后返回这个user即可。

统一设置头像样式

EaseIMKit提供了EaseAvatarOptions这个类用于全局配置头像的样式,包括形状,圆角半径,描边宽度及描边颜色。会话,聊天及联系人中已经增加了对于EaseAvatarOptions的支持。
示例代码如下:

//设置头像配置属性
EaseIM.getInstance().setAvatarOptions(getAvatarOptions());
......
/**
 * 统一配置头像
 * @return
 */
private EaseAvatarOptions getAvatarOptions() {
    EaseAvatarOptions avatarOptions = new EaseAvatarOptions();
    //设置头像形状为圆形,1代表圆形,2代表方形
    avatarOptions.setAvatarShape(1);
    return avatarOptions;
}

使用时可以直接调用EaseUserUtils中的setUserAvatarStyle(EaseImageView imageView)方法即可设置。

EaseIMKit还帮助开发者实现了一系列的事件监听接口,比如条目的点击事件,条目的长按事件,菜单项的点击事件等。

会话列表

条目点击事件

开发者如果使用EaseConversationListFragment及其子类,可以重写onItemClick(View view, int position)方法即可。
代码如下:

@Override
public void onItemClick(View view, int position) {
    super.onItemClick(view, position);
    //添加点击事件实现逻辑
}

开发者如果直接使用的EaseConversationListLayout控件,则可通过该控件设置条目的点击事件。
代码如下:

conversationListLayout.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        //添加点击事件实现逻辑
    }
});

长按事件及弹出菜单点击事件

EaseConversationListLayout中已经实现了一套默认的长按弹出菜单逻辑,开发者只需实现弹出菜单的点击事件即可。
如果开发者使用的是EaseConversationListFragment及其子类,则直接重写onMenuItemClick(MenuItem item, int position)即可。
代码如下:

@Override
public boolean onMenuItemClick(MenuItem item, int position) {
    //添加具体的点击事件实现逻辑,并返回true
    return super.onMenuItemClick(item, position);
}

开发者如果直接使用的EaseConversationListLayout控件,则可通过该控件设置弹出菜单的点击事件。
代码如下:

conversationListLayout.setOnPopupMenuItemClickListener(new OnPopupMenuItemClickListener() {
    @Override
    public boolean onMenuItemClick(MenuItem item, int position) {
        //添加具体的点击事件实现逻辑,并返回true
        return false;
    }
});

如果开发者需要自己实现弹出菜单,通过EaseConversationListLayout控件添加条目的长按监听并实现即可。
代码如下:

conversationListLayout.setOnItemLongClickListener(new OnItemLongClickListener() {
    @Override
    public boolean onItemLongClick(View view, int position) {
        //添加弹出菜单的逻辑,并返回true
        return false;
    }
});

聊天区域

聊天列表事件

开发者如果使用的是EaseChatFragment及其子类,则可以根据需要重写相关的事件方法即可。聊天列表中的常用监听事件均封装到了OnChatLayoutListener接口中,EaseChatFragment已经设置了该监听。
OnChatLayoutListener中有如下事件监听:

public interface OnChatLayoutListener {

    /**
     * 点击消息bubble区域
     * @param message
     * @return
     */
    boolean onBubbleClick(EMMessage message);

    /**
     * 长按消息bubble区域
     * @param v
     * @param message
     * @return
     */
    boolean onBubbleLongClick(View v, EMMessage message);

    /**
     * 点击头像
     * @param username
     */
    void onUserAvatarClick(String username);

    /**
     * 长按头像
     * @param username
     */
    void onUserAvatarLongClick(String username);

    /**
     * 条目点击
     * @param view
     * @param itemId
     */
    void onChatExtendMenuItemClick(View view, int itemId);

    /**
     * EditText文本变化监听
     * @param s
     * @param start
     * @param before
     * @param count
     */
    void onTextChanged(CharSequence s, int start, int before, int count);

    /**
     * 聊天中错误信息
     * @param code
     * @param errorMsg
     */
    void onChatError(int code, String errorMsg);

    /**
     * 用于监听其他人正在数据事件
     * @param action 输入事件 TypingBegin为开始 TypingEnd为结束
     */
    default void onOtherTyping(String action){}

}

如果开发者使用的是EaseChatLayout控件,则通过该控件实现OnChatLayoutListener接口即可。

长按事件及弹出菜单点击事件

EaseChatLayout中已经实现了一套默认的长按弹出菜单逻辑,并对默认的菜单项进行了处理,如果开发者对默认菜单项有其他实现需求,只需实现弹出菜单的点击事件即可。
如果开发者使用的是EaseChatFragment及其子类,则直接重写onMenuItemClick(MenuItemBean item, EMMessage message)即可。
代码如下:

@Override
public boolean onMenuItemClick(MenuItemBean item, EMMessage message) {
    //添加菜单条目点击事件实现逻辑,并返回true
    //返回true表示不采用默认的实现逻辑
    return false;
}

如果开发者需要在弹出菜单展示前对菜单项进行处理,重写onPreMenu(EasePopupWindowHelper helper, EMMessage message)并处理即可。
示例代码如下:

@Override
    public void onPreMenu(EasePopupWindowHelper helper, EMMessage message) {
        //默认两分钟后,即不可撤回
        if(System.currentTimeMillis() - message.getMsgTime() > 2 * 60 * 1000) {
            helper.findItemVisible(R.id.action_chat_recall, false);
        }
        EMMessage.Type type = message.getType();
        helper.findItemVisible(R.id.action_chat_forward, false);
        switch (type) {
            case TXT:
                if(!message.getBooleanAttribute(DemoConstant.MESSAGE_ATTR_IS_VIDEO_CALL, false)
                        && !message.getBooleanAttribute(DemoConstant.MESSAGE_ATTR_IS_VOICE_CALL, false)) {
                    helper.findItemVisible(R.id.action_chat_forward, true);
                }
                break;
            case IMAGE:
                helper.findItemVisible(R.id.action_chat_forward, true);
                break;
        }

        if(chatType == DemoConstant.CHATTYPE_CHATROOM) {
            helper.findItemVisible(R.id.action_chat_forward, true);
        }
    }

开发者如果直接使用的EaseChatLayout控件,则可通过该控件设置弹出菜单的点击事件。
代码如下:

chatLayout.setOnPopupWindowItemClickListener(new OnMenuChangeListener() {
    @Override
    public void onPreMenu(EasePopupWindowHelper helper, EMMessage message) {
        //菜单预处理逻辑
    }
    @Override
    public boolean onMenuItemClick(MenuItemBean item, EMMessage message) {
        //菜单项点击事件,如果默认实现不满足,则自己实现并返回true。
        return false;
    }
});

如果开发者需要自己实现弹出菜单,通过EaseChatLayout控件找到EaseChatMessageListLayout并添加条目的长按监听并实现即可。
代码如下:

chatLayout.getChatMessageListLayout().setOnItemLongClickListener(new OnItemLongClickListener() {
    @Override
    public boolean onItemLongClick(View view, int position) {
        //添加弹出菜单的逻辑,并返回true
        return false;
    }
});

通讯录列表

条目点击事件

开发者如果使用EaseContactListFragment及其子类,可以重写onItemClick(View view, int position)方法即可。
代码如下:

@Override
public void onItemClick(View view, int position) {
    super.onItemClick(view, position);
    //添加点击事件实现逻辑
}

开发者如果直接使用的EaseContactLayout控件,则可通过该控件找到EaseContactListLayout并设置条目的点击事件。
代码如下:

contactLayout.getContactList().setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        //添加点击事件实现逻辑
    }
});

长按事件及弹出菜单点击事件

EaseContactListLayout中已经实现了一套默认的长按弹出菜单逻辑,开发者只需实现弹出菜单的点击事件即可。
如果开发者使用的是EaseContactListFragment及其子类,则直接重写onMenuItemClick(MenuItem item, int position)即可。
代码如下:

@Override
public boolean onMenuItemClick(MenuItem item, int position) {
    //添加具体的点击事件实现逻辑,并返回true
    return super.onMenuItemClick(item, position);
}

如果开发者需要在弹出菜单展示前对菜单项进行处理,重写onMenuPreShow(EasePopupWindowHelper helper, EMMessage message)并处理即可。

开发者如果直接使用的EaseContactLayout控件,则可通过该控件找到EaseContactListLayout并设置弹出菜单的点击事件。
代码如下:

contactLayout.getContactList().setOnPopupMenuItemClickListener(new OnPopupMenuItemClickListener() {
    @Override
    public boolean onMenuItemClick(MenuItem item, int position) {
        //添加具体的点击事件实现逻辑,并返回true
        return false;
    }
});

相应的菜单项预处理,需要通过EaseContactListLayout设置菜单预处理监听事件。
代码如下:

contactLayout.getContactList().setOnPopupMenuPreShowListener(new OnPopupMenuPreShowListener() {
    @Override
    public void onMenuPreShow(EasePopupMenuHelper menuHelper, int position) {
        //菜单预处理逻辑
    }
});

如果开发者需要自己实现弹出菜单,通过EaseContactListLayout控件添加条目的长按监听并实现即可。
代码如下:

contactLayout.getContactList().setOnItemLongClickListener(new OnItemLongClickListener() {
    @Override
    public boolean onItemLongClick(View view, int position) {
        //添加弹出菜单的逻辑,并返回true
        return false;
    }
});

系统消息

EaseIMKit中EaseConversationListLayout已经封装了IM通知的展示逻辑,但是需要开发者将IM的通知封装成系统消息并保存到本地数据库。为了方便开发者封装成符合EaseIMKit能够使用的系统消息,EaseIMKit中提供了EaseSystemMsgManager管理类,开发者可通过该管理类,方便的封装及更新系统消息。
EaseIMKit可处理的系统消息有如下要求:

//设置为文本消息
EMMessage emMessage = EMMessage.createReceiveMessage(EMMessage.Type.TXT);
//设置from为固定的"em_system"
emMessage.setFrom(EaseConstant.DEFAULT_SYSTEM_MESSAGE_ID);
emMessage.setMsgId(UUID.randomUUID().toString());
emMessage.setStatus(EMMessage.Status.SUCCESS);

当然EaseSystemMsgManager管理类已经做了默认处理,开发者只需传入文本内容(会话列表中展示的内容)及扩展内容ext(Map<String, Object>)即可。 示例如下:

@Override
public void onFriendRequestDeclined(String username) {
    EMLog.i("ChatContactListener", "onFriendRequestDeclined");
    Map<String, Object> ext = EaseSystemMsgManager.getInstance().createMsgExt();
    ext.put(DemoConstant.SYSTEM_MESSAGE_FROM, username);
    ext.put(DemoConstant.SYSTEM_MESSAGE_STATUS, InviteMessageStatus.BEREFUSED.name());
    EMMessage message = EaseSystemMsgManager.getInstance().createMessage(PushAndMessageHelper.getSystemMessage(ext), ext);
    ......
}

同时EaseConversationListLayout提供了是否展示系统消息的api,showSystemMessage(boolean show)用于控制是否展示系统消息。