转载

制作高仿QQ的聊天系统(下)—— Adapter & Activity

制作高仿QQ的聊天系统(下)—— Adapter & Activity

一、适配器

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 适配器的全部代码

制作高仿QQ的聊天系统(下)—— Adapter &amp; Activity
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

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的全部代码

制作高仿QQ的聊天系统(下)—— Adapter &amp; 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;    }   }  } } 
View Code

三、不足

因为友盟的开发文档写的真是不清不楚,所以我很难这些东西基本都是试验出来的。我暂时找到一个很好的办法来加载开发者的反馈信息,这里用的是intent的方式来通知的,虽然简单,但会出现开发者一回复,界面会立刻跳转到当前的activity。想要的效果应该是判断当前activity是不是在前台,如果在前台就更新界面,载入新的信息。如果不在前台,就不进行更新信息的操作。把更新信息的操作放在activity的oncreat或者是其他生命周期中做。这个可以用广播来实现,但因为涉及到太多友盟的API,所以就不多说了,谁知道它什么时候又更新了API呢。

源码下载:http://download.csdn.net/detail/shark0017/8450657

注意: 为了我项目的安全性,源码中没有添加友盟的UMENG_APPKEY、UMENG_MESSAGE_SECRET,请大家自行去友盟建立一个应用,把你申请到的码写在manifest.xml中。这样你就可以完整的测试了~

正文到此结束
Loading...