李晨晨:
从本周开始,我开始写P2P聊天界面相关的内容,主要用的是网易云通信相关的云服务。
本次主要实现用网易云服务实现文本,图片,语音,地理位置等信息的创建和发送,同时实现历史消息记录的加载。
1.首先需要关联ChatSession类得到聊天对象的账号:
- private Context mContext;
- private ChatSession mChatSession;
- public ChatMsgHandler(Context context, ChatSession session) {
- mContext = context;
- mChatSession = session;
- }
2.创建文本消息:
- public IMMessage createTextMessage(String text) {
- // 创建文本消息
- return MessageBuilder.createTextMessage(mChatSession.getChatAccount(),
- mChatSession.getSessionType(), text);
- }
3.创建图片消息:消息内容为图片保存路径。
- public IMMessage createImageMessage(String path) {
- return MessageBuilder.createImageMessage(mChatSession.getSessionId(),
- mChatSession.getSessionType(), new File(path));
- }
4.创建语音消息:消息内容为语音保存路径和语音时长。
- public IMMessage createAudioMessage(String path, long time) {
- return MessageBuilder.createAudioMessage(mChatSession.getSessionId(),
- mChatSession.getSessionType(), new File(path), time);
- }
5.创建地理位置信息:内容为经纬度和位置文字描述
- public IMMessage createLocMessage(LatLonPoint latLonPoint, String address) {
- return MessageBuilder.createLocationMessage(mChatSession.getSessionId(),
- mChatSession.getSessionType(), latLonPoint.getLatitude(), latLonPoint.getLongitude(),
- address);
- }
6.加载历史消息记录:加载成功会得到一个放IMMessage的List-result
- public void loadMessage(final IMMessage anchorMessage, final OnLoadMsgListener listener) {
- NIMClient.getService(MsgService.class).queryMessageListEx(anchorMessage,
- QueryDirectionEnum.QUERY_OLD, ONE_QUERY_LIMIT, true)
- .setCallback(new RequestCallbackWrapper<List<IMMessage>>() {
- @Override
- public void onResult(int code, List<IMMessage> result, Throwable exception) {
- if (exception != null) {
- listener.loadFail(exception.getMessage());
- return;
- }
- if (code != 200) {
- listener.loadFail("code:" + code);
- return;
- }
- listener.loadSuccess(result, anchorMessage);
- }
- });
- }
OnLoadMsgListener
- public interface OnLoadMsgListener {
- void loadSuccess(List<IMMessage> messages, IMMessage anchorMessage);
- void loadFail(String message);
- }
可以在具体显示消息的activity中重写,实现具体的功能。
7.处理历史消息记录,如果两条消息之间相隔大于TEN_MINUTE,则需要在两条之间新增时间点文本消息。
- public List<IMMessage> dealLoadMessage(List<IMMessage> messages, IMMessage anchorMessage) {
- IMMessage lastMsg = messages.get(messages.size() - 1);
- if (anchorMessage.getTime() - lastMsg.getTime() >= TEN_MINUTE) {
- messages.add(messages.size() - 1,createTimeMessage(lastMsg));
- }
- for (int i = messages.size() - 2; i > 0; i--) {
- if (!TextUtils.isEmpty(messages.get(i).getUuid()) &&
- !TextUtils.isEmpty(messages.get(i-1).getUuid())){
- if (messages.get(i).getTime() - messages.get(i-1).getTime() >= TEN_MINUTE) {
- messages.add(i , createTimeMessage(messages.get(i)));
- }
- }
- }
- return messages;
- }
- public IMMessage createTimeMessage(IMMessage message) {
- return MessageBuilder.createEmptyMessage(message.getSessionId(),
- message.getSessionType(), message.getTime());
- }
8.在此过程中用到的ChatSession类:主要用于属性等信息的保存和获取。
- public class ChatSession {
- private SessionTypeEnum mSessionType;
- private String mSessionId;
- private String mMyAccount;
- private String mChatAccount;
- private String mChatNick;
- private NimUserInfo mMyInfo;
- private NimUserInfo mChatInfo;
- public SessionTypeEnum getSessionType() {
- return mSessionType;
- }
- public void setSessionType(SessionTypeEnum sessionType) {
- mSessionType = sessionType;
- }
- public String getSessionId() {
- return mSessionId;
- }
- public void setSessionId(String sessionId) {
- mSessionId = sessionId;
- }
- public String getMyAccount() {
- return mMyAccount;
- }
- public void setMyAccount(String myAccount) {
- mMyAccount = myAccount;
- }
- public String getChatAccount() {
- return mChatAccount;
- }
- public void setChatAccount(String chatAccount) {
- mChatAccount = chatAccount;
- }
- public String getChatNick() {
- return mChatNick;
- }
- public void setChatNick(String chatNick) {
- mChatNick = chatNick;
- }
- public NimUserInfo getMyInfo() {
- return mMyInfo;
- }
- public void setMyInfo(NimUserInfo myInfo) {
- mMyInfo = myInfo;
- }
- public NimUserInfo getChatInfo() {
- return mChatInfo;
- }
- public void setChatInfo(NimUserInfo chatInfo) {
- mChatInfo = chatInfo;
- }
- }
仝心:
这次首先实现了播放音频的功能,日后用于语音聊天,通过自定义一个AudioPlayManager类来实现这个功能。
- /**
- * 播放音频文件
- *
- * @param path 音频文件路径
- * @param listener 播放监听器
- */
- public static void playAudio(Context context, final String path, final OnPlayAudioListener listener) {
- if (sAudioManager == null) {
- sAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- }
- assert sAudioManager != null;
- sAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
- if (sMediaPlayer == null) {
- sMediaPlayer = new MediaPlayer();
- } else {
- if (sMediaPlayer.isPlaying()) {
- sMediaPlayer.stop();
- }
- sMediaPlayer.reset();
- }
- sMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
- @Override
- public void onPrepared(MediaPlayer mp) {
- mp.start();
- if (listener != null) {
- listener.onPlay();
- }
- }
- });
- sMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- if (listener != null) {
- listener.onError("播放出错,错误码:" + what);
- }
- return false;
- }
- });
- sMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- if (listener != null) {
- listener.onComplete();
- }
- }
- });
- try {
- int focus = sAudioManager.requestAudioFocus(null,
- AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- if (focus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
- sMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- sMediaPlayer.setDataSource(path);
- sMediaPlayer.prepare();
- } else if (focus == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
- if (listener != null) {
- listener.onError("播放出错:" + "AUDIOFOCUS_REQUEST_FAILED");
- }
- }
- } catch (Exception e) {
- if (listener != null) {
- listener.onError("播放出错:" + e.getMessage());
- }
- release();
- }
- }
其中的listener监听器的接口
- public interface OnPlayAudioListener {
- void onPlay();
- void onComplete();
- void onError(String message);
- }
接口的实现
- AudioPlayManager.playAudio(this, audioAttachment.getPath(),
- new AudioPlayManager.OnPlayAudioListener() {
- @Override
- public void onPlay() {
- // 启动播放动画
- isAudioPlay = true;
- mPlayId = message.getUuid();
- mAudioPlayHandler.startAudioAnim(imageView, isLeft);
- }
- @Override
- public void onComplete() {
- isAudioPlay = false;
- mPlayId = "";
- mAudioPlayHandler.stopAnimTimer();
- }
- @Override
- public void onError(String message) {
- isAudioPlay = false;
- mPlayId = "";
- mAudioPlayHandler.stopAnimTimer();
- ToastUtils.showMessage(P2PChatActivity.this, message);
- }
- });
暂停播放,在contex生命周期onPause中调用
- public static void pause() {
- if (sMediaPlayer != null && sMediaPlayer.isPlaying()) { //正在播放的时候
- sMediaPlayer.pause();
- isPause = true;
- }
- if (sAudioManager != null) {
- sAudioManager.abandonAudioFocus(null);
- }
- }
恢复播放,在contex生命周期onResume中调用
- public static void resume() {
- if (sMediaPlayer != null && isPause) {
- if (sAudioManager != null) {
- int focus = sAudioManager.requestAudioFocus(null,
- AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- if (focus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
- sMediaPlayer.start();
- isPause = false;
- }
- }
- }
- }
释放资源,在context生命周期onDestroy中调用
- public static void release() {
- if (sMediaPlayer != null) {
- sMediaPlayer.release();
- sMediaPlayer = null;
- }
- if (sAudioManager != null) {
- sAudioManager.abandonAudioFocus(null);
- sAudioManager = null;
- }
- }
然后自定义了一个Handler用来播放音频文件,并通过更新界面实现动画效果。
- //语音动画控制器
- private Timer mTimer = null;
- //语音动画控制任务
- private TimerTask mTimerTask = null;
- //记录语音动画图片索引
- private int index = 0;
- //根据 index 更换动画
- private AudioAnimHandler mAudioAnimHandler = null;
类的构造方法继承自Handler:
- private static class AudioAnimHandler extends Handler {
- private final ImageView mIvAudio;
- private final boolean isLeft;
- private int[] mLeftIndex = {
- R.mipmap.sound_left_1,R.mipmap.sound_left_2,R.mipmap.sound_left_3};
- private int[] mRightIndex = {
- R.mipmap.sound_right_1, R.mipmap.sound_right_2,R.mipmap.sound_right_3};
- AudioAnimHandler(ImageView imageView,boolean isLeft){
- this.mIvAudio = imageView;
- this.isLeft = isLeft;
- }
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- switch (msg.what){
- case 0:
- mIvAudio.setImageResource(isLeft? mLeftIndex[0]:mRightIndex[0]);
- break;
- case 1:
- mIvAudio.setImageResource(isLeft? mLeftIndex[1]:mRightIndex[1]);
- break;
- case 2:
- mIvAudio.setImageResource(isLeft? mLeftIndex[2]:mRightIndex[2]);
- break;
- default:
- mIvAudio.setImageResource(isLeft? R.mipmap.sound_left_0:R.mipmap.sound_right_0);
- removeCallbacks(null);
- break;
- }
- }
- }
播放音频动画的方法:
- public void startAudioAnim(ImageView imageView,boolean isLeft){
- stopAnimTimer();
- // 语音播放动画效果
- mAudioAnimHandler = new AudioAnimHandler(imageView,isLeft);
- mTimerTask = new TimerTask() {
- @Override
- public void run() {
- mAudioAnimHandler.obtainMessage(index).sendToTarget();
- index = (index+1)%3;
- }
- };
- mTimer = new Timer();
- // 每半秒更新一次界面
- mTimer.schedule(mTimerTask,0,500);
- }
停止播放音频动画的方法:
- public void stopAnimTimer(){
- if (mTimer != null) {
- mTimer.cancel();
- mTimer = null;
- }
- if (mTimerTask != null) {
- mTimerTask.cancel();
- mTimerTask = null;
- }
- // 将上一个语音消息界面复位
- if (mAudioAnimHandler != null){
- mAudioAnimHandler.obtainMessage(3).sendToTarget();
- }
- }
张静:
本周开始写第三个自定义的Fragment——MeFragment,对应于界面中的“我”。
里面实现的功能有修改个人资料,以及个性化手写字体库的创建
1. 设置加载的布局ID以及“我”布局文件
- @Override
- public int setLayoutID() {
- return R.layout.fragment_me;
- }
fragment_me.xml
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:background="@color/interval_color"
- android:orientation="vertical">
- <View
- android:layout_width="match_parent"
- android:layout_height="10dp"/>
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="80dp"
- android:background="@color/white_color"
- android:gravity="center_vertical">
- <com.joooonho.SelectableRoundedImageView
- android:id="@+id/iv_head_picture"
- android:layout_width="70dp"
- android:layout_height="70dp"
- android:layout_marginLeft="10dp"
- android:layout_marginTop="5dp"
- android:layout_marginBottom="5dp"
- app:sriv_oval="true"
- android:scaleType="fitXY"
- android:src="@mipmap/app_logo_main"/>
- <RelativeLayout
- android:layout_marginLeft="10dp"
- android:id="@+id/layout_account"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_toRightOf="@id/iv_head_picture"
- android:gravity="center_vertical"
- android:orientation="vertical">
- <TextView
- android:id="@+id/tv_user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/app_name"
- android:textColor="@color/app_black_color"/>
- <TextView
- android:id="@+id/tv_tip"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@+id/tv_user_name"
- android:layout_marginTop="10dp"
- android:text="@string/account_tip"
- android:textColor="@color/app_black_color"/>
- <TextView
- android:id="@+id/tv_account"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@+id/tv_user_name"
- android:layout_marginTop="10dp"
- android:layout_toRightOf="@+id/tv_tip"
- android:text="Ezreal-520"
- android:textColor="@color/app_black_color"/>
- </RelativeLayout>
- </RelativeLayout>
- <View
- android:layout_width="match_parent"
- android:layout_height="10dp"/>
- <RelativeLayout
- android:id="@+id/layout_user_words_input"
- android:layout_width="match_parent"
- android:layout_height="50dp"
- android:background="@color/white_color"
- android:paddingLeft="10dp"
- android:paddingRight="10dp">
- <ImageView
- android:id="@+id/iv_user_words"
- android:layout_width="45dp"
- android:layout_height="45dp"
- android:padding="5dp"
- android:layout_marginLeft="5dp"
- android:scaleType="fitXY"
- android:src="@mipmap/setting"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginLeft="5dp"
- android:layout_toRightOf="@+id/iv_user_words"
- android:gravity="center"
- android:text="字体库"
- android:textColor="@color/app_black_color"
- android:textSize="16sp"/>
- </RelativeLayout>
- </LinearLayout>
2. 初始化
- @Override
- public void initView(View rootView) {
- ButterKnife.bind(this,rootView);
- NimUserHandler.getInstance().setUpdateListeners(new OnInfoUpdateListener() {
- @Override
- public void myInfoUpdate() {
- showOrRefreshView();
- }
- });
- showOrRefreshView();
- }
showOrRefreshView——进入“我”界面后,未点入完整信息页前,显示的是头像,昵称,账号,初始化中若这三个属性有变更要及时刷新
- private void showOrRefreshView(){
- mAccountBean = NimUserHandler.getInstance().getLocalAccount();
- if (mAccountBean != null){
- ImageUtils.setImageByUrl(getContext(),mHeadView,
- mAccountBean.getHeadImgUrl(),R.mipmap.app_logo_main);
- mTvName.setText(mAccountBean.getNick());
- mTvAccount.setText(mAccountBean.getAccount());
- }
- }
3. 进入修改信息页(AccountInfoActivity)
- @OnClick(R.id.layout_account)
- public void openAccountInfo(){
- Intent intent = new Intent(getContext(), AccountInfoActivity.class);
- startActivity(intent);
- }
4. 进入字体库(ContentActivity)
- @OnClick(R.id.layout_user_words_input)
- public void openUserWordsAlbum(){
- Intent intent=new Intent(getContext(), ContentActivity.class);
- startActivity(intent);
- }
附上完整MeFragment.java
- package com.ezreal.ezchat.fragment;
- import android.content.Intent;
- import android.util.Log;
- import android.view.View;
- import android.widget.TextView;
- import com.ezreal.ezchat.R;
- import com.ezreal.ezchat.activity.AccountInfoActivity;
- import com.ezreal.ezchat.activity.ContentActivity;
- import com.ezreal.ezchat.activity.CreateActivity;
- import com.ezreal.ezchat.bean.LocalAccountBean;
- import com.ezreal.ezchat.handler.NimUserHandler;
- import com.ezreal.ezchat.handler.NimUserHandler.OnInfoUpdateListener;
- import com.joooonho.SelectableRoundedImageView;
- import com.ezreal.ezchat.commonlibrary.utils.ImageUtils;
- import butterknife.BindView;
- import butterknife.ButterKnife;
- import butterknife.OnClick;
- /**
- * Created by 张静
- */
- public class MeFragment extends BaseFragment {
- @BindView(R.id.iv_head_picture)
- SelectableRoundedImageView mHeadView;
- @BindView(R.id.tv_user_name)
- TextView mTvName;
- @BindView(R.id.tv_account)
- TextView mTvAccount;
- private LocalAccountBean mAccountBean;
- @Override
- public int setLayoutID() {
- return R.layout.fragment_me;
- }
- @Override
- public void initView(View rootView) {
- ButterKnife.bind(this,rootView);
- NimUserHandler.getInstance().setUpdateListeners(new OnInfoUpdateListener() {
- @Override
- public void myInfoUpdate() {
- showOrRefreshView();
- }
- });
- showOrRefreshView();
- }
- @OnClick(R.id.layout_account)
- public void openAccountInfo(){
- Intent intent = new Intent(getContext(), AccountInfoActivity.class);
- startActivity(intent);
- }
- //进入字体库
- @OnClick(R.id.layout_user_words_input)
- public void openUserWordsAlbum(){
- Intent intent=new Intent(getContext(), ContentActivity.class);
- startActivity(intent);
- }
- private void showOrRefreshView(){
- mAccountBean = NimUserHandler.getInstance().getLocalAccount();
- if (mAccountBean != null){
- ImageUtils.setImageByUrl(getContext(),mHeadView,
- mAccountBean.getHeadImgUrl(),R.mipmap.app_logo_main);
- mTvName.setText(mAccountBean.getNick());
- mTvAccount.setText(mAccountBean.getAccount());
- }
- }
- }