制作高仿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中。這樣你就可以完整的測試了~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM