因为聊天信息数目很多,所以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; }
我们的聊天系统中发送的消息有来自用户和来自开发者的,所以有两种信息。在显示前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; } }
当我们发送或者接收到了一条新数据的时候,需要告诉适配器,增加当前数据的显示条数。
/** * @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更新界面。
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; } }
在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; }
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); } }View Code
聊天的activity肯定要接收数据加载反馈结果,所以我定义了一个接口,让activity实现它。
DataCallbackActivity.java
package com.kale.mycmcc; public interface DataCallbackActivity { public void onUpdateSuccess(int dataNum); public void onUpdateError(); }
通过模仿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; } } }
当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); } });
点击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(); }
当用户下拉刷新时,我们需要去加载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 自动生成的方法存根 }
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; } } } }View Code
因为友盟的开发文档写的真是不清不楚,所以我很难这些东西基本都是试验出来的。我暂时找到一个很好的办法来加载开发者的反馈信息,这里用的是intent的方式来通知的,虽然简单,但会出现开发者一回复,界面会立刻跳转到当前的activity。想要的效果应该是判断当前activity是不是在前台,如果在前台就更新界面,载入新的信息。如果不在前台,就不进行更新信息的操作。把更新信息的操作放在activity的oncreat或者是其他生命周期中做。这个可以用广播来实现,但因为涉及到太多友盟的API,所以就不多说了,谁知道它什么时候又更新了API呢。
注意: 为了我项目的安全性,源码中没有添加友盟的UMENG_APPKEY、UMENG_MESSAGE_SECRET,请大家自行去友盟建立一个应用,把你申请到的码写在manifest.xml中。这样你就可以完整的测试了~