zoukankan      html  css  js  c++  java
  • 制作高仿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 适配器的全部代码

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

    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中。这样你就可以完整的测试了~

  • 相关阅读:
    -----------------------------2015年 年度总结-----------------------------
    ------第二节-----------------第二讲----单链表的基本操作---------
    shell 字符串截取
    express, mocha, supertest,istanbul
    Qunit 和 jsCoverage使用方法(js单元测试)
    jsp tutorial
    Unicode 和 UTF-8 是什么关系?
    wget -d --header
    python array
    responsive and functional programming RxJava
  • 原文地址:https://www.cnblogs.com/tianzhijiexian/p/4295420.html
Copyright © 2011-2022 走看看