Android开发实战“基于环信的通信工具”——03.聊天模块的实现

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于客户端同步更新联系人

  1. 在本地客户端对联系人进行更新时,应该同步删除服务端上的联系人,现在让我们来实现这个功能。因为需要消息的传递,所以这里要用到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{
                            //当前网络不可用,请检查网络设置
                        }
                    }
                }
            });
        }
    }
}
  1. 在项目下新建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.“添加好友”模块的实现

有了联系人,就需要考虑“添加好友”这个模块,现在来实现一下。

  1. 在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);
}
  1. 新建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);
        }
    }
}
  1. 创建相应的布局文件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>
  1. 在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());
                        }
                    });
                }
            }
        });

    }
}
  1. 在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.消息的发送和接收

完善了联系人模块后,接下来就需要编写消息模块的相关逻辑

  1. 在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);
    }

}
  1. 在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);
        }
    }
}
  1. 修改布局文件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.消息页面以及未读数据的处理

消息发送和接收的逻辑处理好了,接下来就是要实现展现联系人列表的模块

  1. 在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();
    }
}
  1. 在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 简易聊天工具

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赈川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值