Bug
1. Didn't find class "com.netease.nrtc.engine.rawapi.IRtcEngine
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.netease.nrtc.engine.rawapi.IRtcEngine" on path: DexPathList[[zip file "/data/app/com.risecenter.parent-UVT7jygHbkI03O9nWFNl7w==/base.apk"],nativeLibraryDirectories=[/data/app/com.risecenter.parent-UVT7jygHbkI03O9nWFNl7w==/lib/arm64, /data/app/com.risecenter.parent-UVT7jygHbkI03O9nWFNl7w==/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64, /product/lib64]]
原因:同时集成了音视频的库,但是没有集成完全,少库了。
解决方案:因为只用IM的功能,去掉音视频的库就好了
2. NimUIKit.getAccount()值总为null
我已经在Application里初始化过了,为什么还是没有值?初始化如下:
/**
* 初始化IM
*/
private fun initIM() {
NIMClient.init(this, imLoginInfo(), SDKOptions.DEFAULT)
if (NIMUtil.isMainProcess(this)) {
NimUIKit.init(this)
}
}
/**
* 获取IM登陆信息
*/
private fun imLoginInfo(): LoginInfo? {
val account = risePreferences.getString(NIM_ACCOUNT, null)
val token = risePreferences.getString(NIM_TOKEN, null)
logError(account, "NIMTest")
if (account.isNullOrEmpty() || token.isNullOrEmpty()) {
return null
}
return LoginInfo(account, token)
}
后来在源码中发现如下注释:
/**
* 获取当前登录的账号
*
* @return 必须登录成功后才有值
*/
public static String getAccount() {
return NimUIKitImpl.getAccount();
}
于是,我在登录成功的回调里做了如下处理:
// 登录成功后在此处调用 IM 的成功回调,传入account,之后 NimUIKit.getAccount() 才会有值,与初始化传入无关
NimUIKit.loginSuccess(account)
附,我的登录回调代码:
/**
* 用户登陆
*/
fun accountLogin(phone: String, pwd: String): LiveData> {
return object : NetworkResource() {
override fun createCall(): ApolloCall {
return LoginMutation.builder()
.mobile(phone)
.password(pwd)
.build().request()
}
override fun processResponse(response: LoginMutation.Data): LoginMutation.Data {
val account = response.createUserToken()!!.nim()!!.accid()
risePreferences.edit {
putString(LAST_PHONE, phone)//保存最后一次登录账号
putString(NIM_TOKEN, response.createUserToken()!!.nim()!!.token()) //存储IM用的token
putString(NIM_ACCOUNT, account) //存储IM用的accid
// 登录成功后在此处调用 IM 的成功回调,传入account,之后 NimUIKit.getAccount() 才会有值,与初始化传入无关
NimUIKit.loginSuccess(account)
loginId = response.createUserToken()?.id() //保存登录id
loginToken = response.createUserToken()?.token()?.token() //保存登录Token
}
return super.processResponse(response)
}
}.asLiveData
}
3. 长按消息撤回或者转发时,报错
java.lang.NullPointerException: Attempt to invoke interface method 'boolean com.netease.nim.uikit.business.session.module.MsgForwardFilter.shouldIgnore(com.netease.nimlib.sdk.msg.model.IMMessage)' on a null object reference
at com.netease.nim.uikit.business.session.module.list.MessageListPanelEx$MsgItemEventListener.prepareDialogItems(MessageListPanelEx.java:848)
at com.netease.nim.uikit.business.session.module.list.MessageListPanelEx$MsgItemEventListener.onNormalLongClick(MessageListPanelEx.java:822)
at com.netease.nim.uikit.business.session.module.list.MessageListPanelEx$MsgItemEventListener.showLongClickAction(MessageListPanelEx.java:809)
at com.netease.nim.uikit.business.session.module.list.MessageListPanelEx$MsgItemEventListener.onViewHolderLongClick(MessageListPanelEx.java:757)
at com.netease.nim.uikit.business.session.viewholder.MsgViewHolderBase$5.onLongClick(MsgViewHolderBase.java:325)
at android.view.View.performLongClickInternal(View.java:6374)
at android.view.View.performLongClick(View.java:6332)
at android.widget.TextView.performLongClick(TextView.java:11198)
at android.view.View.performLongClick(View.java:6350)
at android.view.View$CheckForLongPress.run(View.java:24895)
at android.os.Handler.handleCallback(Handler.java:808)
at android.os.Handler.dispatchMessage(Handler.java:101)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7425)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
原因是采用github下列方式,未注册 消息撤回过滤器 和 消息转发器
NimUIKit.startP2PSession(context, account)
官方Demo中是使用下列方式调用的
SessionHelper.startP2PSession(this, message.getSessionId());
而在SessionHelper中注册了:
/**
* 消息转发过滤器
*/
private static void registerMsgForwardFilter() {
NimUIKit.setMsgForwardFilter(new MsgForwardFilter() {
@Override
public boolean shouldIgnore(IMMessage message) {
if (message.getDirect() == MsgDirectionEnum.In
&& (message.getAttachStatus() == AttachStatusEnum.transferring
|| message.getAttachStatus() == AttachStatusEnum.fail)) {
// 接收到的消息,附件没有下载成功,不允许转发
return true;
} else if (message.getMsgType() == MsgTypeEnum.custom && message.getAttachment() != null
&& (message.getAttachment() instanceof SnapChatAttachment
|| message.getAttachment() instanceof RTSAttachment
|| message.getAttachment() instanceof RedPacketAttachment)) {
// 白板消息和阅后即焚消息,红包消息 不允许转发
return true;
} else if (message.getMsgType() == MsgTypeEnum.robot && message.getAttachment() != null && ((RobotAttachment) message.getAttachment()).isRobotSend()) {
return true; // 如果是机器人发送的消息 不支持转发
}
return false;
}
});
}
/**
* 消息撤回过滤器
*/
private static void registerMsgRevokeFilter() {
NimUIKit.setMsgRevokeFilter(new MsgRevokeFilter() {
@Override
public boolean shouldIgnore(IMMessage message) {
if (message.getAttachment() != null
&& (message.getAttachment() instanceof AVChatAttachment
|| message.getAttachment() instanceof RTSAttachment
|| message.getAttachment() instanceof RedPacketAttachment)) {
// 视频通话消息和白板消息,红包消息 不允许撤回
return true;
} else if (DemoCache.getAccount().equals(message.getSessionId())) {
// 发给我的电脑 不允许撤回
return true;
}
return false;
}
});
}
解决方案:
采用官方Demo的方式需要引入很多类,故我只在入口处进行了注册:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val account = risePreferences.getString(NIM_ACCOUNT, null)
requestBasicPermission()
registerMsgRevokeFilter(account)
btn_chat.setOnClickListener {
NimUIKit.startP2PSession(context, account)
}
}
// 消息撤回过滤器
private fun registerMsgRevokeFilter(account: String) {
NimUIKit.setMsgRevokeFilter(MsgRevokeFilter { message ->
if (account == message.sessionId) {
// 发给我的电脑 不允许撤回
return@MsgRevokeFilter true
}
false
})
}
4. 撤回报错,需要注册 MsgViewHolderTip
BaseMessageActivity
NimUIKit.registerTipMsgViewHolder(MsgViewHolderTip.class);
与之相关的类还有:
修改聊天消息中的字体,音频,视频间距
private void layoutDirection() {
if (isReceivedMessage()) {
bodyTextView.setBackgroundResource(NimUIKitImpl.getOptions().messageLeftBackground);
bodyTextView.setTextColor(Color.BLACK);
// bodyTextView.setPadding(ScreenUtil.dip2px(15), ScreenUtil.dip2px(8), ScreenUtil.dip2px(10), ScreenUtil.dip2px(8));
bodyTextView.setPadding(ScreenUtil.dip2px(8), ScreenUtil.dip2px(28), ScreenUtil.dip2px(28), ScreenUtil.dip2px(28));
} else {
bodyTextView.setBackgroundResource(NimUIKitImpl.getOptions().messageRightBackground);
bodyTextView.setTextColor(Color.WHITE);
// bodyTextView.setPadding(ScreenUtil.dip2px(10), ScreenUtil.dip2px(8), ScreenUtil.dip2px(15), ScreenUtil.dip2px(8));
bodyTextView.setPadding(ScreenUtil.dip2px(28), ScreenUtil.dip2px(28), ScreenUtil.dip2px(8), ScreenUtil.dip2px(28));
}
}
另外,如果图片或者视频有内容框要去掉(一般都是 .9 图)的话,注释下面两行代码即可。
MsgViewHolderBase
if (isMiddleItem()) {
setGravity(bodyContainer, Gravity.CENTER);
} else {
if (isReceivedMessage()) {
setGravity(bodyContainer, Gravity.LEFT);
// contentContainer.setBackgroundResource(leftBackground());
} else {
setGravity(bodyContainer, Gravity.RIGHT);
// contentContainer.setBackgroundResource(rightBackground());
}
}
5.TextView添加超链接,有色差,不显示特殊数字(跳转到拨号页)
修改添加下划线的字体的颜色
bodyTextView.setLinkTextColor(Color.WHITE);
public class MsgViewHolderText extends MsgViewHolderBase {
protected void bindContentView() {
layoutDirection();
bodyTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemClick();
}
});
MoonUtil.identifyFaceExpression(NimUIKit.getContext(), bodyTextView, getDisplayText(), ImageSpan.ALIGN_BOTTOM);
bodyTextView.setMovementMethod(LinkMovementMethod.getInstance());
bodyTextView.setLinkTextColor(Color.WHITE);
bodyTextView.setOnLongClickListener(longClickListener);
}
添加下划线的方式(有很多种,这里介绍一种)
nim_message_item_text_body
android:autoLink="phone|email|web"
重点类
1. CropImageActivity (裁剪图片)
2. PickerAlbumPreviewActivity(设置选中照片的数量)
private void setTitleIndex(int index) {
if (totalSize <= 0) {
setTitle("");
} else {
index++;
setTitle(index + "/" + totalSize);
}
}
3. CircleImageView (修改圆头像为圆角头像)
但是我们不能直接在CircleImageView中修改,不利于拓展,会造成困惑,应该创建一个新类,改变继承关系
@Override
protected void onDraw(Canvas canvas) {
if (mBitmap == null) {
return;
}
// if (mFillColor != Color.TRANSPARENT) {
// canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mDrawableRadius, mFillPaint);
// }
// canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mDrawableRadius, mBitmapPaint);
// if (mBorderWidth != 0) {
// canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mBorderRadius, mBorderPaint);
// }
RectF oval3 = new RectF(0, 0, getWidth(), getHeight());// 设置个新的长方形
canvas.drawRoundRect(oval3, 20, 15, mBitmapPaint);//第二个参数是x半径,第三个参数是y半径
}
另外,如果需要和app里的头像同步,需要后端和云信打通,你可以在这里看到加载的头像url,以便于调试
HeadImageView
private void doLoadImage(final String url, final int defaultResId, final int thumbSize) {
/*
* 若使用网易云信云存储,这里可以设置下载图片的压缩尺寸,生成下载URL
* 如果图片来源是非网易云信云存储,请不要使用NosThumbImageUtil
*/
final String thumbUrl = makeAvatarThumbNosUrl(url, thumbSize);
}
4. 聊天消息中的图片&视频圆角太大,要修改小一点
/**
MsgViewHolderBase
* 会话窗口消息列表项的ViewHolder基类,负责每个消息项的外层框架,包括头像,昵称,发送/接收进度条,重发按钮等。
* 具体的消息展示项可继承该基类,然后完成具体消息内容展示即可。
*/
MsgViewHolderBase
public abstract class MsgViewHolderThumbBase extends MsgViewHolderBase {
...
}
MsgViewHolderThumbBase.class
private int maskBg() {
return R.drawable.nim_message_item_round_bg;
}
nim_message_item_round_bg.xml
相关基类:
ps: 高效的定位方式,直接找其用到的资源(最好是图片,这样可以在文件列表里,快速定位,然后通过Find Usages找到使用的地方,然后按图索骥)
5. 修改发送消息控制面板内容
MessageFragment
// 操作面板集合
protected List getActionList() {
List actions = new ArrayList<>();
actions.add(new ImageAction());
actions.add(new VideoAction());
actions.add(new LocationAction());
if (customization != null && customization.actions != null) {
actions.addAll(customization.actions);
}
return actions;
}
/**
* 更多操作模块
*/
public class ActionsPanel {
/**
* adapter
*/
public class ActionsPagerAdapter extends PagerAdapter {
// 最重要的一段代码,实现了相关功能的跳转
actions.get(index).onClick();
// 完整代码 ActionsPagerAdapter
gridView.setOnItemClickListener(new GridView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
int index = ((Integer) parent.getTag()) * ITEM_COUNT_PER_GRID_VIEW + position;
actions.get(index).onClick();
}
});
如果想在其他地方接入上述功能,需要一下步骤;
- 首先注册相关模块
// 操作面板集合
protected List getActionList() {
List actions = new ArrayList<>();
actions.add(new ImageAction());
actions.add(new CameraAction());
actions.add(new VideoAction());
if (customization != null && customization.actions != null) {
actions.addAll(customization.actions);
}
return actions;
}
- 在需要使用的地方,比如按钮的点击监听
// InputPanel
// 记住相应模块的在上面list中的position,即可
else if (v == cameraImgeView) {
actions.get(1).onClick();
6. 控制面板功能修改
原功能:
1. 拍照>>拍照&选择照片
2. 录像>>录像&选择视频
3. 无中间的拍照按钮
新功能:
1. 拍照>>选择照片
2. 录像>>选择视频
3. 中间拍照按钮>>拍照&录像
实现新功能1:
PickImageHelper 注释掉dialog入口,直接打开选择图片界面
/**
* 打开图片选择器
*/
public static void pickImage(final Context context, final int requestCode, final PickImageOption option) {
if (context == null) {
return;
}
// CustomAlertDialog dialog = new CustomAlertDialog(context);
// dialog.setTitle(option.titleResId);
//
// dialog.addItem(context.getString(R.string.input_panel_take), new CustomAlertDialog.onSeparateItemClickListener() {
// @Override
// public void onClick() {
// int from = PickImageActivity.FROM_CAMERA;
// if (!option.crop) {
// PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, option.multiSelect, 1,
// true, false, 0, 0);
// } else {
// PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, false, 1,
// false, true, option.cropOutputImageWidth, option.cropOutputImageHeight);
// }
//
// }
// });
//
// dialog.addItem(context.getString(R.string.choose_from_photo_album), new CustomAlertDialog
// .onSeparateItemClickListener() {
// @Override
// public void onClick() {
// int from = PickImageActivity.FROM_LOCAL;
// if (!option.crop) {
// PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, option.multiSelect,
// option.multiSelectMaxCount, true, false, 0, 0);
// } else {
// PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, false, 1,
// false, true, option.cropOutputImageWidth, option.cropOutputImageHeight);
// }
//
// }
// });
//
// dialog.show();
// 注释掉dialog入口,直接进入选择图片界面
int from = PickImageActivity.FROM_LOCAL;
if (!option.crop) {
PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, option.multiSelect,
option.multiSelectMaxCount, true, false, 0, 0);
} else {
PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, false, 1,
false, true, option.cropOutputImageWidth, option.cropOutputImageHeight);
}
}
实现新功能2:
VideoMessageHelper 注释掉dialog,直接进入选择视频界面
/**
* 显示视频拍摄或从本地相册中选取
*/
public void showVideoSource(int local, int capture) {
this.localRequestCode = local;
this.captureRequestCode = capture;
// CustomAlertDialog dialog = new CustomAlertDialog(activity);
// dialog.setTitle(activity.getString(R.string.input_panel_video));
// dialog.addItem("拍摄视频", new CustomAlertDialog.onSeparateItemClickListener() {
// @Override
// public void onClick() {
// chooseVideoFromCamera();
// }
// });
// dialog.addItem("从相册中选择视频", new CustomAlertDialog.onSeparateItemClickListener() {
// @Override
// public void onClick() {
// chooseVideoFromLocal();
// }
// });
// dialog.show();
chooseVideoFromLocal();
}
尴尬,新需求又改了:
要求打开同时可以选择照片和视频的的图片选择器,目前我只是添加了两个入口:
以下是我做得改动:
// 首先,没有兼容群聊,注释掉了入口
/**
* 高级群群资料页
*/
public class AdvancedTeamInfoActivity extends UI implements
private void showSelector(int titleId, final int requestCode) {
PickImageHelper.PickImageOption option = new PickImageHelper.PickImageOption();
option.titleResId = titleId;
option.multiSelect = false;
option.crop = true;
option.cropOutputImageWidth = 720;
option.cropOutputImageHeight = 720;
// TODO 关于群聊,此处还未做兼容选择视频,故先注释掉
// new PickImageHelper().pickImage(AdvancedTeamInfoActivity.this, requestCode, option);
}
// 其次,在下面文件中添加video需要的相关方法
/**
* update by jake on 2018/6/23
* 修改dialog入口为 进入本地相册&视频 及 该方法以下所有内容
*/
public class PickImageHelper {
// 添加自 videoMessageHelper
dialog.addItem(context.getString(R.string.choose_from_video_album), new CustomAlertDialog.onSeparateItemClickListener() {
@Override
public void onClick() {
chooseVideoFromLocal();
}
});
// 最后,添加了回掉监听,传入了localRequestCode参数,未使用匿名对象
public abstract class PickImageAction extends BaseAction {
private void showSelector(int titleId, final int requestCode, final boolean multiSelect, final String outPath) {
// TODO 此处传入关于 选择视频的监听,所以更改了构造器
// new PickImageHelper().pickImage(getActivity(), requestCode, option);
// TODO 此处不可用匿名函数,因为下面回掉要用到,如果用匿名函数,则导致PickImageHelper中的listenser & localRequestCode & activity 为null,导致回掉失败
// 故,此处实例化
pickImageHelper = new PickImageHelper();
pickImageHelper.pickImage(getActivity(), requestCode, option,makeRequestCode(RequestCode.GET_LOCAL_VIDEO),new VideoMessageHelper.VideoMessageHelperListener() {
@Override
public void onVideoPicked(File file, String md5) {
MediaPlayer mediaPlayer = getVideoMediaPlayer(file);
long duration = mediaPlayer == null ? 0 : mediaPlayer.getDuration();
int height = mediaPlayer == null ? 0 : mediaPlayer.getVideoHeight();
int width = mediaPlayer == null ? 0 : mediaPlayer.getVideoWidth();
IMMessage message = MessageBuilder.createVideoMessage(getAccount(), getSessionType(), file, duration, width, height, md5);
sendMessage(message);
}
});
// 并添加了 返回值回掉 的分支
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case RequestCode.GET_LOCAL_VIDEO:
pickImageHelper.onGetLocalVideoResult(data);
break;
}
}
7 选择图片界面
PickerAlbumActivity
8 发送原图界面
PickerAlbumPreviewActivity
9 设置最大录音时间
UIKitOptions
/**
* 录音时长限制,单位秒,默认最长120s
*/
public int audioRecordMaxTime = 120;
10 消息通知
private void initNotificationConfig() {
// 初始化消息提醒
NIMClient.toggleNotification(UserPreferences.getNotificationToggle());
// 加载状态栏配置
StatusBarNotificationConfig statusBarNotificationConfig = UserPreferences.getStatusConfig();
if (statusBarNotificationConfig == null) {
statusBarNotificationConfig = DemoCache.getNotificationConfig();
UserPreferences.setStatusConfig(statusBarNotificationConfig);
}
// 更新配置
NIMClient.updateStatusBarNotificationConfig(statusBarNotificationConfig);
}
11. 消息撤回,复制,删除,转文字等功能
MessageListPanelEx
// 长按消息item的菜单项准备。如果消息item的MsgViewHolder处理长按事件(MsgViewHolderBase#onItemLongClick),且返回为true,
// 则对应项的长按事件不会调用到此处
private void prepareDialogItems(final IMMessage selectedItem, CustomAlertDialog alertDialog) {
MsgTypeEnum msgType = selectedItem.getMsgType();
MessageAudioControl.getInstance(container.activity).stopAudio();
// 0 EarPhoneMode
longClickItemEarPhoneMode(alertDialog, msgType);
// 1 resend
longClickItemResend(selectedItem, alertDialog);
// 2 copy
longClickItemCopy(selectedItem, alertDialog, msgType);
// 3 revoke
if (enableRevokeButton(selectedItem)) {
longClickRevokeMsg(selectedItem, alertDialog);
}
// 4 delete
longClickItemDelete(selectedItem, alertDialog);
// 5 trans
// longClickItemVoidToText(selectedItem, alertDialog, msgType);
//
// if (!NimUIKitImpl.getMsgForwardFilter().shouldIgnore(selectedItem) && !recordOnly) {
// // 6 forward to person
// longClickItemForwardToPerson(selectedItem, alertDialog);
// // 7 forward to team
// longClickItemForwardToTeam(selectedItem, alertDialog);
// }
}
12. 设置最大录制视频时间
/**
* 视频录制界面
*
* Created by huangjun on 2015/4/11.
*/
public class CaptureVideoActivity extends UI implements SurfaceHolder.Callback {
private static final String TAG = "video";
private static final String EXTRA_DATA_FILE_NAME = "EXTRA_DATA_FILE_NAME";
private static final int VIDEO_TIMES = 180; //最大录制时间
// private static final int VIDEO_TIMES = 10; //最大录制时间
private static final int VIDEO_WIDTH = 320;
private static final int VIDEO_HEIGHT = 240;
}
13. 设置录制视频完成的dialog不可撤销,(点击其他区域不消失)
/**
* 视频录制界面
*/
public class CaptureVideoActivity extends UI implements SurfaceHolder.Callback {
final EasyAlertDialog dialog = EasyAlertDialogHelper.createOkCancelDiolag(this, null, message, true, listener);
// true 改为 false 即可, boolean cancelable
final EasyAlertDialog dialog = EasyAlertDialogHelper.createOkCancelDiolag(this, null, message, false, listener);
14. 设置最大录制视频大小
public class C {
// 视频允许大小
// public static final long MAX_LOCAL_VIDEO_FILE_SIZE = 20 * 1024 * 1024; // 20M
public static final long MAX_LOCAL_VIDEO_FILE_SIZE = 2000 * 1024 * 1024;
15. 未读消息获取与展示
HomeFragment
/**
* 注册未读消息数量观察者
*/
private void registerMsgUnreadInfoObserver(boolean register) {
if (register) {
ReminderManager.getInstance().registerUnreadNumChangedCallback(this);
} else {
ReminderManager.getInstance().unregisterUnreadNumChangedCallback(this);
}
}
/**
* 未读消息数量观察者实现
*/
@Override
public void onUnreadNumChanged(ReminderItem item) {
MainTab tab = MainTab.fromReminderId(item.getId());
if (tab != null) {
tabs.updateTab(tab.tabIndex, item);
}
}
/**
* 注册/注销系统消息未读数变化
*
* @param register
*/
private void registerSystemMessageObservers(boolean register) {
NIMClient.getService(SystemMessageObserver.class).observeUnreadCountChange(sysMsgUnreadCountChangedObserver,
register);
}
private Observer sysMsgUnreadCountChangedObserver = new Observer() {
@Override
public void onEvent(Integer unreadCount) {
SystemMessageUnreadManager.getInstance().setSysMsgUnreadCount(unreadCount);
ReminderManager.getInstance().updateContactUnreadNum(unreadCount);
}
};
/**
* 查询系统消息未读数
*/
private void requestSystemMessageUnreadCount() {
int unread = NIMClient.getService(SystemMessageService.class).querySystemMessageUnreadCountBlock();
SystemMessageUnreadManager.getInstance().setSysMsgUnreadCount(unread);
ReminderManager.getInstance().updateContactUnreadNum(unread);
}
/**
* 悬浮在屏幕上的红点拖拽动画绘制区域
*/
public class DropCover extends View {}
/**
* TAB红点提醒管理器
* Created by huangjun on 2015/3/18.
*/
public class ReminderManager {}
/**
* 未读数红点View(自绘红色的圆和数字)
* 触摸之产生DOWN/MOVE/UP事件(不允许父容器处理TouchEvent),回调给浮在上层的DropCover进行拖拽过程绘制。
* View启动过程:Constructors -> onAttachedToWindow -> onMeasure() -> onSizeChanged() -> onLayout() -> onDraw()
*
* Created by huangjun on 2016/9/13.
*/
public class DropFake extends View {}
16. 获取会话列表
/**
* 最近联系人列表(会话列表)
*/
public class RecentContactsFragment extends TFragment {}
// 更改布局
public RecentContactAdapter(RecyclerView recyclerView, List data) {
super(recyclerView, data);
addItemType(ViewType.VIEW_TYPE_COMMON, R.layout.nim_recent_contact_list_item, CommonRecentViewHolder.class);
addItemType(ViewType.VIEW_TYPE_TEAM, R.layout.nim_recent_contact_list_item, TeamRecentViewHolder.class);
}
17. 个人名片(包括添加,删除,发起会话等功能)
/**
* 用户资料页面
*/
public class UserProfileActivity extends UI {
18. 系统消息列表页(验证消息等)
/**
* 系统消息中心界面
*/
public class SystemMessageActivity extends UI implements TAdapterDelegate,
AutoRefreshListView.OnRefreshListener, SystemMessageViewHolder.SystemMessageListener {
19. 通讯录列表
/**
* 集成通讯录列表 (包含验证提醒,智能机器人,讨论组,高级群等,就是没有真正的好友)
*/
public class ContactListFragment extends MainTabFragment {}
/**
* 通讯录Fragment (真正好友的列表)
*/
public class ContactsFragment extends TFragment {}
HeadImageView
20. 获取好友信息
final class UserDataProvider {
public static List provide(TextQuery query) {
List sources = query(query);
List items = new ArrayList<>(sources.size());
for (UserInfo u : sources) {
items.add(new ContactItem(ContactHelper.makeContactFromUserInfo(u), ItemTypes.FRIEND));
}
LogUtil.i(UIKitLogTag.CONTACT, "contact provide data size =" + items.size());
return items;
}
private static final List query(TextQuery query) {
List friends = NimUIKit.getContactProvider().getUserInfoOfMyFriends();
List users = NimUIKit.getUserInfoProvider().getUserInfo(friends);
if (query == null) {
return users;
}
UserInfo user;
for (Iterator iter = users.iterator(); iter.hasNext(); ) {
user = iter.next();
boolean hit = ContactSearch.hitUser(user, query) || (ContactSearch.hitFriend(user, query));
if (!hit) {
iter.remove();
}
}
return users;
}
}
21. 自定义通知
/**
* 自定义通知
*/
public class CustomNotificationActivity extends UI implements TAdapterDelegate {}
private void sendCustomNotification(String account, String content) {
JSONObject obj = new JSONObject();
obj.put("id", "2");
obj.put("content", content);
String jsonContent = obj.toJSONString();
CustomNotification notification = new CustomNotification();
notification.setFromAccount(DemoCache.getAccount());
notification.setSessionId(account);
notification.setSendToOnlineUserOnly(false);
notification.setSessionType(sendTarget == 1 ? SessionTypeEnum.Team : SessionTypeEnum.P2P);
notification.setApnsText(jsonContent);
notification.setContent(jsonContent);
NIMClient.getService(MsgService.class).sendCustomNotification(notification).setCallback(new RequestCallback() {
@Override
public void onSuccess(Void param) {
Toast.makeText(CustomNotificationActivity.this, R.string.send_custom_notification_success, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailed(int code) {
Toast.makeText(CustomNotificationActivity.this, R.string.send_custom_notification_failed, Toast.LENGTH_SHORT).show();
}
@Override
public void onException(Throwable exception) {
Toast.makeText(CustomNotificationActivity.this, R.string.send_custom_notification_failed, Toast.LENGTH_SHORT).show();
}
});
}
22. 默认头像名称
int defResId = R.drawable.nim_avatar_default;
**
* 初始化sdk 需要的用户信息提供者,现主要用于内置通知提醒获取昵称和头像
*
* 注意不要与 IUserInfoProvider 混淆,后者是 UIKit 与 demo 之间的数据共享接口
*
*/
public class NimUserInfoProvider implements UserInfoProvider {
@Override
public Bitmap getAvatarForMessageNotifier(SessionTypeEnum sessionType, String sessionId) {
/*
* 注意:这里最好从缓存里拿,如果加载时间过长会导致通知栏延迟弹出!该函数在后台线程执行!
*/
Bitmap bm = null;
int defResId = R.drawable.nim_avatar_default;
}
}
23. 配置通知栏跳转的页面
NimSDKOptionConfig
// 这里开发者可以自定义该应用初始的 StatusBarNotificationConfig
private static StatusBarNotificationConfig loadStatusBarNotificationConfig() {
StatusBarNotificationConfig config = new StatusBarNotificationConfig();
// TODO 点击通知需要跳转到的界面
// config.notificationEntrance = LoginActivity.class;
// config.notificationSmallIconId = R.drawable.ic_stat_notify_msg;
config.notificationColor = RiseImCache.getContext().getResources().getColor(R.color.color_8f8f8f);
// 通知铃声的uri字符串
config.notificationSound = "android.resource://com.netease.nim.demo/raw/msg";
config.notificationFolded = true;
// 呼吸灯配置
config.ledARGB = Color.GREEN;
config.ledOnMs = 1000;
config.ledOffMs = 1500;
// 是否APP ICON显示未读数红点(Android O有效)
config.showBadge = true;
// save cache,留做切换账号备用
RiseImCache.setNotificationConfig(config);
return config;
}
24. 初始化在线状态事件
/**
* 用于初始化时,注册全局的广播、云信观察者等等云信相关业务
*/
public class NIMInitManager {
// OnlineStateEventManager.init();
25. 设置 通知栏 跳转到对应聊天界面
MainActivity
因为 android:launchMode="singleTop" ,所以需要在 onNewIntent 方法中也设置一遍
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onParseIntent()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
onParseIntent()
}
private void onParseIntent() {
Intent intent = getIntent();
if (intent.hasExtra(NimIntent.EXTRA_NOTIFY_CONTENT)) {
IMMessage message = (IMMessage) getIntent().getSerializableExtra(NimIntent.EXTRA_NOTIFY_CONTENT);
switch (message.getSessionType()) {
case P2P:
SessionHelper.startP2PSession(this, message.getSessionId());
break;
case Team:
SessionHelper.startTeamSession(this, message.getSessionId());
break;
default:
break;
}
} else if (intent.hasExtra(EXTRA_APP_QUIT)) {
onLogout();
return;
} else if (intent.hasExtra(AVChatActivity.INTENT_ACTION_AVCHAT)) {
if (AVChatProfile.getInstance().isAVChatting()) {
Intent localIntent = new Intent();
localIntent.setClass(this, AVChatActivity.class);
startActivity(localIntent);
}
} else if (intent.hasExtra(AVChatExtras.EXTRA_FROM_NOTIFICATION)) {
String account = intent.getStringExtra(AVChatExtras.EXTRA_ACCOUNT);
if (!TextUtils.isEmpty(account)) {
SessionHelper.startP2PSession(this, account);
}
}
}
26. 长按删除,置顶对话框
RecentContactsFragment
// 长按菜单
private void showLongClickMenu(final RecentContact recent, final int position) {
CustomAlertDialog alertDialog = new CustomAlertDialog(getActivity());
alertDialog.setTitle(UserInfoHelper.getUserTitleName(recent.getContactId(), recent.getSessionType()));
String title = getString(R.string.main_msg_list_delete_chatting);
alertDialog.addItem(title, new CustomAlertDialog.onSeparateItemClickListener() {
@Override
public void onClick() {
// 删除会话,删除后,消息历史被一起删除
NIMClient.getService(MsgService.class).deleteRecentContact(recent);
NIMClient.getService(MsgService.class).clearChattingHistory(recent.getContactId(), recent.getSessionType());
adapter.remove(position);
postRunnable(new Runnable() {
@Override
public void run() {
refreshMessages(true);
}
});
}
});
// title = (isTagSet(recent, RECENT_TAG_STICKY) ? getString(R.string.main_msg_list_clear_sticky_on_top) : getString(R.string.main_msg_list_sticky_on_top));
// alertDialog.addItem(title, new CustomAlertDialog.onSeparateItemClickListener() {
// @Override
// public void onClick() {
// if (isTagSet(recent, RECENT_TAG_STICKY)) {
// removeTag(recent, RECENT_TAG_STICKY);
// } else {
// addTag(recent, RECENT_TAG_STICKY);
// }
// NIMClient.getService(MsgService.class).updateRecent(recent);
//
// refreshMessages(false);
// }
// });
// alertDialog.addItem("删除该聊天(仅服务器)", new CustomAlertDialog.onSeparateItemClickListener() {
// @Override
// public void onClick() {
// NIMClient.getService(MsgService.class)
// .deleteRoamingRecentContact(recent.getContactId(), recent.getSessionType())
// .setCallback(new RequestCallback() {
// @Override
// public void onSuccess(Void param) {
// Toast.makeText(getActivity(), "delete success", Toast.LENGTH_SHORT).show();
// }
//
// @Override
// public void onFailed(int code) {
// Toast.makeText(getActivity(), "delete failed, code:" + code, Toast.LENGTH_SHORT).show();
// }
//
// @Override
// public void onException(Throwable exception) {
//
// }
// });
// }
// });
alertDialog.show();
}
27. Tab设置未读消息数
自己代码 HomePagerAdapter
// 只在家校沟通模块下显示未读消息数
val unread = v.findViewById(R.id.tab_unread)
if (position == 1) {
RxBus.getDefault().toFlowable(UnReadCount::class.java).subscribe { it ->
if (it.type == UNREADE_P2P && it.content.toInt() > 0) {
unread.text = it.content
unread.visibility = View.VISIBLE
}else{
unread.visibility = View.GONE
}
}
} else unread.visibility = View.GONE
发送消息的位置 MessageIMFragment
// 回传未读数与item条数 , 设置标题(数量是会话数+瑞思公告+系统通知)
override fun unReadCount(unreadNum: Int, items: MutableList, titleTXT: TextView) {
super.unReadCount(unreadNum, items, titleTXT)
// TODO 后续接入系统公告推送,需要加上 未读消息数量
titleTXT.text = stringForRes(R.string.message_list_title) + "(" + unreadNum + ")"
// 发送一个通知到Tab,更新红点数目
RxBus.getDefault().post(UnReadCount(from = "MessageIMFragment",content = unreadNum.toString()))
}
28. 解除最大 99 条未读消息的限制
RecentViewHolder
protected String unreadCountShowRule(int unread) {
// unread = Math.min(unread, 99);
return String.valueOf(unread);
}
29. 去掉列表(数据不满屏)弹力滑动效果(仿iOS效果)
RecentContactsFragment 需求要求实现滑动特效
// ios style 注释掉即可
OverScrollDecoratorHelper.setUpOverScroll(recyclerView, OverScrollDecoratorHelper.ORIENTATION_VERTICAL);
30. 录音上滑取消
InputPanel
/**
* 正在进行语音录制和取消语音录制,界面展示
* TODO 此处设置上滑取消的 图片显示
*
* @param cancel
*/
private void updateTimerTip(boolean cancel) {
if (cancel) {
// timerTip.setText(R.string.recording_cancel_tip);
// timerTipContainer.setBackgroundResource(R.drawable.nim_cancel_record_red_bg);
audioImageViewCancel.setVisibility(View.VISIBLE);
audioImageView.setVisibility(View.GONE);
} else {
// timerTip.setText(R.string.recording_cancel);
// timerTipContainer.setBackgroundResource(0);
audioImageViewCancel.setVisibility(View.GONE);
audioImageView.setVisibility(View.VISIBLE);
}
}
31. 添加选择照片视频拍摄的监听 及 新需求布局
布局 nim_message_activity_text_layout
// 类名 InputPanel
// TODO 此处添加选择照片视频拍摄的监听
else if (v == cameraImgeView){
}else if (v == photoImgeView){
}
32. 跳转到图片&视频选择页
VideoMessageHelper
/**
* API19 之后选择视频
*/
protected void chooseVideoFromLocalKitKat() {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
// intent.setType(C.MimeType.MIME_JPEG);
try {
activity.startActivityForResult(intent, localRequestCode);
} catch (ActivityNotFoundException e) {
Toast.makeText(activity, R.string.gallery_invalid, Toast.LENGTH_SHORT).show();
} catch (SecurityException e) {
}
}
/**
* API19 之前选择视频
*/
protected void chooseVideoFromLocalBeforeKitKat() {
Intent mIntent = new Intent(Intent.ACTION_GET_CONTENT);
mIntent.setType(C.MimeType.MIME_VIDEO_ALL);
// mIntent.setType(C.MimeType.MIME_JPEG);
mIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
try {
activity.startActivityForResult(mIntent, localRequestCode);
} catch (ActivityNotFoundException e) {
Toast.makeText(activity, R.string.gallery_invalid, Toast.LENGTH_SHORT).show();
}
}
33. 通讯录相关类
/**
* 通讯录Fragment
*/
public class ContactsFragment extends TFragment {
内容item 布局文件 nim_contacts_item.xml
/**
* 字母导航,点击字母,列表滑动到指定字母集合上。
*
* @author huangjun
*/
public class LivIndex {
/**
* 通讯录Fragment
*
* 此处隐藏掉 快捷选择通讯录的边栏
*/
public class ContactsFragment extends TFragment {
private void buildLitterIdx(View view) {
LetterIndexView livIndex = (LetterIndexView) view.findViewById(R.id.liv_index);
livIndex.setNormalColor(getResources().getColor(R.color.contacts_letters_color));
ImageView imgBackLetter = (ImageView) view.findViewById(R.id.img_hit_letter);
TextView litterHit = (TextView) view.findViewById(R.id.tv_hit_letter);
litterIdx = adapter.createLivIndex(listView, livIndex, litterHit, imgBackLetter);
// litterIdx.show();
litterIdx.hide();
}
设置好友数量
@Override
protected void onPostLoad(boolean empty, String queryText, boolean all) {
loadingFrame.setVisibility(View.GONE);
int userCount = NimUIKit.getContactProvider().getMyFriendsCount();
// countText.setText("共有好友" + userCount + "名");
countText.setText("");
onReloadCompleted();
}
去除好友数量 Item
ContactsFragment
private void findViews() {
// listView.addFooterView(countLayout); // 注意:addFooter要放在setAdapter之前,否则旧版本手机可能会add不上
/**
* 通讯录数据适配器
*/
public class ContactDataAdapter extends BaseAdapter {
// 显示副标题栏
public class ContactHolder extends AbsContactViewHolder {
// desc.setVisibility(View.GONE);
desc.setVisibility(View.VISIBLE);
/**
* 通讯录Fragment
*/
public class ContactsFragment extends TFragment {
// ios style 滑动效果
// OverScrollDecoratorHelper.setUpOverScroll(listView);
去掉通讯录昵称首字母分组item,直接返回null即可
/**
* 通讯录列表数据抽象类
*/
public abstract class AbsContactDataList {
public AbsContactDataList(ContactGroupStrategy groupStrategy) {
// if (groupStrategy == null) {
// groupStrategy = new NoneGroupStrategy();
// }
// this.groupStrategy = groupStrategy;
// 去掉首字母筛选item,直接返回null 即可
this.groupStrategy = new NoneGroupStrategy();
}
设置扩展字段
使用的地方
public class ContactHolder extends AbsContactViewHolder {
// TODO 此处添加课程名称
// NimUIKit.getContactProvider().getAlias(contact.getContactId());
// courseName.setText("课程来了");
courseName.setText(contact.getAlias());
做扩展的地方
// 获取数据的地方
public class ContactDataProvider implements IContactDataProvider {
private final List provide(int itemType, TextQuery query) {
switch (itemType) {
case ItemTypes.FRIEND:
return UserDataProvider.provideFriends(query);
final class UserDataProvider {
// 添加Friend对象列表扩展
public static List provideFriends(TextQuery query) {
List sources = NIMClient.getService(FriendService.class).getFriends();
List items = new ArrayList<>(sources.size());
for (Friend u : sources) {
items.add(new ContactItem(ContactHelper.makeContactFromFriend(u), ItemTypes.FRIEND));
}
LogUtil.i(UIKitLogTag.CONTACT, "contact provide data size =" + items.size());
return items;
}
// ---------------------- 上述扩展有问题,没有判断sources是否为null ---------------------------------------------
public static List provideFriends(TextQuery query) {
List sources = NIMClient.getService(FriendService.class).getFriends();
if (sources != null) {
List items = new ArrayList<>(sources.size());
for (Friend u : sources) {
items.add(new ContactItem(ContactHelper.makeContactFromFriend(u), ItemTypes.FRIEND));
}
LogUtil.i(UIKitLogTag.CONTACT, "contact provide data size =" + items.size());
return items;
}
// 不能返回null
return new ArrayList();
}
public class ContactHelper {
// 添加Friend对象扩展
public static IContact makeContactFromFriend(final Friend friend) {
return new IContactExt() {
@Override
public String getContactId() {
return friend.getAccount();
}
@Override
public int getContactType() {
return Type.Friend;
}
@Override
public String getDisplayName() {
return UserInfoHelper.getUserDisplayName(friend.getAccount());
}
@Override
public String getAlias() {
return friend.getAlias();
}
@Override
public Map getExtension() {
return friend.getExtension();
}
};
}
/**
*
* author : jake
* e-mail : [email protected]
* time : 2018/06/14
* function : Friend 接口扩展
* version: 1.0
*
*/
public interface IContactExt extends IContact{
interface Type {
/**
* TYPE USER
*/
int Friend = 0x1;
/**
* TYPE TEAM
*/
int Team = 0x2;
/**
* TYPE TEAM MEMBER
*/
int TeamMember = 0x03;
/**
* TYPE_MSG
*/
int Msg = 0x04;
}
/**
* get contact id
*
* @return
*/
String getContactId();
/**
* get contact type {@link Type}
*
* @return
*/
int getContactType();
/**
* get contact's display name to show to user
*
* @return
*/
String getDisplayName();
/**
* 获取 备注名
*
* @return
*/
String getAlias();
/**
* 获取 扩展字段
*
* @return
*/
Map getExtension();
}
最后设置
@Override
public void refresh(ContactDataAdapter adapter, int position, final ContactItem item) {
// contact info
final IContactExt contact =(IContactExt) item.getContact();
if (contact.getContactType() == IContact.Type.Friend) {
head.loadBuddyAvatar(contact.getContactId());
} else {
Team team = NimUIKit.getTeamProvider().getTeamById(contact.getContactId());
head.loadTeamIconByTeam(team);
}
name.setText(contact.getDisplayName());
// TODO 此处添加课程名称
// NimUIKit.getContactProvider().getAlias(contact.getContactId());
// courseName.setText("课程来了");
courseName.setText(contact.getAlias());
就是这里,需要做转换
final IContactExt contact =(IContactExt) item.getContact();
34. 动态权限位置优化-录音按钮切换时校验权限
InputPanel
// TODO 此处校验权限 切换成音频,收起键盘,按钮切换成键盘
private void switchToAudioLayout() {
35. 通讯录,最近会话列表,聊天详情页 添加 课程扩展字段
ContactHolder 通讯录
// TODO 此处添加 与联系人绑定的课程名称
if (contact != null && contact.getExtension() != null && contact.getExtension().get(ImGlobalValue.curriculum) != null) {
courseName.setText(contact.getExtension().get(ImGlobalValue.curriculum).toString());
} else {
courseName.setText("");
}
P2PMessageActivity 1v1聊天详情页
// 获取该好友对象,并且获取到扩展字段,然后拼到标题之中
private void initToolBarDelay() {
TextView title = findView(R.id.tv_toolbar_title);
ImageView close = findView(R.id.iv_toolbar_close);
String course = "";
Friend contact = NIMClient.getService(FriendService.class).getFriendByAccount(sessionId);
if (contact != null && contact.getExtension() != null && contact.getExtension().get(ImGlobalValue.curriculum) != null) {
course = contact.getExtension().get(ImGlobalValue.curriculum).toString();
}
title.setText(UserInfoHelper.getUserTitleName(sessionId, SessionTypeEnum.P2P) + " | " +course);
close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
RecentViewHolder 最近会话列表item
// 设置最近会话列表 昵称 的位置
protected void updateNickLabel(String nick) {
int labelWidth = ScreenUtil.screenWidth;
labelWidth -= ScreenUtil.dip2px(50 + 70); // 减去固定的头像和时间宽度
if (labelWidth > 0) {
tvNickname.setMaxWidth(labelWidth);
}
tvNickname.setText(nick);
}
// 更改完毕后
protected void updateNickLabel(RecentContact contact) {
int labelWidth = ScreenUtil.screenWidth;
labelWidth -= ScreenUtil.dip2px(50 + 70); // 减去固定的头像和时间宽度
if (labelWidth > 0) {
tvNickname.setMaxWidth(labelWidth);
}
String nick = UserInfoHelper.getUserTitleName(contact.getContactId(), contact.getSessionType());
String course = "";
Friend friend = NIMClient.getService(FriendService.class).getFriendByAccount(contact.getContactId());
if (friend != null && friend.getExtension() != null && friend.getExtension().get(ImGlobalValue.curriculum) != null) {
course = friend.getExtension().get(ImGlobalValue.curriculum).toString();
}
tvNickname.setText(nick + " | " + course);
}
36 去除已读和未读的功能
/**
* 会话窗口消息列表项的ViewHolder基类,负责每个消息项的外层框架,包括头像,昵称,发送/接收进度条,重发按钮等。
* 具体的消息展示项可继承该基类,然后完成具体消息内容展示即可。
*/
public abstract class MsgViewHolderBase extends RecyclerViewHolder {
private void setReadReceipt() {
if (shouldDisplayReceipt() && !TextUtils.isEmpty(getMsgAdapter().getUuid()) && message.getUuid().equals(getMsgAdapter().getUuid())) {
// readReceiptTextView.setVisibility(View.VISIBLE);
// 去除已读和未读功能
readReceiptTextView.setVisibility(View.GONE);
} else {
readReceiptTextView.setVisibility(View.GONE);
}
}
37 去除最近回话列表发送消息状态回执
RecentViewHolder
MsgStatusEnum status = recent.getMsgStatus();
switch (status) {
case fail:
imgMsgStatus.setImageResource(R.drawable.nim_g_ic_failed_small);
// imgMsgStatus.setVisibility(View.VISIBLE);
break;
case sending:
imgMsgStatus.setImageResource(R.drawable.nim_recent_contact_ic_sending);
// imgMsgStatus.setVisibility(View.VISIBLE);
break;
default:
imgMsgStatus.setVisibility(View.GONE);
break;
}
38 撤回,对方撤回要提示
注册撤回观察者
MessageIMFragment
private fun registerMsgRevokeObserver() {
NIMClient.getService(MsgServiceObserve::class.java).observeRevokeMessage(NimMessageRevokeObserver(), true)
}
但是华为8.0手机,提示两边,而且和消息提示的弹窗有关,故注释掉新消息弹窗
/**
* 基于RecyclerView的消息收发模块
*/
public class MessageListPanelEx {
// incoming messages tip
IMMessage lastMsg = messages.get(messages.size() - 1);
if (isMyMessage(lastMsg)) {
if (needScrollToBottom) {
doScrollToBottom();
} else if (incomingMsgPrompt != null && lastMsg.getSessionType() != SessionTypeEnum.ChatRoom) {
// incomingMsgPrompt.show(lastMsg); // 就是这里
}
}
*
* 新消息提醒模块
*/
public class IncomingMsgPrompt {
新项目也需要集成
40. 修改最近会话列表中,时间提示改为英文
TimeUtil
// 原来 getTimeShowString
String prefix = gregorianCalendar.get(Calendar.AM_PM) == Calendar.AM ? "上午" : "下午";
if (!currentTime.before(todaybegin)) {
dataString = "今天";
} else if (!currentTime.before(yesterdaybegin)) {
dataString = "昨天";
} else if (!currentTime.before(preyesterday)) {
dataString = "前天";
} else if (isSameWeekDates(currentTime, today)) {
dataString = getWeekOfDate(currentTime);
} else {
SimpleDateFormat dateformatter = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
dataString = dateformatter.format(currentTime);
}
修改为
String prefix = gregorianCalendar.get(Calendar.AM_PM) == Calendar.AM ? "AM" : "PM";
if (!currentTime.before(todaybegin)) {
dataString = "Today";
} else if (!currentTime.before(yesterdaybegin)) {
dataString = "Yesterday";
} else if (isSameWeekDates(currentTime, today)) {
dataString = getWeekOfDate(currentTime);
} else {
SimpleDateFormat dateformatter = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
dataString = dateformatter.format(currentTime);
}
public static String getTodayTimeBucket(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
SimpleDateFormat timeformatter0to11 = new SimpleDateFormat("KK:mm", Locale.getDefault());
SimpleDateFormat timeformatter1to12 = new SimpleDateFormat("hh:mm", Locale.getDefault());
int hour = calendar.get(Calendar.HOUR_OF_DAY);
if (hour >= 0 && hour < 5) {
return "AM " + timeformatter0to11.format(date);
} else if (hour >= 5 && hour < 12) {
return "AM " + timeformatter0to11.format(date);
} else if (hour >= 12 && hour < 18) {
return "PM " + timeformatter1to12.format(date);
} else if (hour >= 18 && hour < 24) {
return "PM " + timeformatter1to12.format(date);
}
return "";
}
41 修改近期会话列表消息提示类型为英文
// RecentViewHolder
MoonUtil.identifyRecentVHFaceExpressionAndTags(holder.getContext(), tvMessage, getContent(recent), -1, 0.45f);
最终定位到
// MoonUtil
public static SpannableString makeSpannableStringTags(Context context, String value, float scale, int align, boolean bTagClickable) {
...
}
// 08-14 16:25:58.992 21036-21036/com.rise.planner E/MoonUtil: makeSpannableStringTags: [图片]
// 从云信那边传过来的就是 已经写死的文本,所以只能自己拆了再重新组装成英文
// 修改的代码
// SpannableString mSpannableString = new SpannableString(value);
SpannableString mSpannableString = tapsToEnglish(value);
// 添加了一个方法
/**
* 来自云信的消息类型
* [图片] 转换成 [Photo]
* [语音消息] 转换成 [Audio]
* [视频] 转换成 [Video]
*
* @param s
*/
private static SpannableString tapsToEnglish(String s) {
String englishTip = "";
switch (s) {
case "[图片]":
englishTip = "[Photo]";
break;
case "[语音消息]":
englishTip = "[Audio]";
break;
case "[视频]":
englishTip = "[Video]";
break;
default:
englishTip = s;
break;
}
return new SpannableString(englishTip);
}
42 设置默认头像,添加扩展方法
MsgViewHolderBase 单聊 (群聊在ChatRoomMsgViewHolderBase)
private void setHeadImageView() {
HeadImageView show = isReceivedMessage() ? avatarLeft : avatarRight;
HeadImageView hide = isReceivedMessage() ? avatarRight : avatarLeft;
hide.setVisibility(View.GONE);
if (!isShowHeadImage()) {
show.setVisibility(View.GONE);
return;
}
if (isMiddleItem()) {
show.setVisibility(View.GONE);
} else {
show.setVisibility(View.VISIBLE);
// show.loadBuddyAvatar(message);
show.loadAvatarByMsgIsLeft(message,isReceivedMessage());
}
}
方法扩展 HeadImageView
/**
* 加载用户头像(默认大小的缩略图)
*
* 根据来自左边 ,还是右边,更改默认头像
*
* @param account 用户账号
*/
public void loadAvatarByAccountIsLeft(String account,Boolean isLeft) {
if (isLeft){
DEFAULT_AVATAR_RES_ID = R.drawable.nim_avatar_student;
}else {
DEFAULT_AVATAR_RES_ID = R.drawable.nim_avatar_default;
}
loadBuddyAvatar(account);
}
/**
* 加载用户头像(默认大小的缩略图)
*
* 根据来自左边 ,还是右边,更改默认头像
*
*/
public void loadAvatarByMsgIsLeft(IMMessage message,Boolean isLeft) {
if (isLeft){
DEFAULT_AVATAR_RES_ID = R.drawable.nim_avatar_student;
}else {
DEFAULT_AVATAR_RES_ID = R.drawable.nim_avatar_default;
}
loadBuddyAvatar(message);
}
更改云信tips背景
nim_message_item.xml
也就是改这个颜色 android:background="@drawable/nim_bg_message_tip"
其他
NIMSDK :整个SDK的主入口,单例,主要提供初始化,注册,内部管理类管理的功能。
NIMLoginManager:登录管理类,负责登录,注销和相应的回调收发
NIMChatManager: 聊天管理类,负责消息的收发
NIMConversationManager :会话管理类,负责消息,最近会话的管理
NIMTeamManager 群组管理类,负责群组各种操作
NIMMediaManager 媒体管理类,负责多媒体相关的接口,比如录音
NIMSystemNotificationManager 系统通知管理类,负责系统消息的接收和存储
NIMApnsManager 推送管理类,负责推送的设置和接收
NIMResourceManager 资源管理类,负责文件的上传和下载
NIMUserManager 好友管理类,负责对好友的增删查,以及对其会话的消息设置
NIMChatroomManager 聊天室管理类,负责聊天室状态管理和数据拉取及设置
NIMDocTranscodingManager 文档转码管理类,负责文档转码的查询和删除等
NIMAVChat 主要提供了如下类(协议)与方法
NIMAVChat 是 NIMSDK 的音视频和实时会话扩展,封装了网络通话、实时会话和网络探测等的管理
NIMNetCallManager 音视频网络通话管理类,提供音视频网络通话功能
NIMRTSManager 实时会话管理类,提供数据通道 (TCP/语音通道) 来满足实时会话的需求
NIMRTSConferenceManager 多人实时会话管理类,提供多人数据通道 (TCP) 来满足多人实时会话的需求
NIMAVChatNetDetectManager 音视频网络探测管理类,提供音视频网络状态诊断功能