一、適配器
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中。這樣你就可以完整的測試了~