網(wǎng)易云信IM 集成&關(guān)鍵類 總結(jié)

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]]
Didn't find class "com.netease.nrtc.engine.rawapi.IRtcEngine

原因:同時集成了音視頻的庫当犯,但是沒有集成完全,少庫了。
解決方案:因為只用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)的類還有:
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));
        }
    }

另外罪针,如果圖片或者視頻有內(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"/>
實際上是有數(shù)字的

重點類

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)基類:


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

如果想在其他地方接入上述功能,需要一下步驟;
  1. 首先注冊相關(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;
    }
  1. 在需要使用的地方背率,比如按鈕的點擊監(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)診斷功能

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市怎顾,隨后出現(xiàn)的幾起案子读慎,更是在濱河造成了極大的恐慌,老刑警劉巖槐雾,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夭委,死亡現(xiàn)場離奇詭異,居然都是意外死亡募强,警方通過查閱死者的電腦和手機株灸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門崇摄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慌烧,你說我怎么就攤上這事配猫。” “怎么了杏死?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵泵肄,是天一觀的道長。 經(jīng)常有香客問我淑翼,道長腐巢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任玄括,我火速辦了婚禮冯丙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遭京。我一直安慰自己胃惜,他們只是感情好,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布哪雕。 她就那樣靜靜地躺著船殉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪斯嚎。 梳的紋絲不亂的頭發(fā)上利虫,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音堡僻,去河邊找鬼糠惫。 笑死,一個胖子當著我的面吹牛钉疫,可吹牛的內(nèi)容都是我干的嚎幸。 我是一名探鬼主播赠橙,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼电湘,長吁一口氣:“原來是場噩夢啊……” “哼金蜀!你這毒婦竟也來了诈火?” 一聲冷哼從身側(cè)響起究恤,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤承桥,失蹤者是張志新(化名)和其女友劉穎原在,沒想到半個月后柒爵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體役电,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年棉胀,在試婚紗的時候發(fā)現(xiàn)自己被綠了法瑟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冀膝。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖霎挟,靈堂內(nèi)的尸體忽然破棺而出窝剖,到底是詐尸還是另有隱情,我是刑警寧澤酥夭,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布赐纱,位于F島的核電站,受9級特大地震影響熬北,放射性物質(zhì)發(fā)生泄漏疙描。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一讶隐、第九天 我趴在偏房一處隱蔽的房頂上張望起胰。 院中可真熱鬧,春花似錦巫延、人聲如沸效五。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畏妖。三九已至,卻和暖如春疼阔,著一層夾襖步出監(jiān)牢的瞬間瓜客,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工竿开, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谱仪,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓否彩,卻偏偏與公主長得像疯攒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子列荔,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容