网易云信IM 集成&关键类 总结

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 集成&关键类 总结_第1张图片
Didn't find class "com.netease.nrtc.engine.rawapi.IRtcEngine

原因:同时集成了音视频的库,但是没有集成完全,少库了。
解决方案:因为只用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);
网易云信IM 集成&关键类 总结_第2张图片
无法显示该内容
与之相关的类还有:
网易云信IM 集成&关键类 总结_第3张图片
image.png

修改聊天消息中的字体,音频,视频间距

    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());
            }
        }
网易云信IM 集成&关键类 总结_第4张图片
注释掉这两行代码

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"
    
网易云信IM 集成&关键类 总结_第5张图片
实际上是有数字的

重点类

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



    
    
             
             
             
    
    

相关基类:


网易云信IM 集成&关键类 总结_第6张图片
相关基类

ps: 高效的定位方式,直接找其用到的资源(最好是图片,这样可以在文件列表里,快速定位,然后通过Find Usages找到使用的地方,然后按图索骥)

5. 修改发送消息控制面板内容

网易云信IM 集成&关键类 总结_第7张图片
消息面板

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

如果想在其他地方接入上述功能,需要一下步骤;
  1. 首先注册相关模块
  // 操作面板集合
    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;
    }
  1. 在需要使用的地方,比如按钮的点击监听
// 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


网易云信IM 集成&关键类 总结_第8张图片
将头像样式由方圆角改为 圆形头像 ,只需要改这里就可以了

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 音视频网络探测管理类,提供音视频网络状态诊断功能

你可能感兴趣的:(网易云信IM 集成&关键类 总结)