集成融云Android SDK实现在群聊/讨论组中@人的功能

集成融云Android SDK实现在群聊/讨论组中@人的功能

可以确定的是融云SDK本身不提供@的功能,需要自定义实现。
在实现这个功能时,基本模仿微信的做法:

  • 在列表中显示有人@了你
  • 通知显示有人@了你
  • 群聊中输入框输入@时弹出群成员列表,选择要@的人
  • 键盘回删的时候,不可以弹出成员列表
  • 这消息未读时,有人@了你一直在列表中显示,包括程序杀死的情况
  • 长按头像实现@人的功能

实现的逻辑

1.消息发送方:

发送@消息本身是个普通的文本消息,为了要明确@的人,在消息的extra中添加被@人的id数组(可以@多人)。
具体规则:

  • @弹出成员列表时,每点击一个成员,则用List进行保存(因为需要get某个id,所以没办法使用set)
  • 在调用融云发送消息的时,判断这个列表是否有id,如果有,则为文本消息setExtra(ids);

2.接收方:

判定规则:

  • 首先判断是否是群消息,是
  • 判断是否是文本消息,是
  • 判断是否包含@,是
  • 判断文本消息中extra是否你的id,是

这个时候可以判定你收到了@消息,然后就是具体显示的问题了:
将这条消息的群id保存到一个set中,如果消息已读,则把id移除

写了这么多,感觉有点废话,直接上代码吧


具体实现

1.自定义群消息provider(列表中的),这个在融云的demo中有,是讨论组provider,拿过来稍微改一下

@ConversationProviderTag(conversationType = "group", portraitPosition = 1)
public class GroupConversationProvider implements IContainerItemProvider.ConversationProvider<UIConversation> {
    private static int i = 0;
    private String TAG = GroupConversationProvider.class.getSimpleName();

    class ViewHolder {

        TextView title;
        TextView time;
        TextView content;
        ImageView notificationBlockImage;
        TextView atMe;
        final GroupConversationProvider provider;

        ViewHolder() {
            provider = GroupConversationProvider.this;
        }
    }

    public GroupConversationProvider() {

    }

    @Override
    public void bindView(View view, int position, UIConversation data) {

        ViewHolder holder = (ViewHolder) view.getTag();
        ProviderTag tag = null;

        if (data == null) {
            holder.title.setText(null);
            holder.time.setText(null);
            holder.content.setText(null);
        } else {
            //设置会话标题
            holder.title.setText(data.getUIConversationTitle());
            //设置会话时间
            String time = RongDateUtils.getConversationListFormatDate(new Date(data.getUIConversationTime()));
            holder.time.setText(time);
            //设置内容
            if (!TextUtils.isEmpty(data.getDraft())) {
                SpannableStringBuilder builder = new SpannableStringBuilder();
                SpannableString string = new SpannableString("[草稿]");
                string.setSpan(new ForegroundColorSpan(Color.parseColor("#cb120f")), 0, string.length(), 33);

                if(data.getDraft().toString().substring(data.getDraft().toString().length() - 1, data.getDraft().toString().length()).equals("@")){
                    data.setDraft(data.getDraft().toString().substring(0,data.getDraft().toString().length()-1));
                }
                builder.append(string).append(data.getDraft());
                AndroidEmoji.ensure(builder);
                holder.content.setText(builder);
            } else {
                setDateView(holder, data);
                holder.content.setText(data.getConversationContent());
            }
            if (RongContext.getInstance() != null && data.getMessageContent() != null)
                tag = RongContext.getInstance().getMessageProviderTag(data.getMessageContent().getClass());
            if (data.getSentStatus() != null && (data.getSentStatus() == io.rong.imlib.model.Message.SentStatus.FAILED || data.getSentStatus() == io.rong.imlib.model.Message.SentStatus.SENDING) && tag != null && tag.showWarning()) {
                int width = ViewUtils.dp2px(17);
                Drawable drawable = null;
                if (data.getSentStatus() == io.rong.imlib.model.Message.SentStatus.FAILED)
                    drawable = view.getContext().getResources().getDrawable(R.drawable.de_conversation_list_msg_send_failure);
                else if (data.getSentStatus() == io.rong.imlib.model.Message.SentStatus.SENDING)
                    drawable = view.getContext().getResources().getDrawable(R.drawable.de_conversation_list_msg_sending);
                if (drawable != null) {
                    drawable.setBounds(0, 0, width, width);
                    holder.content.setCompoundDrawablePadding(10);
                    holder.content.setCompoundDrawables(drawable, null, null, null);
                }
            } else {
                holder.content.setCompoundDrawables(null, null, null, null);
            }
            ConversationKey key = ConversationKey.obtain(data.getConversationTargetId(), data.getConversationType());
            io.rong.imlib.model.Conversation.ConversationNotificationStatus status = RongContext.getInstance().getConversationNotifyStatusFromCache(key);
            if (status != null && status.equals(io.rong.imlib.model.Conversation.ConversationNotificationStatus.DO_NOT_DISTURB))
                holder.notificationBlockImage.setVisibility(View.VISIBLE);
            else
                holder.notificationBlockImage.setVisibility(View.GONE);
        }
    }

    /**
     * @param holder
     * @param data
     * @ 消息提示
     */
    private void setDateView(ViewHolder holder, UIConversation data) {
        if (AtUserService.getInstance().getAtGroupIds() != null && AtUserService.getInstance().getAtGroupIds().size() > 0
                &&AtUserService.getInstance().getAtGroupIds().contains(data.getConversationTargetId())) {
            if (data.getUnReadMessageCount() == 0) {
                holder.atMe.setVisibility(View.GONE);
                data.setExtraFlag(false);
            } else if (data.getUnReadMessageCount() > 0) {
                holder.atMe.setVisibility(View.VISIBLE);
                data.setExtraFlag(true);
            }
        } else {
            if (data.getExtraFlag()) {
                holder.atMe.setVisibility(View.VISIBLE);
            } else {
                holder.atMe.setVisibility(View.GONE);
                data.setExtraFlag(false);
            }
            if (data.getUnReadMessageCount() == 0) {
                holder.atMe.setVisibility(View.GONE);
                data.setExtraFlag(false);
            }
        }
    }

    @Override
    public View newView(Context context, ViewGroup viewgroup) {
        View result = LayoutInflater.from(context).inflate(R.layout.de_item_base_conversation, null);
        ViewHolder holder = new ViewHolder();
        holder.title = (TextView) result.findViewById(R.id.de_conversation_title);
        holder.time = (TextView) result.findViewById(R.id.de_conversation_time);
        holder.content = (TextView) result.findViewById(R.id.de_conversation_content);
        holder.notificationBlockImage = (ImageView) result.findViewById(R.id.de_conversation_msg_block);
        holder.atMe = (TextView) result.findViewById(R.id.de_at_me);
        result.setTag(holder);
        return result;
    }

    @Override
    public String getTitle(String s) {
        String name;
        if (RongContext.getInstance().getGroupInfoFromCache(s) == null)
            name = "群组";
        else{
            name = RongContext.getInstance().getGroupInfoFromCache(s).getName();
        }

        return name;
    }

    @Override
    public Uri getPortraitUri(String s) {
        Uri uri;
        if (RongContext.getInstance().getGroupInfoFromCache(s) == null)
            uri = null;
        else{
            uri = RongContext.getInstance().getGroupInfoFromCache(s).getPortraitUri();
        }
        return uri;
    }

}

对应的xml文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical"
    android:background="#00000000">

    <TextView
        android:id="@+id/de_conversation_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="85dp"
        android:layout_marginTop="4dp"
        android:background="#00000000"
        android:ellipsize="end"
        android:singleLine="true"
        android:textColor="#353535"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/de_conversation_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginRight="8dp"
        android:layout_marginTop="5dp"
        android:background="#00000000"
        android:textColor="#d7d7d7"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/de_at_me"
        android:layout_width="wrap_content"
        android:layout_height="24dp"
        android:layout_below="@+id/de_conversation_title"
        android:layout_marginBottom="2dp"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="4dp"
        android:background="#00000000"
        android:ellipsize="end"
        android:gravity="center_vertical"
        android:singleLine="true"
        android:text="[有人@我]"
        android:textColor="#cb120f"
        android:visibility="gone"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/de_conversation_content"
        android:layout_width="wrap_content"
        android:layout_height="24dp"
        android:layout_toRightOf="@+id/de_at_me"
        android:layout_below="@+id/de_conversation_title"
        android:layout_marginBottom="2dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="70dp"
        android:layout_marginTop="4dp"
        android:background="#00000000"
        android:ellipsize="end"
        android:gravity="center_vertical"
        android:singleLine="true"
        android:textColor="#999999"
        android:textSize="14sp" />

    <ImageView
        android:id="@+id/de_conversation_msg_block"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/de_conversation_time"
        android:layout_marginRight="8dp"
        android:layout_marginTop="10dp"
        android:src="@drawable/de_ic_message_block"
        android:visibility="gone" />

</RelativeLayout>

接着上面写

2.写了单独service来处理@相关数据

public class AtUserService {
    private static AtUserService instance;
    private static final String AT_GTOUP_IDS="at_group_ids";
    public static AtUserService getInstance() {
        if (instance == null) {
            instance = new AtUserService();
        }
        return instance;
    }

    //发送时@人的列表
    public List<TempUser> atUsers =new ArrayList<>();

    public void addUser(User user){
        TempUser tempUser=new TempUser();
        tempUser.uid=user.id.toString();
        tempUser.name=user.name;
        atUsers.add(tempUser);
    }
    public List<String> getUserIds(String text){
        if(atUsers==null||atUsers.size()==0){
            return null;
        }
        ArrayList<String> ids=new ArrayList();
        for(int i=0;i<atUsers.size();i++){
            if(text.contains(atUsers.get(i).name)){
                ids.add(atUsers.get(i).uid);
            }
        }
        return ids;
    }

    //接受 :谁@了我的列表
    private Set<String> atGroupIds =new HashSet<>();

    public Set<String> getAtGroupIds(){
        if(atGroupIds==null||atGroupIds.size()==0){
            String string=UserConfigUtil.getStringConfig(AT_GTOUP_IDS,"");
            if(string!=null&&!string.equals("")){
                atGroupIds=GSONUtil.getGsonInstence().fromJson(string,new TypeToken<Set<String>>(){}.getType());
            }
        }
        return atGroupIds;
    }
    public void addAtGroupId(String id){
        atGroupIds.add(id);
        UserConfigUtil.setConfig(AT_GTOUP_IDS,GSONUtil.getGsonInstence().toJson(atGroupIds),true);
    }
    public void removeAtGroupId(String id){
        atGroupIds.remove(id);
        UserConfigUtil.setConfig(AT_GTOUP_IDS,GSONUtil.getGsonInstence().toJson(atGroupIds),true);
    }
    public Set<String> curConversationId=new HashSet<>();

    public void addCurConversationId(String id){
        curConversationId.add(id);
    }
    public void removeCurConversationId(String id){
        curConversationId.remove(id);
    }
    public class TempUser{
        String uid;
        String name;
    }
}

3.RongService 里处理和融云相关的操作

  • 发送消息
private void setSendMessageListener() {
        if (RongIM.getInstance() != null) {
            //设置自己发出的消息监听器。
            RongIM.getInstance().setSendMessageListener(new RongIM.OnSendMessageListener() {
                /**
                 * 消息发送前监听器处理接口(是否发送成功可以从SentStatus属性获取)。
                 *
                 * @param message 发送的消息实例。
                 * @return 处理后的消息实例。
                 */
                @Override
                public Message onSend(Message message) {
                    MessageContent msgContent = message.getContent();
                   if(message.getConversationType().equals(Conversation.ConversationType.GROUP)&&msgContent.toString().contains("@")){
                            AtMsgBody msgBody=new AtMsgBody();
                            msgBody.groupId = message.getTargetId();
                            msgBody.senderName = AccountService.getInstance().me.name;

                            if(AtUserService.getInstance().getUserIds(((TextMessage) msgContent).getContent().toString())!=null){
                                List<String> ids=AtUserService.getInstance().getUserIds(((TextMessage) msgContent).getContent().toString());
                                String uids="";
                                for(String str:ids){
                                    uids+=str+",";
extraJson.addProperty("uids",uids.equals("")?"":uids.substring(0,uids.length()-1));
                                uids=uids.equals("")?"":uids.substring(0,uids.length()-1);
                                AtUserService.getInstance().atUsers.clear();
                            }
                        }
                        ((TextMessage) msgContent).setExtra(extraJson.toString());
                    } 
                    return message;
                }
  • 接收到消息
RongIM.setOnReceiveMessageListener(new RongIMClient.OnReceiveMessageListener() {
            @Override
            public boolean onReceived(Message message, int i) {
            if(message.getConversationType().equals(Conversation.ConversationType.GROUP)&&message.getContent() instanceof TextMessage){
                    if(!AtUserService.getInstance().curConversationId.contains(message.getTargetId().toString())){
                        JsonObject jsonObject= GSONUtil.getGsonParser().parse(((TextMessage) message.getContent()).getExtra()).getAsJsonObject();
                        if(!jsonObject.isJsonNull()&& jsonObject.has("uids")&&!jsonObject.get("uids").isJsonNull()){
                            String strUids=jsonObject.get("uids").getAsString();
                            if(strUids.contains(AccountService.getInstance().me.id.toString())){
                                AtUserService.getInstance().addAtGroupId(message.getTargetId().toString());
                            }
                        }
                    }
                }
            }
  • 消息点击后取消@显示
RongIM.setConversationListBehaviorListener(new RongIM.ConversationListBehaviorListener() {
@Override
            public boolean onConversationClick(Context context, View view, final UIConversation uiConversation) {
if(uiConversation.getConversationType().equals(Conversation.ConversationType.GROUP)
                        &&AtUserService.getInstance().getAtGroupIds().contains(uiConversation.getConversationTargetId())){
                    AtUserService.getInstance().removeAtGroupId(uiConversation.getConversationTargetId());
          }
     }
}

最后一段,一些注意事项


写到这基本功能已经实现了,但是还是有些细节问题要处理

1.输入框调起成员列表,在聊天页面写入这个方法(我的聊天页面是ChatActivity)

InputProvider.MainInputProvider provider = RongContext.getInstance().getPrimaryInputProvider();
        if (provider instanceof RongTextInputProvider) {
            RongTextInputProvider textInputProvider = (RongTextInputProvider) provider;
            textInputProvider.setEditTextContent("");
            textInputProvider.setEditTextChangedListener(new TextWatcher() {

                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    if(count==0){
                    //键盘回删操作,不调起列表
                        return;
                    }

                    if (mConversationType.equals(Conversation.ConversationType.GROUP)) {
                        if (s.length() > 0) {
                            String str = s.toString().substring(s.toString().length() - 1, s.toString().length());
                            if (str.equals("@")) {
                                Intent intent = new Intent(ChatActivity.this, ChatGroupUserListActivity.class);
                                intent.putExtra(ChatGroupUserListActivity.IS_AT, true);
                                intent.putExtra(ChatGroupUserListActivity.ARGUMENT_GID, mUserId);
                                startActivityForResult(intent, 29);
                                mEditText = s.toString();
                            }
                        }
                    }
                }

                @Override
                public void afterTextChanged(Editable s) {;
                }
            });
        }

2.如果当前app停留的页面就是这个会话页面,则不用在列表中显示有人@了我,具体实现我是在每次进入群聊就保存当前会话的id,关闭聊天页面时,remove掉。

3.如果聊天草稿最后一个字符是@,则进入聊天页面会默认调起成员列表,这样体验不好,想解决这个问题,最后”曲线救国“”了。
具体方法:如果草稿最后一个字符是@则将这个字符删,循环处理,直至最后一个字符不是@符号.

SaveDraftRunnable(Conversation conversation, String content) {
            this.conversation = conversation;
            if(content!=null&&content.toString().length()>0){
                int size=content.length();
                for(int i=0;i<size;i++){
                    if(content.endsWith("@")){
                        content = content.toString().substring(0,content.toString().length()-1);
                    }else{
                        break;
                    }
                }
            }
            this.content = content;
        }

为了解决这个问题,还重写了融云的TextInputProvider,只是修改了上面那一段。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容