项目中IM即时消息有个需求要做群成员@提醒功能,参考了QQ群聊的类似功能,实现过程大致分为:首先在编辑框输入@字符,跳转到选择群成员界面,选择完毕填充到编辑框内,用spannable着色;对选中的群成员点击,让光标落在尾处,保证群成员作为个人整体删除;如果有超过一页消息且有未读@消息,右上角有悬浮按钮提醒,点击后定位到首个未读的@消息。现在重新整理成一个demo,与大家分享下。效果图如下:
首先是监听编辑框输入的文本内容。如果最后一位是“@”字符,并且此时用户没有进行删除操作,则跳转到选择群成员界面。实现代码是实现TextChangedListener的监听,在onTextChanged方法进行判断:
public void onTextChanged(CharSequence s, int start, int before, int count) {
// str.endsWith("@")
// 输入@跳转到选择要@的人界面
// 自定义表情是/:smile@ 类似于这种
String str = s.toString();
if (!isDelete) {
if (str.endsWith("@")) {
int index = str.lastIndexOf("@");
if (index >= 1 && checkCharSequence(str.substring(index - 1, str.length() - 1))) {
return;
}
Intent groupIntent = new Intent(MainActivity.this, SelectGroupMemberActivity.class);
groupIntent.putExtra(SelectGroupMemberActivity.GROUP_MEMBER, (Serializable)groupMemberList);
startActivityForResult(groupIntent, SelectGroupMemberActivity.REQUEST_CODE);
}
}
}
选择群成员界面包括:多选、全选、根据群成员手机号或者姓名搜索选择。选择完毕点击确定后,返回聊天页面,编辑框自动填充选中的群成员,并且用spannable进行着色。
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (data != null && data.getExtras() != null) {
boolean isAllSelected = data.getBooleanExtra(SelectGroupMemberActivity.SELECTED_ALL,false);
List<GroupMember> tempList=(ArrayList<GroupMember>)data.getSerializableExtra(SelectGroupMemberActivity.SELECTED_MEMBER);
selectedList.addAll(tempList);
isDelete = true;//锁住Text监听
Editable text = mEditText.getText();
int index = text.toString().length();
text = text.delete(index - 1, index);//删掉'@'
if(isAllSelected){//如果是全选
SpannableString ss = new SpannableString("@全体成员 ");//要@的人
ss.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.blue)), 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EX CLUSIVE);
text = text.append(ss);
}else {
for(GroupMember member:tempList){
SpannableString ss = new SpannableString("@" + member.getName() + " ");//要@的人
ss.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.blue)), 0, ss.length(), Spanned.SPAN_EXCLUSIV E_EXCLUSIVE);
text = text.append(ss);
}
}
mEditText.setText(text);
mEditText.setSelection(text.toString().length());
isDelete = false;//解开Text监听
}
}
当用户点击选中的群成员时,如果点击焦点落在spannable的start与end之间区间,那么设置光标落在end处。
//监听EditText点击事件,删除时如果点击位置处于span的start与end区间,让光标落在end后面
mEditText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Editable editable = mEditText.getEditableText();
ForegroundColorSpan[] spans = editable.getSpans(0, editable.length(), ForegroundColorSpan.class);
if(spans.length == 0)
return;
int selectionStart = mEditText.getSelectionStart();
for (ForegroundColorSpan span : spans) {
int start = editable.getSpanStart(span);
int end = editable.getSpanEnd(span);
if(selectionStart >= start && selectionStart <= end){
mEditText.setSelection(end);
return;
}
}
}
});
当用户按下删除键时,遍历整个编辑框的所有spannable数组。如果删除位置与某个spannable数组元素吻合,则执行删除操作。特殊情况是,“ @全体成员”,如果执行删除则所有选中状态被清空。
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
Editable editable = mEditText.getText();
ForegroundColorSpan[] spans = editable.getSpans(0, editable.length(), ForegroundColorSpan.class);
String str = editable.toString();
for (int i = 0; i < spans.length; i++) {
int spanStart = editable.getSpanStart(spans[i]);
int spanEnd = editable.getSpanEnd(spans[i]);
//@全体成员
if (str.substring(spanStart, spanEnd).equals("@全体成员")) {
editable = editable.delete(spanStart, spanEnd);
// selectedList.clear();
isDelete = true;
mEditText.setText(editable);
return true;
}
if (str.substring(spanStart, spanEnd).equals("@" + selectedList.get(i).getName())) {//@单独成员
if (mEditText.getSelectionStart() == spanEnd) {
editable = editable.delete(spanStart, spanEnd);
selectedList.remove(i);
isDelete = true;
mEditText.setText(editable);
mEditText.setSelection(spanStart);
break;
}
}
}
}
return super.onKeyDown(keyCode, event);
}
接下来是实现未读@消息的定位。这里,有个前提条件:有未读@消息,并且该消息不在当前页面。我是在CursorAdapter里面的getItem方法里,遍历未读消息,判断是否有未读@消息(实际应该是从服务器数据库获取对应字段来进行判断),如果有则返回true同时记录它的position。
public Boolean getItem(int position) {
Cursor cursor = getCursor();
cursor.moveToPosition(position);
String content = cursor.getString(cursor.getColumnIndex(Provider.MessageColumns._MESSAGE_CONTENT));
boolean hasAtMessage = false;
if(content.contains("@") && content.contains(" ")){//判断是否含有@群成员
String[] mArray = content.split("@");
for(int i=1; i<mArray.length; i++){
if(mArray[i] != null) {
if(mArray[i].contains(" "))
mArray[i] = mArray[i].substring(0,mArray[i].indexOf(" "));//@群成员后面可能有其他文字
if (CURRENT_ACCOUNT.equals(mArray[i].trim())//这里假如当前用户是"徐福记6"
|| ALL_MEMBERS.equals(mArray[i].trim())) {//或者是全体成员
hasAtMessage = true;//标记有@消息
mAtPosition = position;//@消息在list的position
break;
}
}
}
}
return hasAtMessage;
}
判断出未读@消息position位置后,需要在listView实现OnScrollListener监听,在OnScroll方法判断未读@消息是否在当前页面。如果不在当前页面,则显示未读@消息的悬浮按钮进行提醒。点击该按钮可定位到首个未读@消息,并且隐藏提醒。
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if((totalItemCount > visibleItemCount) && !hasOtherPage){//item总数大于可见item数,说明listView的item超过一页
hasOtherPage = true;
if(hasAt && (mAtPosition < firstVisibleItem)){//有未读@消息且不在当前页面
rl_at_notify.setVisibility(View.VISIBLE);
}
}
}