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
我已經(jīng)在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)
}
后來在源碼中發(fā)現(xiàn)如下注釋:
/**
* 獲取當前登錄的賬號
*
* @return 必須登錄成功后才有值
*/
public static String getAccount() {
return NimUIKitImpl.getAccount();
}
于是,我在登錄成功的回調(diào)里做了如下處理:
// 登錄成功后在此處調(diào)用 IM 的成功回調(diào)胜嗓,傳入account高职,之后 NimUIKit.getAccount() 才會有值,與初始化傳入無關(guān)
NimUIKit.loginSuccess(account)
附辞州,我的登錄回調(diào)代碼:
/**
* 用戶登陸
*/
fun accountLogin(phone: String, pwd: String): LiveData<Resource<LoginMutation.Data>> {
return object : NetworkResource<LoginMutation.Data>() {
override fun createCall(): ApolloCall<LoginMutation.Data> {
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
// 登錄成功后在此處調(diào)用 IM 的成功回調(diào)初厚,傳入account,之后 NimUIKit.getAccount() 才會有值孙技,與初始化傳入無關(guān)
NimUIKit.loginSuccess(account)
loginId = response.createUserToken()?.id() //保存登錄id
loginToken = response.createUserToken()?.token()?.token() //保存登錄Token
}
return super.processResponse(response)
}
}.asLiveData
}
3. 長按消息撤回或者轉(zhuǎn)發(fā)時产禾,報錯
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下列方式,未注冊 消息撤回過濾器 和 消息轉(zhuǎn)發(fā)器
NimUIKit.startP2PSession(context, account)
官方Demo中是使用下列方式調(diào)用的
SessionHelper.startP2PSession(this, message.getSessionId());
而在SessionHelper中注冊了:
/**
* 消息轉(zhuǎn)發(fā)過濾器
*/
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)) {
// 接收到的消息牵啦,附件沒有下載成功亚情,不允許轉(zhuǎn)發(fā)
return true;
} else if (message.getMsgType() == MsgTypeEnum.custom && message.getAttachment() != null
&& (message.getAttachment() instanceof SnapChatAttachment
|| message.getAttachment() instanceof RTSAttachment
|| message.getAttachment() instanceof RedPacketAttachment)) {
// 白板消息和閱后即焚消息,紅包消息 不允許轉(zhuǎn)發(fā)
return true;
} else if (message.getMsgType() == MsgTypeEnum.robot && message.getAttachment() != null && ((RobotAttachment) message.getAttachment()).isRobotSend()) {
return true; // 如果是機器人發(fā)送的消息 不支持轉(zhuǎn)發(fā)
}
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())) {
// 發(fā)給我的電腦 不允許撤回
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) {
// 發(fā)給我的電腦 不允許撤回
return@MsgRevokeFilter true
}
false
})
}
4. 撤回報錯衫生,需要注冊 MsgViewHolderTip
BaseMessageActivity
NimUIKit.registerTipMsgViewHolder(MsgViewHolderTip.class);
無法顯示該內(nèi)容
與之相關(guān)的類還有:
修改聊天消息中的字體,音頻土浸,視頻間距
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));
}
}
另外罪针,如果圖片或者視頻有內(nèi)容框要去掉(一般都是 .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添加超鏈接泪酱,有色差,不顯示特殊數(shù)字(跳轉(zhuǎn)到撥號頁)
修改添加下劃線的字體的顏色
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"
<TextView
android:id="@+id/nim_message_item_text_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="phone|email|web"
android:gravity="center_vertical|left"
android:includeFontPadding="false"
android:lineSpacingExtra="3dip"
android:maxWidth="@dimen/max_text_bubble_width"
android:textColor="@color/color_black_b3000000"
android:textSize="16sp"/>
重點類
1. CropImageActivity (裁剪圖片)
2. PickerAlbumPreviewActivity(設置選中照片的數(shù)量)
private void setTitleIndex(int index) {
if (totalSize <= 0) {
setTitle("");
} else {
index++;
setTitle(index + "/" + totalSize);
}
}
3. CircleImageView (修改圓頭像為圓角頭像)
但是我們不能直接在CircleImageView中修改墓阀,不利于拓展,會造成困惑拓轻,應該創(chuàng)建一個新類斯撮,改變繼承關(guān)系
@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);//第二個參數(shù)是x半徑,第三個參數(shù)是y半徑
}
另外扶叉,如果需要和app里的頭像同步勿锅,需要后端和云信打通,你可以在這里看到加載的頭像url枣氧,以便于調(diào)試
HeadImageView
private void doLoadImage(final String url, final int defaultResId, final int thumbSize) {
/*
* 若使用網(wǎng)易云信云存儲溢十,這里可以設置下載圖片的壓縮尺寸,生成下載URL
* 如果圖片來源是非網(wǎng)易云信云存儲作瞄,請不要使用NosThumbImageUtil
*/
final String thumbUrl = makeAvatarThumbNosUrl(url, thumbSize);
}
4. 聊天消息中的圖片&視頻圓角太大,要修改小一點
/**
MsgViewHolderBase
* 會話窗口消息列表項的ViewHolder基類危纫,負責每個消息項的外層框架宗挥,包括頭像,昵稱种蝶,發(fā)送/接收進度條契耿,重發(fā)按鈕等。<br>
* 具體的消息展示項可繼承該基類螃征,然后完成具體消息內(nèi)容展示即可搪桂。
*/
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
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_grey_eaeaea" />
<!--<corners android:topLeftRadius="15dp"-->
<!--android:topRightRadius="15dp"-->
<!--android:bottomRightRadius="15dp"-->
<!--android:bottomLeftRadius="15dp"/>-->
<!-- 此處用于聊天消息圖片&視頻的圓角 -->
<corners android:topLeftRadius="4dp"
android:topRightRadius="4dp"
android:bottomRightRadius="4dp"
android:bottomLeftRadius="4dp"/>
</shape>
相關(guān)基類:
ps: 高效的定位方式,直接找其用到的資源(最好是圖片盯滚,這樣可以在文件列表里踢械,快速定位,然后通過Find Usages找到使用的地方魄藕,然后按圖索驥)
5. 修改發(fā)送消息控制面板內(nèi)容
MessageFragment
// 操作面板集合
protected List<BaseAction> getActionList() {
List<BaseAction> 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 {
// 最重要的一段代碼内列,實現(xiàn)了相關(guān)功能的跳轉(zhuǎn)
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();
}
});
如果想在其他地方接入上述功能,需要一下步驟;
- 首先注冊相關(guān)模塊
// 操作面板集合
protected List<BaseAction> getActionList() {
List<BaseAction> 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;
}
- 在需要使用的地方背率,比如按鈕的點擊監(jiān)聽
// InputPanel
// 記住相應模塊的在上面list中的position话瞧,即可
else if (v == cameraImgeView) {
actions.get(1).onClick();
6. 控制面板功能修改
原功能:
1. 拍照>>拍照&選擇照片
2. 錄像>>錄像&選擇視頻
3. 無中間的拍照按鈕
新功能:
1. 拍照>>選擇照片
2. 錄像>>選擇視頻
3. 中間拍照按鈕>>拍照&錄像
實現(xiàn)新功能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);
}
}
實現(xiàn)新功能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 關(guān)于群聊,此處還未做兼容選擇視頻都许,故先注釋掉
// new PickImageHelper().pickImage(AdvancedTeamInfoActivity.this, requestCode, option);
}
// 其次稻薇,在下面文件中添加video需要的相關(guān)方法
/**
* update by jake on 2018/6/23
* 修改dialog入口為 進入本地相冊&視頻 及 該方法以下所有內(nèi)容
*/
public class PickImageHelper {
// 添加自 videoMessageHelper
dialog.addItem(context.getString(R.string.choose_from_video_album), new CustomAlertDialog.onSeparateItemClickListener() {
@Override
public void onClick() {
chooseVideoFromLocal();
}
});
// 最后,添加了回掉監(jiān)聽胶征,傳入了localRequestCode參數(shù)塞椎,未使用匿名對象
public abstract class PickImageAction extends BaseAction {
private void showSelector(int titleId, final int requestCode, final boolean multiSelect, final String outPath) {
// TODO 此處傳入關(guān)于 選擇視頻的監(jiān)聽,所以更改了構(gòu)造器
// new PickImageHelper().pickImage(getActivity(), requestCode, option);
// TODO 此處不可用匿名函數(shù)睛低,因為下面回掉要用到案狠,如果用匿名函數(shù),則導致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 發(fā)送原圖界面
PickerAlbumPreviewActivity
9 設置最大錄音時間
UIKitOptions
/**
* 錄音時長限制,單位秒罩抗,默認最長120s
*/
public int audioRecordMaxTime = 120;
10 消息通知
private void initNotificationConfig() {
// 初始化消息提醒
NIMClient.toggleNotification(UserPreferences.getNotificationToggle());
// 加載狀態(tài)欄配置
StatusBarNotificationConfig statusBarNotificationConfig = UserPreferences.getStatusConfig();
if (statusBarNotificationConfig == null) {
statusBarNotificationConfig = DemoCache.getNotificationConfig();
UserPreferences.setStatusConfig(statusBarNotificationConfig);
}
// 更新配置
NIMClient.updateStatusBarNotificationConfig(statusBarNotificationConfig);
}
11. 消息撤回拉庵,復制,刪除套蒂,轉(zhuǎn)文字等功能
MessageListPanelEx
// 長按消息item的菜單項準備钞支。如果消息item的MsgViewHolder處理長按事件(MsgViewHolderBase#onItemLongClick),且返回為true,
// 則對應項的長按事件不會調(diào)用到此處
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. 設置最大錄制視頻時間
/**
* 視頻錄制界面
* <p/>
* 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不可撤銷操刀,(點擊其他區(qū)域不消失)
/**
* 視頻錄制界面
*/
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
/**
* 注冊未讀消息數(shù)量觀察者
*/
private void registerMsgUnreadInfoObserver(boolean register) {
if (register) {
ReminderManager.getInstance().registerUnreadNumChangedCallback(this);
} else {
ReminderManager.getInstance().unregisterUnreadNumChangedCallback(this);
}
}
/**
* 未讀消息數(shù)量觀察者實現(xiàn)
*/
@Override
public void onUnreadNumChanged(ReminderItem item) {
MainTab tab = MainTab.fromReminderId(item.getId());
if (tab != null) {
tabs.updateTab(tab.tabIndex, item);
}
}
/**
* 注冊/注銷系統(tǒng)消息未讀數(shù)變化
*
* @param register
*/
private void registerSystemMessageObservers(boolean register) {
NIMClient.getService(SystemMessageObserver.class).observeUnreadCountChange(sysMsgUnreadCountChangedObserver,
register);
}
private Observer<Integer> sysMsgUnreadCountChangedObserver = new Observer<Integer>() {
@Override
public void onEvent(Integer unreadCount) {
SystemMessageUnreadManager.getInstance().setSysMsgUnreadCount(unreadCount);
ReminderManager.getInstance().updateContactUnreadNum(unreadCount);
}
};
/**
* 查詢系統(tǒng)消息未讀數(shù)
*/
private void requestSystemMessageUnreadCount() {
int unread = NIMClient.getService(SystemMessageService.class).querySystemMessageUnreadCountBlock();
SystemMessageUnreadManager.getInstance().setSysMsgUnreadCount(unread);
ReminderManager.getInstance().updateContactUnreadNum(unread);
}
/**
* 懸浮在屏幕上的紅點拖拽動畫繪制區(qū)域
*/
public class DropCover extends View {}
/**
* TAB紅點提醒管理器
* Created by huangjun on 2015/3/18.
*/
public class ReminderManager {}
/**
* 未讀數(shù)紅點View(自繪紅色的圓和數(shù)字)
* 觸摸之產(chǎn)生DOWN/MOVE/UP事件(不允許父容器處理TouchEvent),回調(diào)給浮在上層的DropCover進行拖拽過程繪制骨坑。
* View啟動過程:Constructors -> onAttachedToWindow -> onMeasure() -> onSizeChanged() -> onLayout() -> onDraw()
* <p>
* Created by huangjun on 2016/9/13.
*/
public class DropFake extends View {}
16. 獲取會話列表
/**
* 最近聯(lián)系人列表(會話列表)
*/
public class RecentContactsFragment extends TFragment {}
// 更改布局
public RecentContactAdapter(RecyclerView recyclerView, List<RecentContact> 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. 個人名片(包括添加撼嗓,刪除,發(fā)起會話等功能)
/**
* 用戶資料頁面
*/
public class UserProfileActivity extends UI {
18. 系統(tǒng)消息列表頁(驗證消息等)
/**
* 系統(tǒng)消息中心界面
*/
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<AbsContactItem> provide(TextQuery query) {
List<UserInfo> sources = query(query);
List<AbsContactItem> 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<UserInfo> query(TextQuery query) {
List<String> friends = NimUIKit.getContactProvider().getUserInfoOfMyFriends();
List<UserInfo> users = NimUIKit.getUserInfoProvider().getUserInfo(friends);
if (query == null) {
return users;
}
UserInfo user;
for (Iterator<UserInfo> 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<Void>() {
@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 需要的用戶信息提供者,現(xiàn)主要用于內(nèi)置通知提醒獲取昵稱和頭像
* <p>
* 注意不要與 IUserInfoProvider 混淆亡脸,后者是 UIKit 與 demo 之間的數(shù)據(jù)共享接口
* <p>
*/
public class NimUserInfoProvider implements UserInfoProvider {
@Override
public Bitmap getAvatarForMessageNotifier(SessionTypeEnum sessionType, String sessionId) {
/*
* 注意:這里最好從緩存里拿押搪,如果加載時間過長會導致通知欄延遲彈出树酪!該函數(shù)在后臺線程執(zhí)行!
*/
Bitmap bm = null;
int defResId = R.drawable.nim_avatar_default;
}
}
23. 配置通知欄跳轉(zhuǎn)的頁面
NimSDKOptionConfig
// 這里開發(fā)者可以自定義該應用初始的 StatusBarNotificationConfig
private static StatusBarNotificationConfig loadStatusBarNotificationConfig() {
StatusBarNotificationConfig config = new StatusBarNotificationConfig();
// TODO 點擊通知需要跳轉(zhuǎn)到的界面
// 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顯示未讀數(shù)紅點(Android O有效)
config.showBadge = true;
// save cache大州,留做切換賬號備用
RiseImCache.setNotificationConfig(config);
return config;
}
24. 初始化在線狀態(tài)事件
/**
* 用于初始化時续语,注冊全局的廣播、云信觀察者等等云信相關(guān)業(yè)務
*/
public class NIMInitManager {
// OnlineStateEventManager.init();
25. 設置 通知欄 跳轉(zhuǎn)到對應聊天界面
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<Void>() {
// @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設置未讀消息數(shù)
自己代碼 HomePagerAdapter
// 只在家校溝通模塊下顯示未讀消息數(shù)
val unread = v.findViewById<TextView>(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
發(fā)送消息的位置 MessageIMFragment
// 回傳未讀數(shù)與item條數(shù) , 設置標題(數(shù)量是會話數(shù)+瑞思公告+系統(tǒng)通知)
override fun unReadCount(unreadNum: Int, items: MutableList<RecentContact>, titleTXT: TextView) {
super.unReadCount(unreadNum, items, titleTXT)
// TODO 后續(xù)接入系統(tǒng)公告推送根暑,需要加上 未讀消息數(shù)量
titleTXT.text = stringForRes(R.string.message_list_title) + "(" + unreadNum + ")"
// 發(fā)送一個通知到Tab力试,更新紅點數(shù)目
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. 去掉列表(數(shù)據(jù)不滿屏)彈力滑動效果(仿iOS效果)
RecentContactsFragment 需求要求實現(xiàn)滑動特效
// 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. 添加選擇照片視頻拍攝的監(jiān)聽 及 新需求布局
布局 nim_message_activity_text_layout
// 類名 InputPanel
// TODO 此處添加選擇照片視頻拍攝的監(jiān)聽
else if (v == cameraImgeView){
}else if (v == photoImgeView){
}
32. 跳轉(zhuǎn)到圖片&視頻選擇頁
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. 通訊錄相關(guān)類
/**
* 通訊錄Fragment
*/
public class ContactsFragment extends TFragment {
內(nèi)容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();
}
設置好友數(shù)量
@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();
}
去除好友數(shù)量 Item
ContactsFragment
private void findViews() {
// listView.addFooterView(countLayout); // 注意:addFooter要放在setAdapter之前淳地,否則舊版本手機可能會add不上
/**
* 通訊錄數(shù)據(jù)適配器
*/
public class ContactDataAdapter extends BaseAdapter {
// 顯示副標題欄
public class ContactHolder extends AbsContactViewHolder<ContactItem> {
// desc.setVisibility(View.GONE);
desc.setVisibility(View.VISIBLE);
/**
* 通訊錄Fragment
*/
public class ContactsFragment extends TFragment {
// ios style 滑動效果
// OverScrollDecoratorHelper.setUpOverScroll(listView);
去掉通訊錄昵稱首字母分組item怖糊,直接返回null即可
/**
* 通訊錄列表數(shù)據(jù)抽象類
*/
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<ContactItem> {
// TODO 此處添加課程名稱
// NimUIKit.getContactProvider().getAlias(contact.getContactId());
// courseName.setText("課程來了");
courseName.setText(contact.getAlias());
做擴展的地方
// 獲取數(shù)據(jù)的地方
public class ContactDataProvider implements IContactDataProvider {
private final List<AbsContactItem> provide(int itemType, TextQuery query) {
switch (itemType) {
case ItemTypes.FRIEND:
return UserDataProvider.provideFriends(query);
final class UserDataProvider {
// 添加Friend對象列表擴展
public static List<AbsContactItem> provideFriends(TextQuery query) {
List<Friend> sources = NIMClient.getService(FriendService.class).getFriends();
List<AbsContactItem> 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<AbsContactItem> provideFriends(TextQuery query) {
List<Friend> sources = NIMClient.getService(FriendService.class).getFriends();
if (sources != null) {
List<AbsContactItem> 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<AbsContactItem>();
}
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<String, Object> getExtension() {
return friend.getExtension();
}
};
}
/**
* <pre>
* author : jake
* e-mail : hongjiewang@rdchina.net
* time : 2018/06/14
* function : Friend 接口擴展
* version: 1.0
* </pre>
*/
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());
就是這里伍伤,需要做轉(zhuǎn)換
final IContactExt contact =(IContactExt) item.getContact();
34. 動態(tài)權(quán)限位置優(yōu)化-錄音按鈕切換時校驗權(quán)限
InputPanel
// TODO 此處校驗權(quán)限 切換成音頻,收起鍵盤遣钳,按鈕切換成鍵盤
private void switchToAudioLayout() {
35. 通訊錄扰魂,最近會話列表,聊天詳情頁 添加 課程擴展字段
ContactHolder 通訊錄
// TODO 此處添加 與聯(lián)系人綁定的課程名稱
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基類,負責每個消息項的外層框架荐开,包括頭像付翁,昵稱简肴,發(fā)送/接收進度條晃听,重發(fā)按鈕等。<br>
* 具體的消息展示項可繼承該基類砰识,然后完成具體消息內(nèi)容展示即可能扒。
*/
public abstract class MsgViewHolderBase extends RecyclerViewHolder<BaseMultiItemFetchLoadAdapter, BaseViewHolder, IMMessage> {
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 去除最近回話列表發(fā)送消息狀態(tài)回執(zhí)
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手機辫狼,提示兩邊初斑,而且和消息提示的彈窗有關(guān),故注釋掉新消息彈窗
/**
* 基于RecyclerView的消息收發(fā)模塊
*/
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: [圖片]
// 從云信那邊傳過來的就是 已經(jīng)寫死的文本见秤,所以只能自己拆了再重新組裝成英文
// 修改的代碼
// SpannableString mSpannableString = new SpannableString(value);
SpannableString mSpannableString = tapsToEnglish(value);
// 添加了一個方法
/**
* 來自云信的消息類型
* [圖片] 轉(zhuǎn)換成 [Photo]
* [語音消息] 轉(zhuǎn)換成 [Audio]
* [視頻] 轉(zhuǎn)換成 [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
/**
* 加載用戶頭像(默認大小的縮略圖)
*
* 根據(jù)來自左邊 ,還是右邊鹃答,更改默認頭像
*
* @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);
}
/**
* 加載用戶頭像(默認大小的縮略圖)
*
* 根據(jù)來自左邊 乎澄,還是右邊,更改默認頭像
*
*/
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
<TextView
android:id="@+id/message_item_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/bubble_time_layout_margin_bottom"
android:layout_marginTop="@dimen/bubble_time_layout_margin_top"
android:background="@drawable/nim_bg_message_tip"
android:paddingLeft="7dip"
android:paddingRight="7dip"
android:textColor="#ffffff"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="gone" />
也就是改這個顏色 android:background="@drawable/nim_bg_message_tip"
其他
NIMSDK :整個SDK的主入口测摔,單例置济,主要提供初始化,注冊锋八,內(nèi)部管理類管理的功能浙于。
NIMLoginManager:登錄管理類,負責登錄挟纱,注銷和相應的回調(diào)收發(fā)
NIMChatManager: 聊天管理類羞酗,負責消息的收發(fā)
NIMConversationManager :會話管理類,負責消息樊销,最近會話的管理
NIMTeamManager 群組管理類整慎,負責群組各種操作
NIMMediaManager 媒體管理類,負責多媒體相關(guān)的接口围苫,比如錄音
NIMSystemNotificationManager 系統(tǒng)通知管理類裤园,負責系統(tǒng)消息的接收和存儲
NIMApnsManager 推送管理類,負責推送的設置和接收
NIMResourceManager 資源管理類剂府,負責文件的上傳和下載
NIMUserManager 好友管理類拧揽,負責對好友的增刪查,以及對其會話的消息設置
NIMChatroomManager 聊天室管理類腺占,負責聊天室狀態(tài)管理和數(shù)據(jù)拉取及設置
NIMDocTranscodingManager 文檔轉(zhuǎn)碼管理類淤袜,負責文檔轉(zhuǎn)碼的查詢和刪除等
NIMAVChat 主要提供了如下類(協(xié)議)與方法
NIMAVChat 是 NIMSDK 的音視頻和實時會話擴展,封裝了網(wǎng)絡通話衰伯、實時會話和網(wǎng)絡探測等的管理
NIMNetCallManager 音視頻網(wǎng)絡通話管理類铡羡,提供音視頻網(wǎng)絡通話功能
NIMRTSManager 實時會話管理類,提供數(shù)據(jù)通道 (TCP/語音通道) 來滿足實時會話的需求
NIMRTSConferenceManager 多人實時會話管理類意鲸,提供多人數(shù)據(jù)通道 (TCP) 來滿足多人實時會話的需求
NIMAVChatNetDetectManager 音視頻網(wǎng)絡探測管理類烦周,提供音視頻網(wǎng)絡狀態(tài)診斷功能