1."联系人"模块的内容适配
为了填充联系人模块中的内容,在包下新建adapter包,然后在该包下新建ContactAdapter,代码如下:
package com.itheima.imclient95.adapter;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.itheima.imclient95.R;
import com.itheima.imclient95.utils.StringUtils;
import java.util.List;
/**
* Created by fullcircle on 2017/1/3.
*/
public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.MyViewHolder> {
public void setContacts(List<String> contacts) {
this.contacts = contacts;
}
public List<String> getContacts() {
return contacts;
}
private List<String> contacts;
public ContactAdapter(List<String> contacts) {
this.contacts = contacts;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// View view = View.inflate(parent.getContext(), R.layout.list_contact_item, null);
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_contact_item,parent,false);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final String contact = contacts.get(position);
holder.tv_contact.setText(contact);
holder.tv_section.setText(StringUtils.getFirstChar(contact));
if(position == 0){
holder.tv_section.setVisibility(View.VISIBLE);
}else{
String current = StringUtils.getFirstChar(contact);
String last = StringUtils.getFirstChar(contacts.get(position-1));
if(current.equals(last)){
holder.tv_section.setVisibility(View.GONE);
}else{
holder.tv_section.setVisibility(View.VISIBLE);
}
}
//给条目设置单击事件
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(onItemClickListener!=null){
onItemClickListener.onclick(v,contact);
}
}
});
//给条目设置长按事件
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if(onItemClickListener!=null){
onItemClickListener.onLongClick(v,contact);
return true;
}
return false;
}
});
}
public interface OnItemClickListener{
void onclick(View v,String username);
boolean onLongClick(View v,String username);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
private OnItemClickListener onItemClickListener;
@Override
public int getItemCount() {
return contacts==null?0:contacts.size();
}
class MyViewHolder extends RecyclerView.ViewHolder{
TextView tv_section;
TextView tv_contact;
public MyViewHolder(View itemView) {
super(itemView);
tv_section = (TextView) itemView.findViewById(R.id.tv_section);
tv_contact = (TextView) itemView.findViewById(R.id.tv_contact);
}
}
}
2.使用EventBus于客户端同步更新联系人
- 在本地客户端对联系人进行更新时,应该同步删除服务端上的联系人,现在让我们来实现这个功能。因为需要消息的传递,所以这里要用到EventBus。EventBus的使用可以查看相关网页,这里不再赘述。新建一个MyApplication,实现相应方法,代码如下:
package com.itheima.imclient95;
import android.app.ActivityManager;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.media.SoundPool;
import com.avos.avoscloud.AVOSCloud;
import com.hyphenate.EMConnectionListener;
import com.hyphenate.EMContactListener;
import com.hyphenate.EMError;
import com.hyphenate.EMMessageListener;
import com.hyphenate.chat.EMClient;
import com.hyphenate.chat.EMMessage;
import com.hyphenate.chat.EMOptions;
import com.hyphenate.chat.EMTextMessageBody;
import com.hyphenate.exceptions.HyphenateException;
import com.hyphenate.util.NetUtils;
import com.itheima.imclient95.db.DBUtils;
import com.itheima.imclient95.event.ContactChangeEvent;
import com.itheima.imclient95.event.ExitEvent;
import com.itheima.imclient95.utils.ThreadUtils;
import com.itheima.imclient95.view.ChatActivity;
import org.greenrobot.eventbus.EventBus;
import java.util.Iterator;
import java.util.List;
/**
* Created by fullcircle on 2016/12/31.
*/
public class MyApplication extends Application {
private int foregoundSound;
private int backgoundSound;
private SoundPool soundPool;
@Override
public void onCreate() {
super.onCreate();
//初始化环信
initEaseMobe();
//初始化bmob
// Bmob.initialize(this, "ceea506098f4e62eef8347c7ccbf5ee1");
AVOSCloud.initialize(this,"rU7CFMKuCikTNvjBb4EqmIPM-gzGzoHsz","Uyf0s4rmkowaRB7Wj7NbNte6");
try {
final List<String> allcontacts = EMClient.getInstance().contactManager().getAllContactsFromServer();
} catch (HyphenateException e) {
e.printStackTrace();
}
//初始化数据库
DBUtils.initDBUtils(this);
initGetMessageListener();
//初始化声音池
initSoundPool();
//注册一个监听连接状态的listener
EMClient.getInstance().addConnectionListener(new MyConnectionListener());
}
private void initSoundPool() {
//soundpool 构造 第一个参数 这个池子中管理几个音频
//第二个参数 音频的类型 一般传入AudioManager.STREAM_MUSIC
//第三个参数 声音的采样频率 但是 没有用默认值使用0
soundPool = new SoundPool(2, AudioManager.STREAM_MUSIC,0);
foregoundSound = soundPool.load(getApplicationContext(), R.raw.duan, 1);
backgoundSound = soundPool.load(getApplicationContext(), R.raw.yulu, 1);
}
private void initGetMessageListener() {
EMClient.getInstance().chatManager().addMessageListener(new EMMessageListener() {
@Override
public void onMessageReceived(List<EMMessage> list) {
//收到消息
EventBus.getDefault().post(list);
//获取应用处于前台还是后台的状态
if(isInBackgoundState()){
soundPool.play(backgoundSound,1,1,0,0,1);
//发送通知
sendNotification(list.get(0));
}else{
soundPool.play(foregoundSound,1,1,0,0,1);
}
}
@Override
public void onCmdMessageReceived(List<EMMessage> list) {
//收到透传消息
}
@Override
public void onMessageRead(List<EMMessage> list) {
//处理消息已读回执
}
@Override
public void onMessageDelivered(List<EMMessage> list) {
//处理消息发送回执
}
@Override
public void onMessageChanged(EMMessage emMessage, Object o) {
//消息变化
}
});
}
private void sendNotification(EMMessage message) {
Notification.Builder builder = new Notification.Builder(getApplicationContext());
//设置通知点击之后可以自动消失
builder.setAutoCancel(true);
//设置通知的小图标
builder.setSmallIcon(R.mipmap.message);
//设置通知的大标题
builder.setContentTitle("您有一条新消息需要处理");
//消息的内容
EMTextMessageBody body = (EMTextMessageBody) message.getBody();
//把消息的内容设置到通知的内容中
builder.setContentText(body.getMessage());
//设置一个大图标
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.avatar3));
builder.setContentInfo("来自"+message.getUserName());
//创建要打开的activity对应的意图
Intent mainActivityIntent = new Intent(getApplicationContext(),MainActivity.class);
Intent chatActivityIntent = new Intent(getApplicationContext(),ChatActivity.class);
chatActivityIntent.putExtra("contact",message.getUserName());
Intent[] intents = new Intent[]{mainActivityIntent,chatActivityIntent};
//通过pendingIntent 延迟执行的意图 来处理通知的点击事件
PendingIntent pendingItent = PendingIntent.getActivities(getApplicationContext(),1,intents,PendingIntent.FLAG_UPDATE_CURRENT);
//给通知设置点击事件
builder.setContentIntent(pendingItent);
//创建notification
Notification notification = builder.build();
//通过 NotificationManager 发送通知
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.notify(1,notification);
}
/**
* 判断当前的应用是否处于后台状态
* @param //返回true说明应用处于后台
* 返回false 说明应用处于前台
*/
private boolean isInBackgoundState() {
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
//通过ActivityManager 获取正在运行的 任务信息
List<ActivityManager.RunningTaskInfo> runningTasks = manager.getRunningTasks(50);
//获取第一个activity栈的信息
ActivityManager.RunningTaskInfo runningTaskInfo = runningTasks.get(0);
//获取栈中的栈顶activity 根据activity的包名判断 是否是当前应用的包名
ComponentName componentName = runningTaskInfo.topActivity;
if(componentName.getPackageName().equals(getPackageName())){
//处于前台状态
return false;
}else{
//处于后台状态
return true;
}
}
private void initEaseMobe() {
EMOptions options = new EMOptions();
// 默认添加好友时,是不需要验证的,改成需要验证
options.setAcceptInvitationAlways(false);
int pid = android.os.Process.myPid();
String processAppName = getAppName(pid);
// 如果APP启用了远程的service,此application:onCreate会被调用2次
// 为了防止环信SDK被初始化2次,加此判断会保证SDK被初始化1次
// 默认的APP会在以包名为默认的process name下运行,如果查到的process name不是APP的process name就立即返回
if (processAppName == null ||!processAppName.equalsIgnoreCase(this.getPackageName())) {
// Log.e(TAG, "enter the service process!");
// 则此application::onCreate 是被service 调用的,直接返回
return;
}
//初始化
EMClient.getInstance().init(this, options);
//在做打包混淆时,关闭debug模式,避免消耗不必要的资源
EMClient.getInstance().setDebugMode(false);
//添加好友监听
EMClient.getInstance().contactManager().setContactListener(new EMContactListener() {
@Override
public void onContactInvited(String username, String reason) {
//收到好友邀请
try {
//acceptInvitation 接收邀请
EMClient.getInstance().contactManager().acceptInvitation(username);
//拒绝邀请
// EMClient.getInstance().contactManager().declineInvitation(username);
} catch (HyphenateException e) {
e.printStackTrace();
}
}
@Override
public void onFriendRequestAccepted(String s) {
//如果别人接受了邀请会走这个回调
}
@Override
public void onFriendRequestDeclined(String s) {
//如果拒绝了会走这个回调
}
@Override
public void onContactDeleted(String username) {
//被删除时回调此方法 通过evnetbus发布消息
EventBus.getDefault().post(new ContactChangeEvent(username,false));
}
@Override
public void onContactAdded(String username) {
//增加了联系人时回调此方法
EventBus.getDefault().post(new ContactChangeEvent(username,true));
}
});
}
private String getAppName(int pID) {
String processName = null;
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List l = am.getRunningAppProcesses();
Iterator i = l.iterator();
PackageManager pm = this.getPackageManager();
while (i.hasNext()) {
ActivityManager.RunningAppProcessInfo info = (ActivityManager.RunningAppProcessInfo) (i.next());
try {
if (info.pid == pID) {
processName = info.processName;
return processName;
}
} catch (Exception e) {
// Log.d("Process", "Error>> :"+ e.toString());
}
}
return processName;
}
//实现ConnectionListener接口
private class MyConnectionListener implements EMConnectionListener {
@Override
public void onConnected() {
}
@Override
public void onDisconnected(final int error) {
ThreadUtils.runOnMainThread(new Runnable() {
@Override
public void run() {
if(error == EMError.USER_REMOVED){
// 显示帐号已经被移除
EventBus.getDefault().post(new ExitEvent(EMError.USER_REMOVED));
}else if (error == EMError.USER_LOGIN_ANOTHER_DEVICE) {
// 显示帐号在其他设备登录
EventBus.getDefault().post(new ExitEvent(EMError.USER_LOGIN_ANOTHER_DEVICE));
} else {
if (NetUtils.hasNetwork(getApplicationContext())){
//连接不到聊天服务器
}
else{
//当前网络不可用,请检查网络设置
}
}
}
});
}
}
}
- 在项目下新建event包,专门用来存放事件,然后新建ContactChangeEvent,作为联系人发生变动的事件,代码如下:
package com.itheima.imclient95.event;
/**
* Created by fullcircle on 2017/1/4.
*/
public class ContactChangeEvent {
//发生改变的联系人昵称
public String username;
//联系人是增加了还是删除了
public boolean isAdded;
public ContactChangeEvent(String username, boolean isAdded) {
this.username = username;
this.isAdded = isAdded;
}
}
3.“添加好友”模块的实现
有了联系人,就需要考虑“添加好友”这个模块,现在来实现一下。
- 在view包下新建一个AddFriendView接口,代码如下:
package com.itheima.imclient95.view;
import com.avos.avoscloud.AVUser;
import java.util.List;
/**
* Created by fullcircle on 2017/1/4.
*/
public interface AddFriendView {
void onQuerySuccess(List<AVUser> list, List<String> users, boolean b, String errorMeg);
void onGetAddFriendResult(boolean b, String message);
}
- 新建AddFriendActivity,继承AddFriendView,代码如下:
package com.itheima.imclient95.view;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImageView;
import android.widget.TextView;
import com.avos.avoscloud.AVUser;
import com.itheima.imclient95.R;
import com.itheima.imclient95.adapter.AddFriendAdapter;
import com.itheima.imclient95.presenter.AddFriendPresenter;
import com.itheima.imclient95.presenter.impl.AddFriendPresenterImpl;
import org.w3c.dom.Text;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* Created by fullcircle on 2017/1/4.
*/
public class AddFriendActivity extends BaseActivity implements AddFriendView{
@BindView(R.id.tv_title)
TextView tvTitle;
@BindView(R.id.tb_toolbar)
Toolbar tbToolbar;
@BindView(R.id.rv_addfriend)
RecyclerView rvAddfriend;
@BindView(R.id.iv_nodata)
ImageView ivNodata;
private SearchView searchView;
private AddFriendPresenter presenter;
private AddFriendAdapter adapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_friend);
ButterKnife.bind(this);
initToolbar();
presenter = new AddFriendPresenterImpl(this);
}
private void initToolbar() {
tbToolbar.setTitle("");
setSupportActionBar(tbToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.menu_add_friend,menu);
//找到包含searchview的菜单项
MenuItem menuItem = menu.findItem(R.id.menu_search);
//通过菜单项获取actionView 强转成searchView
searchView = (SearchView) menuItem.getActionView();
//设置搜索框的提示
searchView.setQueryHint("搜索好友");
//给searchView添加搜索文字变化的监听
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
//按了搜索按钮就会走这个方法
//创建适配器
if(adapter==null){
adapter = new AddFriendAdapter(null,null);
rvAddfriend.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
rvAddfriend.setAdapter(adapter);
adapter.setOnAddFriendClickListener(new AddFriendAdapter.onAddFriendClickListener() {
@Override
public void onAddFriendClick(View v, String username) {
presenter.addFriend(username);
}
});
}
//到服务端查询好友
presenter.searchFriend(query);
//隐藏软键盘
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(rvAddfriend.getWindowToken(),0);
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
//搜索框的文字有变化就会走这个方法
if(!TextUtils.isEmpty(newText)){
showToast(newText);
}
return true;
}
});
return super.onCreateOptionsMenu(menu);
}
@Override
public void onQuerySuccess(List<AVUser> list, List<String> users, boolean isSuccess, String errorMeg) {
if(isSuccess){
adapter.setContacts(users);
adapter.setUsers(list);
adapter.notifyDataSetChanged();
ivNodata.setVisibility(View.GONE);
rvAddfriend.setVisibility(View.VISIBLE);
}else{
showToast(errorMeg);
rvAddfriend.setVisibility(View.GONE);
ivNodata.setVisibility(View.VISIBLE);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
switch (itemId){
case android.R.id.home:
finish();
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onGetAddFriendResult(boolean b, String message) {
if(b){
showToast("添加好友成功");
}else{
showToast(message);
}
}
}
- 创建相应的布局文件activity_add_friend.xml,menu_add_friend.xml,代码如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
>
<android.support.v7.widget.Toolbar
android:id="@+id/tb_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/btn_normal"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
>
<TextView
android:layout_gravity="center"
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="搜"
android:textSize="20sp"
android:textColor="@android:color/white"/>
</android.support.v7.widget.Toolbar>
<FrameLayout
android:id="@+id/fl_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/iv_nodata"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@mipmap/nodata"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_addfriend"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</FrameLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_search"
android:title="搜"
app:showAsAction="always"
app:actionViewClass="android.support.v7.widget.SearchView"/>
</menu>
- 在presenter包下新建AddFraendPresenter,以及在presenter/impl下实现类AddFraendPresenterImpl,代码如下:
package com.itheima.imclient95.presenter;
/**
* Created by fullcircle on 2017/1/4.
*/
public interface AddFriendPresenter {
/**
* 到服务端通过关键字搜索好友
* @param keyword
*/
void searchFriend(String keyword);
/**
* 添加好友
* @param username
*/
void addFriend(String username);
}
package com.itheima.imclient95.presenter.impl;
import android.util.Log;
import com.avos.avoscloud.AVException;
import com.avos.avoscloud.AVQuery;
import com.avos.avoscloud.AVUser;
import com.avos.avoscloud.FindCallback;
import com.hyphenate.chat.EMClient;
import com.hyphenate.chat.EMContactManager;
import com.hyphenate.exceptions.HyphenateException;
import com.itheima.imclient95.db.DBUtils;
import com.itheima.imclient95.presenter.AddFriendPresenter;
import com.itheima.imclient95.utils.ThreadUtils;
import com.itheima.imclient95.view.AddFriendView;
import java.util.List;
/**
* Created by fullcircle on 2017/1/4.
*/
public class AddFriendPresenterImpl implements AddFriendPresenter {
private AddFriendView addFriendView;
public AddFriendPresenterImpl(AddFriendView addFriendView) {
this.addFriendView = addFriendView;
}
@Override
public void searchFriend(String keyword) {
final String currentUser = EMClient.getInstance().getCurrentUser();
//AVQuery 要到LeanCould服务端查询数据 参数 要进行查询的表名字
AVQuery<AVUser> query = new AVQuery<>("_User");
//模糊查询用户名 以输入的字母开头的都查出来
query.whereStartsWith("username",keyword)
//要把自己去掉 查询其它用户
.whereNotEqualTo("username",currentUser)
//查询满足条件的所有记录
.findInBackground(new FindCallback<AVUser>() {
@Override
public void done(List<AVUser> list, AVException e) {
if(e==null&&list!=null&&list.size()>0){
//查出数据可以显示
//从数据库中拿到已经在通讯录中的好友
List<String> users = DBUtils.initContact(currentUser);
addFriendView.onQuerySuccess(list,users,true,null);
for(AVUser user:list){
Log.e("test",user.getUsername());
}
}else{
if(e == null){
//查询成功但是没有匹配的数据
addFriendView.onQuerySuccess(null,null,false,"没有满足条件的用户");
}else{
//查询失败
addFriendView.onQuerySuccess(null,null,false,e.getMessage());
}
}
}
});
}
@Override
public void addFriend(final String username) {
ThreadUtils.runOnNonUIThread(new Runnable() {
@Override
public void run() {
try {
EMClient.getInstance().contactManager().addContact(username,"申请添加好友");
ThreadUtils.runOnMainThread(new Runnable() {
@Override
public void run() {
addFriendView.onGetAddFriendResult(true,null);
}
});
} catch (final HyphenateException e) {
e.printStackTrace();
ThreadUtils.runOnMainThread(new Runnable() {
@Override
public void run() {
addFriendView.onGetAddFriendResult(true,e.getMessage());
}
});
}
}
});
}
}
- 在adapter下新建AddFriendAdapter,作为内容的适配器,代码如下:
package com.itheima.imclient95.adapter;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import com.avos.avoscloud.AVUser;
import com.itheima.imclient95.R;
import com.itheima.imclient95.utils.StringUtils;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
/**
* Created by fullcircle on 2017/1/4.
*/
public class AddFriendAdapter extends RecyclerView.Adapter<AddFriendAdapter.MyViewHolder> {
//从数据服务器获取的所有的用户信息
private List<AVUser> users;
//从环信服务器获取的好友信息
private List<String> contacts = new ArrayList<>();
public void setUsers(List<AVUser> users) {
this.users = users;
}
public void setContacts(List<String> contacts) {
if(contacts!=null){
this.contacts = contacts;
}
}
public AddFriendAdapter(List<AVUser> users, List<String> contacts) {
this.users = users;
if(contacts != null){
this.contacts = contacts;
}
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// View view = View.inflate(parent.getContext(), R.layout.list_item_addfriend, null);
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_addfriend,parent,false);
MyViewHolder viewHolder = new MyViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final String username = users.get(position).getUsername();
holder.tv_username.setText(username);
String date = StringUtils.getDate(users.get(position).getCreatedAt());
holder.tv_addTime.setText(date);
if(contacts.contains(username)){
//已经是好友了
holder.btn_add.setText("已经是好友");
//holder.btn_add.setClickable(false);
holder.btn_add.setEnabled(false);
}else{
//不是好友
holder.btn_add.setText("添加");
holder.btn_add.setEnabled(true);
holder.btn_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(listener!=null){
listener.onAddFriendClick(v,username);
}
}
});
}
}
@Override
public int getItemCount() {
return users == null? 0:users.size();
}
class MyViewHolder extends RecyclerView.ViewHolder{
TextView tv_username;
TextView tv_addTime;
Button btn_add;
public MyViewHolder(View itemView) {
super(itemView);
tv_addTime = (TextView) itemView.findViewById(R.id.tv_regist_time);
tv_username = (TextView) itemView.findViewById(R.id.tv_username);
btn_add = (Button) itemView.findViewById(R.id.btn_add);
}
}
public interface onAddFriendClickListener{
void onAddFriendClick(View v,String username);
}
private onAddFriendClickListener listener;
public void setOnAddFriendClickListener(onAddFriendClickListener listener){
this.listener = listener;
}
}
4.消息的发送和接收
完善了联系人模块后,接下来就需要编写消息模块的相关逻辑
- 在view下新建ChatView和ChatActivity,作为Activity,代码分别如下:
package com.itheima.imclient95.view;
import com.hyphenate.chat.EMMessage;
import java.util.List;
/**
* Created by fullcircle on 2017/1/6.
*/
public interface ChatView {
/**
* 获取历史消息记录
* @param emMessages
*/
void getHistoryMessage(List<EMMessage> emMessages);
/**
* 更新消息列表
*/
void updateList();
}
package com.itheima.imclient95.view;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.hyphenate.chat.EMMessage;
import com.itheima.imclient95.R;
import com.itheima.imclient95.adapter.ChatAdapter;
import com.itheima.imclient95.presenter.ChatPresenter;
import com.itheima.imclient95.presenter.impl.ChatPresenterImpl;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
/**
* Created by fullcircle on 2017/1/4.
*/
public class ChatActivity extends BaseActivity implements ChatView{
@BindView(R.id.tv_title)
TextView tvTitle;
@BindView(R.id.tb_toolbar)
Toolbar tbToolbar;
@BindView(R.id.rv_chat)
RecyclerView rvChat;
@BindView(R.id.et_message)
EditText etMessage;
@BindView(R.id.btn_send)
Button btnSend;
private ChatPresenter presenter;
private ChatAdapter adapter;
private String contact;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
ButterKnife.bind(this);
initToolbar();
contact = getIntent().getStringExtra("contact");
tvTitle.setText("与"+ contact +"聊天中");
etMessage.addTextChangedListener(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) {
Log.e("ChatActivity",s+"start:::"+start+"before:::"+before+"count:::"+count);
if(s.length()==0){
btnSend.setEnabled(false);
}else{
btnSend.setEnabled(true);
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
presenter = new ChatPresenterImpl(this);
//初始化recyclerview
rvChat.setLayoutManager(new LinearLayoutManager(this));
adapter = new ChatAdapter(null);
rvChat.setAdapter(adapter);
presenter.getChatHistoryMessage(contact);
}
private void initToolbar() {
tbToolbar.setTitle("");
setSupportActionBar(tbToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@OnClick(R.id.btn_send)
public void onClick() {
String msgText = etMessage.getText().toString();
//通过presenter处理消息发送的逻辑
presenter.sendMessage(msgText,contact);
//把edittext清空
etMessage.setText("");
}
@Override
public void getHistoryMessage(List<EMMessage> emMessages) {
//给适配器设置数据
adapter.setMessages(emMessages);
// 刷新界面
adapter.notifyDataSetChanged();
if(adapter.getItemCount()>0){
rvChat.smoothScrollToPosition(adapter.getItemCount()-1);
}
}
@Override
public void updateList() {
// 刷新界面
adapter.notifyDataSetChanged();
if(adapter.getItemCount()>0){
rvChat.smoothScrollToPosition(adapter.getItemCount()-1);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
void onGetMessageEvent(List<EMMessage> messages){
presenter.getChatHistoryMessage(contact);
}
}
- 在adapter下新建ChatAdapter,作为内容的适配器,代码如下:
package com.itheima.imclient95.adapter;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.hyphenate.chat.EMMessage;
import com.hyphenate.chat.EMMessageBody;
import com.hyphenate.chat.EMTextMessageBody;
import com.hyphenate.util.DateUtils;
import com.itheima.imclient95.R;
import java.util.Date;
import java.util.List;
/**
* Created by fullcircle on 2017/1/6.
*/
public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.MyViewHolder> {
private List<EMMessage> messages;
public ChatAdapter(List<EMMessage> messages) {
this.messages = messages;
}
public void setMessages(List<EMMessage> messages) {
this.messages = messages;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = null;
if(viewType == 0){
//收到的消息
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_chat_item,parent,false);
}else{
//发送的消息
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_chat_item_send,parent,false);
}
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
//获取当前条目对应的消息
EMMessage message = messages.get(position);
long msgTime = message.getMsgTime();
//判断是否要显示时间
if(position == 0){
//两个时间 间隔是否很近 <30s
if(DateUtils.isCloseEnough(msgTime,System.currentTimeMillis())) {
//如果当前消息是刚发送的不显示发送的时间
holder.tv_time.setVisibility(View.GONE);
Log.e("ChatActivity", "position" + position + DateUtils.getTimestampString(new Date(msgTime)));
}else{
//DateUtils.getTimestampString(new Date(msgTime)) 使用环信的工具类 来格式化时间
holder.tv_time.setText(DateUtils.getTimestampString(new Date(msgTime)));
holder.tv_time.setVisibility(View.VISIBLE);
}
}else{
if(DateUtils.isCloseEnough(msgTime,messages.get(position-1).getMsgTime())){
//跟上一条消息的发送时间比较 获取两条消息的时间间隔
holder.tv_time.setVisibility(View.GONE);
Log.e("ChatActivity","gone"+position);
}else{
holder.tv_time.setText(DateUtils.getTimestampString(new Date(msgTime)));
holder.tv_time.setVisibility(View.VISIBLE);
Log.e("ChatActivity","position"+position+DateUtils.getTimestampString(new Date(msgTime)));
}
}
//显示消息的内容 EMTextMessageBody 文本消息
EMTextMessageBody body = (EMTextMessageBody)message.getBody();
//获取消息内容
String msg = body.getMessage();
holder.tv_message.setText(msg);
if(message.direct()== EMMessage.Direct.SEND){
//如果是发送的消息还要处理发送的状态
EMMessage.Status status = message.status();
switch (status){
case SUCCESS:
//隐藏发送状态的图标
holder.iv_state.setVisibility(View.GONE);
break;
case FAIL:
holder.iv_state.setImageResource(R.mipmap.msg_error);
break;
case INPROGRESS:
holder.iv_state.setImageResource(R.drawable.send_animation);
AnimationDrawable drawable = (AnimationDrawable) holder.iv_state.getDrawable();
drawable.start();
break;
}
}
}
@Override
public int getItemViewType(int position) {
//EMMessage 环信聊天的消息 对应的对象
EMMessage message = messages.get(position);
EMMessage.Direct direct = message.direct();
//Direct 消息的发送方向 send说明是自己发的 receive收到的消息
// 根据direct的值来决定 究竟是发送的消息 还是收到的消息
return direct== EMMessage.Direct.RECEIVE?0:1;
}
@Override
public int getItemCount() {
return messages == null?0:messages.size();
}
class MyViewHolder extends RecyclerView.ViewHolder{
TextView tv_time;
TextView tv_message;
ImageView iv_state;
public MyViewHolder(View itemView) {
super(itemView);
tv_message = (TextView) itemView.findViewById(R.id.tv_message);
tv_time = (TextView) itemView.findViewById(R.id.tv_time);
iv_state = (ImageView) itemView.findViewById(R.id.iv_state);
}
}
}
- 修改布局文件activity_chat.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
>
<android.support.v7.widget.Toolbar
android:id="@+id/tb_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/btn_normal"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
>
<TextView
android:layout_gravity="center"
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="消息"
android:textSize="20sp"
android:textColor="@android:color/white"/>
</android.support.v7.widget.Toolbar>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_chat"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</android.support.v7.widget.RecyclerView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/et_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送"/>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
5.消息页面以及未读数据的处理
消息发送和接收的逻辑处理好了,接下来就是要实现展现联系人列表的模块
- 在presenter下新增ConversationPresenter接口以及在presenter/impl下新增实现类ConverSationPresenterImpl,代码分别如下:
package com.itheima.imclient95.presenter;
/**
* Created by fullcircle on 2017/1/6.
*/
public interface ConversationPresenter {
void getConversations();
void clearAllUnreadMark();
}
package com.itheima.imclient95.presenter.impl;
import com.hyphenate.chat.EMClient;
import com.hyphenate.chat.EMConversation;
import com.itheima.imclient95.presenter.ConversationPresenter;
import com.itheima.imclient95.view.ConversationView;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* Created by fullcircle on 2017/1/6.
*/
public class ConversationPresenterImpl implements ConversationPresenter {
private ConversationView conversationView;
public ConversationPresenterImpl(ConversationView conversationView) {
this.conversationView = conversationView;
}
@Override
public void getConversations() {
Map<String, EMConversation> allConversations = EMClient.getInstance().chatManager().getAllConversations();
Collection<EMConversation> values = allConversations.values();
//获取会话的集合
List<EMConversation> conversationList = new ArrayList<>(values);
//根据最近收到的消息时间的顺序对会话进行排序
Collections.sort(conversationList, new Comparator<EMConversation>() {
@Override
public int compare(EMConversation o1, EMConversation o2) {
return (int) (o2.getLastMessage().getMsgTime()-o1.getLastMessage().getMsgTime());
}
});
conversationView.onGetConversations(conversationList);
}
@Override
public void clearAllUnreadMark() {
EMClient.getInstance().chatManager().markAllConversationsAsRead();
conversationView.onClearAllUnreadMark();
}
}
- 在view下新建ConversationView以及ConversationFragment,代码分别如下:
package com.itheima.imclient95.view;
import com.hyphenate.chat.EMConversation;
import java.util.List;
/**
* Created by fullcircle on 2017/1/6.
*/
public interface ConversationView {
void onGetConversations(List<EMConversation> conversationList);
void onClearAllUnreadMark();
}
package com.itheima.imclient95.view;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.hyphenate.chat.EMClient;
import com.hyphenate.chat.EMConversation;
import com.hyphenate.chat.EMMessage;
import com.itheima.imclient95.MainActivity;
import com.itheima.imclient95.R;
import com.itheima.imclient95.adapter.ConversationAdapter;
import com.itheima.imclient95.presenter.ConversationPresenter;
import com.itheima.imclient95.presenter.impl.ConversationPresenterImpl;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.List;
/**
* Created by fullcircle on 2017/1/3.
*/
public class ConversationFragment extends BaseFragment implements ConversationView {
private RecyclerView recyclerView;
private FloatingActionButton fab;
private ConversationAdapter adapter;
private ConversationPresenter presenter;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_conversation,null);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
recyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
fab = (FloatingActionButton) view.findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.clearAllUnreadMark();
}
});
adapter = new ConversationAdapter(null);
adapter.setOnItemClickListener(new ConversationAdapter.onAddItemClickListener() {
@Override
public void onConversationClick(View v, String username) {
Intent intent = new Intent(getContext(),ChatActivity.class);
intent.putExtra("contact",username);
startActivity(intent);
}
});
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(adapter);
presenter = new ConversationPresenterImpl(this);
presenter.getConversations();
super.onViewCreated(view, savedInstanceState);
}
@Override
public void onGetConversations(List<EMConversation> conversationList) {
adapter.setEMConversations(conversationList);
adapter.notifyDataSetChanged();
}
@Override
public void onClearAllUnreadMark() {
presenter.getConversations();
MainActivity activity = (MainActivity) getActivity();
activity.updateBadgeItem();
}
@Subscribe(threadMode = ThreadMode.MAIN)
void onGetMessageEvent(List<EMMessage> list){
presenter.getConversations();
}
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
@Override
public void onResume() {
super.onResume();
presenter.getConversations();
}
}
6.源码
感谢您能浏览到这里,这一小节将放出源码,供读者进行进一步学习
实时通讯类App 简易聊天工具