EaseUI 是一个 UI 库,封装了 IM 功能常用的控件、fragment 等等,旨在帮助开发者快速集成环信 SDK。
EaseUI 及 Demo 的 GitHub 下载地址为:
注意:因为这是一个 UI 库,后续很可能还会继续改动,新旧版本在 API 的兼容上不会像 IM SDK 那样绝对的兼容。
EaseUI 库的代码是开源的,下载的 SDK 压缩包里面已经包含此库,解压后路径为/examples/easeui。
打开你的 AS 项目 → File → New → Import Module → 选择或输入 EaseUI 库路径 → Next → Next → Finish。
导入完可能会有如下错误:
找到相应报错的地方把swipeRefreshLayout.setColorSchemeResources
改成swipeRefreshLayout.setColorScheme
,ViewCompat.getX
改成new ViewCompat().getX
,然后重新 build 即可。
或者,把v4包的版本号加大,譬如compile 'com.android.support:support-v4:23.1.1'。
这里对聊天页面几个控件做简单图示:
正式使用 EaseUI 需要先调用初始化方法,在 Application 的 oncreate 里调用初始化。
EaseUI.getInstance().init(applicationContext)
new 出 EaseChatFragment 或者其子类,调用 setArguments 方法传入 chatType(会话类型)和userId(用户或群id),通过 getSupportFragmentManager() 启动 fragment。
//new出EaseChatFragment或其子类的实例
EaseChatFragment chatFragment = new EaseChatFragment();
//传入参数
Bundle args = new Bundle();
args.putInt(EaseConstant.EXTRA_CHAT_TYPE, EaseConstant.CHATTYPE_GROUP);
args.putString(EaseConstant.EXTRA_USER_ID, "zw123");
chatFragment.setArguments(args);
getSupportFragmentManager().beginTransaction().add(R.id.container, chatFragment).commit();
EaseUI 提供现成的聊天相关能直接使用的 fragment,APP 无需任何改动,可以直接使用(可参考simpledemo)。也可以通过继承 EaseUI 里的 fragment 亦或是直接使用 EaseUI 里提供的 widget 扩展自己的需要的功能。
自组合控件时可参考 EaseChatFragment 类。
注意:使用 EaseUI 中的自定义控件时,如果需要 xml 中设置其属性(具体哪些属性可查看 attrs 文件),务必在 xml 根节点中加上xmlns:easemob=“http://schemas.android.com/apk/res-auto”
。
在 xml 中声明标题栏控件,可以在 xml 直接设置标题内容,左右图片,在 Java 文件中亦可以设置这些属性以及相关的点击事件。
示例:
<com.easemob.easeui.widget.EaseTitleBar
android:id="@+id/title_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
easemob:titleBarLeftImage="@drawable/ease_mm_title_back" />
titleBar = (EaseTitleBar) getView().findViewById(R.id.title_bar);
titleBar.setTitle("张建国");
titleBar.setRightImageResource(R.drawable.ease_mm_title_remove);
EaseChatMessageList 默认包含文字、表情、图片、语音、视频、文件消息的显示。
使用该控件,可以在 xml 中设置其 item(chatrow) 的背景图片,是否显示用户头像、昵称等属性。
<com.easemob.easeui.widget.EaseChatMessageList
android:id="@+id/message_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
easemob:msgListShowUserNick="false"
/>
常用 API:
messageList = (EaseChatMessageList) getView().findViewById(R.id.message_list);
//初始化messagelist
messageList.init(toChatUsername, chatType, null);
//设置item里的控件的点击事件
messageList.setItemClickListener(new EaseChatMessageList.MessageListItemClickListener() {
@Override
public void onUserAvatarClick(String username) {
//头像点击事件
}
@Override
public void onResendClick(final EMMessage message) {
//重发消息按钮点击事件
}
@Override
public void onBubbleLongClick(EMMessage message) {
//气泡框长按事件
}
@Override
public boolean onBubbleClick(EMMessage message) {
//气泡框点击事件,EaseUI有默认实现这个事件,如果需要覆盖,return值要返回true
return false;
}
});
//获取下拉刷新控件
swipeRefreshLayout = messageList.getSwipeRefreshLayout();
//刷新消息列表
messageList.refresh();
messageList.refreshSeekTo(position);
messageList.refreshSelectLast();
在 EaseUI 里,每一个 messagelist 的 item 称为一个 chatrow,开发者可以覆盖默认的 chatrow 或者自定义自己的 chatrow。
简单示例(具体可参考的 chatfragment 和 easechatfragment 类):
private final class CustomChatRowProvider implements EaseCustomChatRowProvider {
@Override
public int getCustomChatRowTypeCount() {
//音、视频通话发送、接收共4种
return 4;
}
@Override
public int getCustomChatRowType(EMMessage message) {
if(message.getType() == EMMessage.Type.TXT){
//语音通话类型
if (message.getBooleanAttribute(Constant.MESSAGE_ATTR_IS_VOICE_CALL, false)){
return message.direct == EMMessage.Direct.RECEIVE ? MESSAGE_TYPE_RECV_VOICE_CALL : MESSAGE_TYPE_SENT_VOICE_CALL;
}else if (message.getBooleanAttribute(Constant.MESSAGE_ATTR_IS_VIDEO_CALL, false)){
//视频通话
return message.direct == EMMessage.Direct.RECEIVE ? MESSAGE_TYPE_RECV_VIDEO_CALL : MESSAGE_TYPE_SENT_VIDEO_CALL;
}
}
return 0;
}
@Override
public EaseChatRow getCustomChatRow(EMMessage message, int position, BaseAdapter adapter) {
if(message.getType() == EMMessage.Type.TXT){
// 语音通话、视频通话
if (message.getBooleanAttribute(Constant.MESSAGE_ATTR_IS_VOICE_CALL, false) ||
message.getBooleanAttribute(Constant.MESSAGE_ATTR_IS_VIDEO_CALL, false)){
//ChatRowVoiceCall为一个继承自EaseChatRow的类
return new ChatRowVoiceCall(getActivity(), message, position, adapter);
}
}
return null;
}
}
//初始化的时候把provider传入
messageList.init(toChatUsername, chatType, new CustomChatRowProvider());
EaseChatInputMenu
包含3个控件:
EaseChatPrimaryMenu
:主菜单栏,包含文字输入、发送等功能EaseChatExtendMenu
:扩展栏,点击加号按钮出来的小宫格的菜单栏EaseEmojiconMenu
:表情栏xml 中使用示例:
<com.easemob.easeui.widget.EaseChatInputMenu
android:id="@+id/input_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
EaseChatPrimaryMenu扩展:
考虑的 EaseChatPrimaryMenu 变动的复杂性,EaseUI 未提供具体修改的 API,但是提供了一个 API 让开发者替换成自己写的控件。
/**
* 设置自定义的表情栏,该控件需要继承自EaseEmojiconMenuBase,
* 以及回调你想要回调出去的事件给设置的EaseEmojiconMenuListener
* @param customEmojiconMenu
*/
public void setCustomEmojiconMenu(EaseEmojiconMenuBase customEmojiconMenu){
this.emojiconMenu = customEmojiconMenu;
}
通过 inputmenu 对象调用此方法就行。
EaseChatExtendMenu扩展:
扩展菜单栏默认是不包含任何按钮的(EaseChatFragment中会默认加上拍照、图片、位置),通过调用 registerExtendMenuItem 就行。
inputMenu = (EaseChatInputMenu) getView().findViewById(R.id.input_menu);
//注册底部菜单扩展栏item
//传入item对应的文字,图片及点击事件监听,extendMenuItemClickListener实现EaseChatExtendMenuItemClickListener
inputMenu.registerExtendMenuItem(R.string.attach_video, R.drawable.em_chat_video_selector, ITEM_VIDEO, extendMenuItemClickListener);
inputMenu.registerExtendMenuItem(R.string.attach_file, R.drawable.em_chat_file_selector, ITEM_FILE, extendMenuItemClickListener);
...
EaseEmojiconMenu扩展:
EaseEmojiconMenu 默认包含一套默认的表情,通过 API 可替换或增加表情。
两种方法:
inputMenu.init(null);
((EaseEmojiconMenu)inputMenu.getEmojiconMenu()).addEmojiconGroup(EmojiconExampleGroupData.getData());
((EaseEmojiconMenu)inputMenu.getEmojiconMenu()).removeEmojiconGroup(1);
如果想完全自己实现这套表情的布局,通过调用 setCustomEmojiconMenu 实现。
/**
* 设置自定义的表情栏,该控件需要继承自EaseEmojiconMenuBase,
* 以及回调你想要回调出去的事件给设置的EaseEmojiconMenuListener
* @param customEmojiconMenu
*/
public void setCustomEmojiconMenu(EaseEmojiconMenuBase customEmojiconMenu){
this.emojiconMenu = customEmojiconMenu;
}
整体代码示例:
inputMenu = (EaseChatInputMenu) getView().findViewById(R.id.input_menu);
//注册底部菜单扩展栏item
//传入item对应的文字,图片及点击事件监听,extendMenuItemClickListener实现EaseChatExtendMenuItemClickListener
inputMenu.registerExtendMenuItem(R.string.attach_video, R.drawable.em_chat_video_selector, ITEM_VIDEO, extendMenuItemClickListener);
inputMenu.registerExtendMenuItem(R.string.attach_file, R.drawable.em_chat_file_selector, ITEM_FILE, extendMenuItemClickListener);
//初始化,此操作需放在registerExtendMenuItem后
inputMenu.init();
//设置相关事件监听
inputMenu.setChatInputMenuListener(new ChatInputMenuListener() {
@Override
public void onSendMessage(String content) {
// 发送文本消息
sendTextMessage(content);
}
@Override
public boolean onPressToSpeakBtnTouch(View v, MotionEvent event) {
////把touch事件传入到EaseVoiceRecorderView 里进行录音
return voiceRecorderView.onPressToSpeakBtnTouch(v, event, new EaseVoiceRecorderCallback() {
@Override
public void onVoiceRecordComplete(String voiceFilePath, int voiceTimeLength) {
// 发送语音消息
sendVoiceMessage(voiceFilePath, voiceTimeLength);
}
});
}
});
更多 API 及使用请参考 API 文档及 Demo。
EaseChatFragment 对很多功能进行了封装,开发者可通过继承 EaseChatFragment,使用已经封装好的功能。
由于聊天页面比较复杂,EaseUI 把 fragment 中常见的扩展功能放到一个 interface——EaseChatFragmentHelper 中,在 setUpView()
方法中调用 setChatFragmentHelper
实现需要的功能,具体代码可参考 EaseUIDemo。
public interface EaseChatFragmentHelper{
/**
* 设置消息扩展属性
*/
void onSetMessageAttributes(EMMessage message);
/**
* 进入会话详情
*/
void onEnterToChatDetails();
/**
* 用户头像点击事件
* @param username
*/
void onAvatarClick(String username);
/**
* 消息气泡框点击事件
*/
boolean onMessageBubbleClick(EMMessage message);
/**
* 消息气泡框长按事件
*/
void onMessageBubbleLongClick(EMMessage message);
/**
* 扩展输入栏item点击事件,如果要覆盖EaseChatFragment已有的点击事件,return true
* @param view
* @param itemId
* @return
*/
boolean onExtendMenuItemClick(int itemId, View view);
/**
* 设置自定义chatrow提供者
* @return
*/
EaseCustomChatRowProvider onSetCustomChatRowProvider();
}
虽然标题栏控件提供了修改的 API,但是如果完全不想要时,可以调用隐藏方法(在任何库里 fragment 都提供此方法)
//隐藏标题栏
hideTitleBar();
//显示标题栏
ShowTitleBar();
其他可根据自己需求调用或者覆盖某些方法。
1. 直接启动 EaseConversationListFragment。
示例:
conversationListFragment = new EaseConversationListFragment();
conversationListFragment.setConversationListItemClickListener(new EaseConversationListItemClickListener() {
@Override
public void onListItemClicked(EMConversation conversation) {
startActivity(new Intent(MainActivity.this, ChatActivity.class).putExtra(EaseConstant.EXTRA_USER_ID, conversation.getUserName()));
}
});
//通过getSupportFragmentManager启动fragment即可
2. 继承 EaseConversationListFragment 扩展 fragment。
此页面比较简单,调用 conversationListView 设置 item 点击事件,其余按自己需求编写即可,譬如 listview 长按事件,可参考 easeuidemo。
//conversationListView为EaseConversationList
conversationListView.setOnItemClickListener(new OnItemClickListener() {})
3. EaseConversationList 使用
可在 xml 中设置 listview 的 item 的文字颜色、大小等,具体可查看 attrs 定义的 EaseConversationList 里的属性。
示例:
<com.easemob.easeui.widget.EaseConversationList
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:cacheColorHint="#00000000"
android:divider="@null"
easemob:cvsListPrimaryTextSize="16sp"
/>
API 调用示例:
//会话列表控件
conversationListView = (EaseConversationList) getView().findViewById(R.id.list);
//初始化,参数为会话列表集合
conversationListView.init(conversationList);
//刷新列表
conversationListView.refresh();
其他方法参照 listview 使用即可。
1. 直接启动 EaseConversationListFragment。
示例:
conversationListFragment = new EaseConversationListFragment();
//需要设置联系人列表才能启动fragment
contactListFragment.setContactsMap(getContacts());
//设置item点击事件
contactListFragment.setContactListItemClickListener(new EaseContactListItemClickListener() {
@Override
public void onListItemClicked(EaseUser user) {
startActivity(new Intent(MainActivity.this, ChatActivity.class).putExtra(EaseConstant.EXTRA_USER_ID, user.getUsername()));
}
});
2. 继承自EaseConversationListFragment。
同 EaseConversationList 一样,此 fragment 比较简单,注意在 setUpView()
里调用 setContactsMap
即可,其余按需求自由实现,可参考 ContactListFragment。
3. 联系人列表控件 EaseContactList 使用。
可直接在 xml 里设置 list item 文字颜色、大小等等,详细可设置的属性可查看 attrs 文件。
示例:
<com.easemob.easeui.widget.EaseContactList
android:id="@+id/contact_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
easemob:ctsListInitialLetterBg="#abc"/>
API 调用示例:
contactListLayout = (EaseContactList) getView().findViewById(R.id.contact_list);
//初始化时需要传入联系人list
contactListLayout.init(contactList);
//刷新列表
contactListLayout.refresh();
设置了此提供者后,EaseUI 里的 fragment 及相关控件就会自动显示用户头像和昵称了,当然前提是你设置的 provider 返回的 user 对象设置了昵称和头像地址。
示例(具体可参考 demohelper 类):
//get easeui instance
easeUI = EaseUI.getInstance();
//需要EaseUI库显示用户头像和昵称设置此provider
easeUI.setUserProfileProvider(new EaseUserProfileProvider() {
@Override
public EaseUser getUser(String username) {
return getUserInfo(username);
}
});
easeUI.setSettingsProvider(new EaseSettingsProvider() {})
表情的显示,通过设置此 provider 实现。
//设置表情provider
easeUI.setEmojiconInfoProvider(new EaseEmojiconInfoProvider() {
@Override
public EaseEmojicon getEmojiconInfo(String emojiconIdentityCode) {
//通过表情id返回具体表情数据
EaseEmojiconGroupEntity data = EmojiconExampleGroupData.getData();
for(EaseEmojicon emojicon : data.getEmojiconList()){
if(emojicon.getIdentityCode().equals(emojiconIdentityCode)){
return emojicon;
}
}
return null;
}
@Override
public Map<String, Object> getTextEmojiconMapping() {
//返回文字表情emoji文本和图片(resource id或者本地路径)的映射map
return null;
}
});
示例:
easeUI.getNotifier().setNotificationInfoProvider(new EaseNotificationInfoProvider() {
@Override
public String getTitle(EMMessage message) {
//修改标题,这里使用默认
return null;
}
@Override
public int getSmallIcon(EMMessage message) {
//设置小图标,这里为默认
return 0;
}
@Override
public String getDisplayedText(EMMessage message) {
// 设置状态栏的消息提示,可以根据message的类型做相应提示
String ticker = EaseCommonUtils.getMessageDigest(message, appContext);
if(message.getType() == Type.TXT){
ticker = ticker.replaceAll("\\[.{2,3}\\]", "[表情]");
}
EaseUser user = getUserInfo(message.getFrom());
if(user != null){
return getUserInfo(message.getFrom()).getNick() + ": " + ticker;
}else{
return message.getFrom() + ": " + ticker;
}
}
@Override
public String getLatestText(EMMessage message, int fromUsersNum, int messageNum) {
return null;
// return fromUsersNum + "个基友,发来了" + messageNum + "条消息";
}
@Override
public Intent getLaunchIntent(EMMessage message) {
//设置点击通知栏跳转事件
Intent intent = new Intent(appContext, ChatActivity.class);
//有电话时优先跳转到通话页面
if(isVideoCalling){
intent = new Intent(appContext, VideoCallActivity.class);
}else if(isVoiceCalling){
intent = new Intent(appContext, VoiceCallActivity.class);
}else{
ChatType chatType = message.getChatType();
if (chatType == ChatType.Chat) { // 单聊信息
intent.putExtra("userId", message.getFrom());
intent.putExtra("chatType", Constant.CHATTYPE_SINGLE);
} else { // 群聊信息
// message.getTo()为群聊id
intent.putExtra("userId", message.getTo());
if(chatType == ChatType.GroupChat){
intent.putExtra("chatType", Constant.CHATTYPE_GROUP);
}else{
intent.putExtra("chatType", Constant.CHATTYPE_CHATROOM);
}
}
}
return intent;
}
});