一、适配器
1.1 分页显示数据
因为聊天信息数目很多,所以adpter需要做分页处理,这里的分页处理是我自己实现的,如果有更好的办法欢迎在评论中告知。我们从友盟的反馈SDK中能得到聊天的list,我设定的是一次性显示10条数据,所以在适配器中传入和传出的position并不是listview的index,需要进行一定的计算。
下面是计算position的方法:
/**
* @description 重要方法,计算出当前的position
*
* @param position
* @return 当前的position
*/
private int getCurrentPosition(int position) {
int totalCount = mConversation.getReplyList().size();
if (totalCount < mCurrentCount) {
mCurrentCount = totalCount;
}
return totalCount - mCurrentCount + position;
}
通过
int totalCount = mConversation.getReplyList().size();
得到list的size,通过数据总条数(size)和当前一屏需要显示的条数(mCurrentCount)进比较,如果准备显示的数据条数大于数据的总条数,那么就进行一定的计算,如果不进行处理会出现数组越界异常。
同理,Adapter都需要设置一个存储数据的数目,在getCount()中我们也要进行处理。
@Override
public int getCount() {
// 如果开始时的数目小于一次性显示的数目,就按照当前的数目显示,否则会数组越界
int totalCount = mConversation.getReplyList().size();
if (totalCount < mCurrentCount) {
mCurrentCount = totalCount;
}
return mCurrentCount;
}
1.2 设置Adapter中的数据种类
我们的聊天系统中发送的消息有来自用户和来自开发者的,所以有两种信息。在显示前adapter会通过getItemViewType(positon)得到当前position对应的view类型。
// 表示是一对一聊天,有两个类型的信息
private final int VIEW_TYPE_COUNT = 2;
// 用户的标识
private final int VIEW_TYPE_USER = 0;
// 开发者的标识
private final int VIEW_TYPE_DEV = 1;
/*
* @return 表示当前适配器中有两种类型的数据,也就是说item会加载两个布局
*/
@Override
public int getViewTypeCount() {
// 这里是一对一聊天,所以是两种类型
return VIEW_TYPE_COUNT;
} /*
* 通过list中的反馈对象,判断对象的类型,然后返回当前position对应的数据类型
*
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
position = getCurrentPosition(position);
// 获取单条回复
Reply reply = mConversation.getReplyList().get(position);
if (Reply.TYPE_DEV_REPLY.equals(reply.type)) {
// 开发者回复Item布局
return VIEW_TYPE_DEV;
} else {
// 用户反馈、回复Item布局
return VIEW_TYPE_USER;
}
}
1.3 加载数据
当我们发送或者接收到了一条新数据的时候,需要告诉适配器,增加当前数据的显示条数。
/**
* @description 添加了一条新数据后调用此方法
*
*/
public void addOneCount() {
mCurrentCount++;
}
用户在下拉刷新后应该能加载一定条数的聊天记录
/**
* @description 加载之前的聊天信息
*
* @param dataCount 一次性加载的数据数目
*/
public void loadOldData(int dataCount) {
int totalCount = mConversation.getReplyList().size();
if (mCurrentCount >= totalCount) {
// 如果要加载的数据超过了数据的总量,算出实际加载的数据条数
dataCount = dataCount - (mCurrentCount - totalCount);
mCurrentCount = totalCount;
}
mCurrentCount += dataCount;
/**
* 下面的代码可以放在异步任务中执行,这里图省事就没写异步任务。 对于这种从磁盘读取之前数据的人物,用asynTask就行,不用loader
*/
mActivity.onUpdateSuccess(dataCount);
}
如果旧的数据比较多,可能需要用异步任务来做处理,加载完毕后需要通过onUpdateSuccess方法通知activity更新界面。
1.4 ViewHolder
package com.kale.mycmcc; import android.util.SparseArray;
import android.view.View; public class ViewHolder {
// I added a generic return type to reduce the casting noise in client code
@SuppressWarnings("unchecked")
public static <T extends View> T get(View view, int id) {
SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();
if (viewHolder == null) {
viewHolder = new SparseArray<View>();
view.setTag(viewHolder);
}
View childView = viewHolder.get(id);
if (childView == null) {
childView = view.findViewById(id);
viewHolder.put(id, childView);
}
return (T) childView;
}
}
1.5 getView()
在getview()方法中我们做了很多重要的处理。首先是,根据position加载不同的布局文件,并且将消息添加到textview中去。
// 计算出位置
position = getCurrentPosition(position);
// 得到当前位置的reply对象
Reply reply = mConversation.getReplyList().get(position);
// 通过converView来优化listview
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from((Activity) mActivity);
// 根据Type的类型来加载不同的Item布局
if (Reply.TYPE_DEV_REPLY.equals(reply.type)) {
// 如果是开发者回复的,那么就加载开发者回复的布局
convertView = inflater.inflate(R.layout.umeng_fb_dev_reply, null);
} else {
convertView = inflater.inflate(R.layout.umeng_fb_user_reply, null);
}
}
// 放入消息
TextView textView = ViewHolder.get(convertView, R.id.reply_textView);
textView.setText(reply.content);
然后,处理消息发送的结果,如果正在发送就显示进度条,如果发送成功就不显示状态,如果发送失败就显示感叹号。
/**
* 检查发送状态,如果发送失败就进行提示
* 这里的提示信息有进度条和感叹号两种。如果正在发送就显示进度条,如果发送失败就显示感叹号
*/
if (!Reply.TYPE_DEV_REPLY.equals(reply.type)) {
//System.out.println("states = " + reply.status);
ImageView msgErrorIv;
ProgressBar msgSentingPb;
// 根据Reply的状态来设置replyStateFailed的状态,如果发送失败就显示提示图标
msgErrorIv = (ImageView) ViewHolder.get(convertView, R.id.msg_error_imageView);
msgSentingPb = (ProgressBar) ViewHolder.get(convertView, R.id.msg_senting_progressBar); if (Reply.STATUS_NOT_SENT.equals(reply.status)) {
msgSentingPb.setVisibility(View.GONE);
msgErrorIv.setVisibility(View.VISIBLE);
} else if (Reply.STATUS_SENDING.equals(reply.status) || Reply.STATUS_WILL_SENT.equals(reply.status)) {
msgSentingPb.setVisibility(View.VISIBLE);
msgErrorIv.setVisibility(View.GONE);
} else {
msgSentingPb.setVisibility(View.GONE);
msgErrorIv.setVisibility(View.GONE);
}
}
接着,处理消息发送时间的问题。如果两条消息时间间隔较长,那么就显示消息发送的时间。
/**
* 设置回复时间,两条Reply之间相差1分钟则展示时间
*/
ViewStub timeView = ViewHolder.get(convertView, R.id.time_view_stub);
if ((position + 1) < mConversation.getReplyList().size()) {
Reply nextReply = mConversation.getReplyList().get(position + 1);
// 当两条回复相差1分钟时显示时间
if (nextReply.created_at - reply.created_at > 1 * 60 * 1000) {
timeView.setVisibility(View.VISIBLE);
TextView timeTv = ViewHolder.get(convertView, R.id.msg_Time_TextView);
Date replyTime = new Date(reply.created_at);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
timeTv.setText(sdf.format(replyTime));
} else {
timeView.setVisibility(View.GONE);
}
}
最后,返回convertView。getView()的代码如下:
/*
* 通过list中的反馈对象,判断对象的类型,然后返回当前position对应的数据类型
*
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
position = getCurrentPosition(position);
// 获取单条回复
Reply reply = mConversation.getReplyList().get(position);
if (Reply.TYPE_DEV_REPLY.equals(reply.type)) {
// 开发者回复Item布局
return VIEW_TYPE_DEV;
} else {
// 用户反馈、回复Item布局
return VIEW_TYPE_USER;
}
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
// 计算出位置
position = getCurrentPosition(position);
// 得到当前位置的reply对象
Reply reply = mConversation.getReplyList().get(position);
// 通过converView来优化listview
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from((Activity) mActivity);
// 根据Type的类型来加载不同的Item布局
if (Reply.TYPE_DEV_REPLY.equals(reply.type)) {
// 如果是开发者回复的,那么就加载开发者回复的布局
convertView = inflater.inflate(R.layout.umeng_fb_dev_reply, null);
} else {
convertView = inflater.inflate(R.layout.umeng_fb_user_reply, null);
}
}
// 放入消息
TextView textView = ViewHolder.get(convertView, R.id.reply_textView);
textView.setText(reply.content); /**
* 检查发送状态,如果发送失败就进行提示
* 这里的提示信息有进度条和感叹号两种。如果正在发送就显示进度条,如果发送失败就显示感叹号
*/
if (!Reply.TYPE_DEV_REPLY.equals(reply.type)) {
//System.out.println("states = " + reply.status);
ImageView msgErrorIv;
ProgressBar msgSentingPb;
// 根据Reply的状态来设置replyStateFailed的状态,如果发送失败就显示提示图标
msgErrorIv = (ImageView) ViewHolder.get(convertView, R.id.msg_error_imageView);
msgSentingPb = (ProgressBar) ViewHolder.get(convertView, R.id.msg_senting_progressBar); if (Reply.STATUS_NOT_SENT.equals(reply.status)) {
msgSentingPb.setVisibility(View.GONE);
msgErrorIv.setVisibility(View.VISIBLE);
} else if (Reply.STATUS_SENDING.equals(reply.status) || Reply.STATUS_WILL_SENT.equals(reply.status)) {
msgSentingPb.setVisibility(View.VISIBLE);
msgErrorIv.setVisibility(View.GONE);
} else {
msgSentingPb.setVisibility(View.GONE);
msgErrorIv.setVisibility(View.GONE);
}
} /**
* 设置回复时间,两条Reply之间相差1分钟则展示时间
*/
ViewStub timeView = ViewHolder.get(convertView, R.id.time_view_stub);
if ((position + 1) < mConversation.getReplyList().size()) {
Reply nextReply = mConversation.getReplyList().get(position + 1);
// 当两条回复相差1分钟时显示时间
if (nextReply.created_at - reply.created_at > 1 * 60 * 1000) {
timeView.setVisibility(View.VISIBLE);
TextView timeTv = ViewHolder.get(convertView, R.id.msg_Time_TextView);
Date replyTime = new Date(reply.created_at);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
timeTv.setText(sdf.format(replyTime));
} else {
timeView.setVisibility(View.GONE);
}
}
return convertView;
}
1.6 适配器的全部代码
package com.kale.mycmcc; import java.text.SimpleDateFormat;
import java.util.Date; import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView; import com.umeng.fb.model.Conversation;
import com.umeng.fb.model.Reply; /**
* @author:Jack Tony
* @description : 定义回话界面的adapter
* @date :2015年2月9日
*/
class ReplyAdapter extends BaseAdapter { // 表示是一对一聊天,有两个类型的信息
private final int VIEW_TYPE_COUNT = 2;
// 用户的标识
private final int VIEW_TYPE_USER = 0;
// 开发者的标识
private final int VIEW_TYPE_DEV = 1; // 一次性加载多少条数据
// private final int LOAD_DATA_NUM = 10; private int mCurrentCount = 10; // 默认一次性显示多少条数据 private DataCallbackActivity mActivity; // 实现反馈接口的activity
// 回话对象
private Conversation mConversation; public ReplyAdapter(DataCallbackActivity activity, Conversation conversation) {
mActivity = activity;
mConversation = conversation;
} /**
* @description 添加了一条新数据后调用此方法
*
*/
public void addOneCount() {
mCurrentCount++;
} @Override
public int getCount() {
// 如果开始时的数目小于一次性显示的数目,就按照当前的数目显示,否则会数组越界
int totalCount = mConversation.getReplyList().size();
if (totalCount < mCurrentCount) {
mCurrentCount = totalCount;
}
return mCurrentCount;
} @Override
public Object getItem(int position) {
// getCurrentPosition(position)通过计算得出当前相对的position
position = getCurrentPosition(position);
return mConversation.getReplyList().get(position);
} @Override
public long getItemId(int position) {
return getCurrentPosition(position);
} /*
* @return 表示当前适配器中有两种类型的数据,也就是说item会加载两个布局
*/
@Override
public int getViewTypeCount() {
// 这里是一对一聊天,所以是两种类型
return VIEW_TYPE_COUNT;
} /*
* 通过list中的反馈对象,判断对象的类型,然后返回当前position对应的数据类型
*
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
position = getCurrentPosition(position);
// 获取单条回复
Reply reply = mConversation.getReplyList().get(position);
if (Reply.TYPE_DEV_REPLY.equals(reply.type)) {
// 开发者回复Item布局
return VIEW_TYPE_DEV;
} else {
// 用户反馈、回复Item布局
return VIEW_TYPE_USER;
}
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
// 计算出位置
position = getCurrentPosition(position);
// 得到当前位置的reply对象
Reply reply = mConversation.getReplyList().get(position);
// 通过converView来优化listview
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from((Activity) mActivity);
// 根据Type的类型来加载不同的Item布局
if (Reply.TYPE_DEV_REPLY.equals(reply.type)) {
// 如果是开发者回复的,那么就加载开发者回复的布局
convertView = inflater.inflate(R.layout.umeng_fb_dev_reply, null);
} else {
convertView = inflater.inflate(R.layout.umeng_fb_user_reply, null);
}
}
// 放入消息
TextView textView = ViewHolder.get(convertView, R.id.reply_textView);
textView.setText(reply.content); /**
* 检查发送状态,如果发送失败就进行提示
* 这里的提示信息有进度条和感叹号两种。如果正在发送就显示进度条,如果发送失败就显示感叹号
*/
if (!Reply.TYPE_DEV_REPLY.equals(reply.type)) {
//System.out.println("states = " + reply.status);
ImageView msgErrorIv;
ProgressBar msgSentingPb;
// 根据Reply的状态来设置replyStateFailed的状态,如果发送失败就显示提示图标
msgErrorIv = (ImageView) ViewHolder.get(convertView, R.id.msg_error_imageView);
msgSentingPb = (ProgressBar) ViewHolder.get(convertView, R.id.msg_senting_progressBar); if (Reply.STATUS_NOT_SENT.equals(reply.status)) {
msgSentingPb.setVisibility(View.GONE);
msgErrorIv.setVisibility(View.VISIBLE);
} else if (Reply.STATUS_SENDING.equals(reply.status) || Reply.STATUS_WILL_SENT.equals(reply.status)) {
msgSentingPb.setVisibility(View.VISIBLE);
msgErrorIv.setVisibility(View.GONE);
} else {
msgSentingPb.setVisibility(View.GONE);
msgErrorIv.setVisibility(View.GONE);
}
} /**
* 设置回复时间,两条Reply之间相差1分钟则展示时间
*/
ViewStub timeView = ViewHolder.get(convertView, R.id.time_view_stub);
if ((position + 1) < mConversation.getReplyList().size()) {
Reply nextReply = mConversation.getReplyList().get(position + 1);
// 当两条回复相差1分钟时显示时间
if (nextReply.created_at - reply.created_at > 1 * 60 * 1000) {
timeView.setVisibility(View.VISIBLE);
TextView timeTv = ViewHolder.get(convertView, R.id.msg_Time_TextView);
Date replyTime = new Date(reply.created_at);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
timeTv.setText(sdf.format(replyTime));
} else {
timeView.setVisibility(View.GONE);
}
}
return convertView;
} /**
* @description 重要方法,计算出当前的position
*
* @param position
* @return 当前的position
*/
private int getCurrentPosition(int position) {
int totalCount = mConversation.getReplyList().size();
if (totalCount < mCurrentCount) {
mCurrentCount = totalCount;
}
return totalCount - mCurrentCount + position;
// return position;
} /**
* @description 加载之前的聊天信息
*
* @param dataCount 一次性加载的数据数目
*/
public void loadOldData(int dataCount) {
int totalCount = mConversation.getReplyList().size();
if (mCurrentCount >= totalCount) {
// 如果要加载的数据超过了数据的总量,算出实际加载的数据条数
dataCount = dataCount - (mCurrentCount - totalCount);
mCurrentCount = totalCount;
}
mCurrentCount += dataCount;
/**
* 下面的代码可以放在异步任务中执行,这里图省事就没写异步任务。 对于这种从磁盘读取之前数据的人物,用asynTask就行,不用loader
*/
mActivity.onUpdateSuccess(dataCount);
} }
二、Activity
2.1 用接口给Activity添加数据反馈的方法
聊天的activity肯定要接收数据加载反馈结果,所以我定义了一个接口,让activity实现它。
DataCallbackActivity.java
package com.kale.mycmcc; public interface DataCallbackActivity { public void onUpdateSuccess(int dataNum);
public void onUpdateError();
}
2.2 监听listview的状态并进行处理
通过模仿QQ我们发现,当listview滚动的时候就是用户查看聊天记录的时候,所以应该隐藏输入法,给用户更大的浏览空间。
/**
* @author:Jack Tony
* @description : 监听listview的滑动状态,如果到了顶部就刷新数据
* @date :2015年2月9日
*/
private class ListViewListener implements OnScrollListener { InputMethodManager inputMethodManager; public ListViewListener() {
inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
} @Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
} @Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
// 滚动结束
case OnScrollListener.SCROLL_STATE_IDLE:
// 滚动停止
if (view.getLastVisiblePosition() == (view.getCount() - 1)) {
// 如果滚动到底部,就强制显示输入法
// inputMethodManager.showSoftInput(mInputEt,
// InputMethodManager.SHOW_FORCED);
} else if (view.getFirstVisiblePosition() == 0) {
loadOldData();
}
break;
case OnScrollListener.SCROLL_STATE_FLING:
// 开始滚动
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
if (inputMethodManager.isActive()) {
// 正在滚动, 如果在滚动,就隐藏输入法
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
break;
} } }
2.3通过监听EditText的状态来设置button的样式
当editText中没有文字的时候button不可用,如果有文字button变得可用。在这里我还做了回车键发送消息的功能,方便快速发送信息。
/**
* 设置发送消息的按钮和输入框 按下回车键,发送消息
*/
mInputEt = (EditText) findViewById(R.id.conversation_editText);
mInputEt.setOnKeyListener(new OnKeyListener() { @Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// 这两个条件必须同时成立,如果仅仅用了enter判断,就会执行两次
if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_DOWN) {
sendMsgToDev();
return true;
}
return false;
}
});
// 给editText添加监听器
mInputEt.addTextChangedListener(new TextWatcher() { @Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 输入过程中,还在内存里,没到屏幕上
} @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// 在输入之前会触发的
} @Override
public void afterTextChanged(Editable s) {
// 输入完将要显示到屏幕上时会触发
boolean isEmpty = s.toString().trim().isEmpty();
sendBtn.setEnabled(!isEmpty);
sendBtn.setTextColor(isEmpty ? 0xffa1a2a5 : 0xffffffff);
}
});
2.4 发送消息
点击button后,发送消息并且让数据和服务器进行同步
/**
* 设置发送按钮的事件
*/
final Button sendBtn = (Button) findViewById(R.id.conversation_send_btn);
sendBtn.setEnabled(false);
sendBtn.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
sendMsgToDev();
}
});
发送消息
/**
* @description 发送消息
*
*/
private void sendMsgToDev() {
String replyMsg = mInputEt.getText().toString().trim();
mInputEt.getText().clear();
if (!TextUtils.isEmpty(replyMsg)) {
// 将反馈信息放入回话中,有可能发送失败,失败的话在适配器中处理
mComversation.addUserReply(replyMsg);
sync(false);
mAdapter.addOneCount();
}
}
数据同步
/**
* @description 更新数据
*
*/
private void updateData() {
mAdapter.notifyDataSetChanged();
} /**
* @description 将数据和服务器同步
*
*/
private void sync(final boolean isDevReply) {
if (!isDevReply) {
// 如果不是开发者回复的信息,那么就先更新数据,再同步到服务器(快)
updateData();
}
mComversation.sync(new SyncListener() { @Override
public void onSendUserReply(List<Reply> replyList) {
} /*
* 接收开发者回复的信息
*/
@Override
public void onReceiveDevReply(List<Reply> replyList) {
if (replyList == null || replyList.size() < 1) {
return;
}
if (isDevReply) {
// 如果是开发者回复的,就在这里进行数据的同步操作
updateData();
}
}
});
updateData();
}
2.5 配置下拉刷新控件
当用户下拉刷新时,我们需要去加载n条聊天记录,加载完毕后通知activity更新视图。
mSwipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);
mSwipeLayout.setSize(SwipeRefreshLayout.DEFAULT);
// 设置下拉圆圈上的颜色,蓝色、绿色、橙色、红色
mSwipeLayout.setColorSchemeResources(android.R.color.holo_blue_bright, android.R.color.holo_green_light,
android.R.color.holo_orange_light, android.R.color.holo_red_light);
mSwipeLayout.setOnRefreshListener(new OnRefreshListener() { @Override
public void onRefresh() {
mAdapter.loadOldData(LOAD_DATA_NUM);
}
});
数据加载完毕后的回调方法:
/*
* 当加载旧的数据完成后的回调方法
*
* @param dataNum 加载了多少个旧的数据
*/
@Override
public void onUpdateSuccess(int dataNum) {
mSwipeLayout.setRefreshing(false);
// 加载完毕旧的数据,跳到刷新出来数据的位置
if (dataNum - 1 >= 0) {
mListView.setSelection(dataNum - 1);
} else {
Toast.makeText(mContext, "没有数据了", 0).show();
mListView.setSelection(0);
}
} @Override
public void onUpdateError() {
// TODO 自动生成的方法存根 }
2.6 Activity的全部代码
package com.kale.mycmcc; import java.util.List; import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast; import com.umeng.fb.FeedbackAgent;
import com.umeng.fb.SyncListener;
import com.umeng.fb.model.Conversation;
import com.umeng.fb.model.Conversation.OnChangeListener;
import com.umeng.fb.model.Reply;
import com.umeng.message.PushAgent; public class CustomActivity extends BaseActivity implements DataCallbackActivity { private final int LOAD_DATA_NUM = 10; private static Context mContext;
private Conversation mComversation; private EditText mInputEt;
private SwipeRefreshLayout mSwipeLayout;
private ListView mListView;
private ReplyAdapter mAdapter; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.umeng_fb__conversation); mContext = this;
mComversation = new FeedbackAgent(this).getDefaultConversation();
mAdapter = new ReplyAdapter(this, mComversation); inMainActivity();
initView(); // 初始化各种view
sync(false); // 更新数据 // 开启语音反馈
// new FeedbackAgent(this).openAudioFeedback();
new FeedbackAgent(this).sync(); mComversation.setOnChangeListener(new OnChangeListener() { @Override
public void onChange() {
// 发送消息后会自动调用此方法,在这里更新下发送状态
updateData();
}
});
} /**
* @description 应该在主activity使用的方法
*
*/
private void inMainActivity() {
// 开启友盟消息推送服务
PushAgent.getInstance(this).enable();
// 开启反馈回复推送服务
FeedbackAgent fbAgent = new FeedbackAgent(this);
fbAgent.openFeedbackPush(); } /**
* @description 初始化各种view
*
*/
private void initView() {
mSwipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);
mSwipeLayout.setSize(SwipeRefreshLayout.DEFAULT);
// 设置下拉圆圈上的颜色,蓝色、绿色、橙色、红色
mSwipeLayout.setColorSchemeResources(android.R.color.holo_blue_bright, android.R.color.holo_green_light,
android.R.color.holo_orange_light, android.R.color.holo_red_light);
mSwipeLayout.setOnRefreshListener(new OnRefreshListener() { @Override
public void onRefresh() {
mAdapter.loadOldData(LOAD_DATA_NUM);
}
}); /**
* list不显示分割线,设置滚动监听器,设置适配器
*/
mListView = (ListView) findViewById(R.id.conversation_listView);
// 设置listview不显示分割线
mListView.setDivider(null);
mListView.setAdapter(mAdapter);
mListView.setOnScrollListener(new ListViewListener()); /**
* 设置发送按钮的事件
*/
final Button sendBtn = (Button) findViewById(R.id.conversation_send_btn);
sendBtn.setEnabled(false);
sendBtn.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
sendMsgToDev();
}
}); /**
* 设置发送消息的按钮和输入框 按下回车键,发送消息
*/
mInputEt = (EditText) findViewById(R.id.conversation_editText);
mInputEt.setOnKeyListener(new OnKeyListener() { @Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// 这两个条件必须同时成立,如果仅仅用了enter判断,就会执行两次
if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_DOWN) {
sendMsgToDev();
return true;
}
return false;
}
});
// 给editText添加监听器
mInputEt.addTextChangedListener(new TextWatcher() { @Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 输入过程中,还在内存里,没到屏幕上
} @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// 在输入之前会触发的
} @Override
public void afterTextChanged(Editable s) {
// 输入完将要显示到屏幕上时会触发
boolean isEmpty = s.toString().trim().isEmpty();
sendBtn.setEnabled(!isEmpty);
sendBtn.setTextColor(isEmpty ? 0xffa1a2a5 : 0xffffffff);
}
}); } /**
* @description 发送消息
*
*/
private void sendMsgToDev() {
String replyMsg = mInputEt.getText().toString().trim();
mInputEt.getText().clear();
if (!TextUtils.isEmpty(replyMsg)) {
// 将反馈信息放入回话中,有可能发送失败,失败的话在适配器中处理
mComversation.addUserReply(replyMsg);
sync(false);
mAdapter.addOneCount();
}
} /*
* 当这个activity在最上方时不重复启动activity, 如果调用了startActivity,那么就更新下视图
*
* @param intent
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
sync(true);
mAdapter.addOneCount();
} /**
* @description 更新数据
*
*/
private void updateData() {
mAdapter.notifyDataSetChanged();
} /**
* @description 将数据和服务器同步
*
*/
private void sync(final boolean isDevReply) {
if (!isDevReply) {
// 如果不是开发者回复的信息,那么就先更新数据,再同步到服务器(快)
updateData();
}
mComversation.sync(new SyncListener() { @Override
public void onSendUserReply(List<Reply> replyList) {
} /*
* 接收开发者回复的信息
*/
@Override
public void onReceiveDevReply(List<Reply> replyList) {
if (replyList == null || replyList.size() < 1) {
return;
}
if (isDevReply) {
// 如果是开发者回复的,就在这里进行数据的同步操作
updateData();
}
}
});
updateData();
} /*
* 当加载旧的数据完成后的回调方法
*
* @param dataNum 加载了多少个旧的数据
*/
@Override
public void onUpdateSuccess(int dataNum) {
mSwipeLayout.setRefreshing(false);
// 加载完毕旧的数据,跳到刷新出来数据的位置
if (dataNum - 1 >= 0) {
mListView.setSelection(dataNum - 1);
} else {
Toast.makeText(mContext, "没有数据了", 0).show();
mListView.setSelection(0);
}
} @Override
public void onUpdateError() {
// TODO 自动生成的方法存根 } /**
* @description 因为这里获取数据很快,所以看不出效果。
* 当你的数据是从数据库或磁盘中读取的,并且加载的数据很多的时候就可以用下面的方法了。
*
*/
private void loadOldData() {
// 如果滚动到顶部,就刷新出旧的数据
// System.out.println(" load old data");
/*
* mSwipeLayout.setRefreshing(true);
* mAdapter.loadOldData(LOAD_DATA_NUM); mSwipeLayout.setEnabled(false);
*/
} /**
* @author:Jack Tony
* @description : 监听listview的滑动状态,如果到了顶部就刷新数据
* @date :2015年2月9日
*/
private class ListViewListener implements OnScrollListener { InputMethodManager inputMethodManager; public ListViewListener() {
inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
} @Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
} @Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
// 滚动结束
case OnScrollListener.SCROLL_STATE_IDLE:
// 滚动停止
if (view.getLastVisiblePosition() == (view.getCount() - 1)) {
// 如果滚动到底部,就强制显示输入法
// inputMethodManager.showSoftInput(mInputEt,
// InputMethodManager.SHOW_FORCED);
} else if (view.getFirstVisiblePosition() == 0) {
loadOldData();
}
break;
case OnScrollListener.SCROLL_STATE_FLING:
// 开始滚动
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
if (inputMethodManager.isActive()) {
// 正在滚动, 如果在滚动,就隐藏输入法
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
break;
} } }
}
三、不足
因为友盟的开发文档写的真是不清不楚,所以我很难这些东西基本都是试验出来的。我暂时找到一个很好的办法来加载开发者的反馈信息,这里用的是intent的方式来通知的,虽然简单,但会出现开发者一回复,界面会立刻跳转到当前的activity。想要的效果应该是判断当前activity是不是在前台,如果在前台就更新界面,载入新的信息。如果不在前台,就不进行更新信息的操作。把更新信息的操作放在activity的oncreat或者是其他生命周期中做。这个可以用广播来实现,但因为涉及到太多友盟的API,所以就不多说了,谁知道它什么时候又更新了API呢。
源码下载:http://download.csdn.net/detail/shark0017/8450657
注意:为了我项目的安全性,源码中没有添加友盟的UMENG_APPKEY、UMENG_MESSAGE_SECRET,请大家自行去友盟建立一个应用,把你申请到的码写在manifest.xml中。这样你就可以完整的测试了~