1. 程式人生 > >Android框架之路——聊天Demo實現

Android框架之路——聊天Demo實現

一、所用技術

  • GreenDao儲存聊天資料;
  • RecyclerView根據viewtype顯示聊天介面;
  • butterknife繫結view;

   如果這些你還沒有了解,你可以參考這些文章:

二、實現效果

後臺每5s傳送資料過來,儲存到資料庫中,並顯示到介面上,使用者可以傳送文字,儲存到資料庫並顯示。.9的圖片比較醜,缺一個美工姑娘,歡迎聯絡。。。

三、總體思路

通過RecyclerView的viewType來決定載入左右聊天佈局,通過greendao來操作資料庫。一些細節問題較多,需要逐個解決。

  1. 編寫activity_main.xml,主要由一個RecyclerView和下方的EditText輸入框以及傳送按鈕組成。

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:padding="5dp"
        tools:context="com.ping.chatdemo.activity.MainActivity">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_chatList"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@+id/linearLayout">
    
        </android.support.v7.widget.RecyclerView>
    
        <LinearLayout
            android:layout_alignParentBottom="true"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/linearLayout">
            <android.support.design.widget.TextInputLayout
                android:layout_width="0dp"
                android:layout_weight="6"
                android:layout_height="50dp">
                <EditText
                    android:id="@+id/et_content"
                    android:hint="請輸入文字..."
                    android:textSize="15dp"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"/>
            </android.support.design.widget.TextInputLayout>
    
            <Button
                android:id="@+id/bt_send"
                android:padding="10dp"
                android:textSize="15dp"
                android:text="傳送"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </LinearLayout>
    
    </RelativeLayout>
    
  2. 看一下我們的聊天佈局,分為左右倆個佈局檔案,一個TextView顯示當前時間,然後就是聊天頭像與內容;
    左佈局:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:background="@android:color/white"
                  android:orientation="vertical"
                  android:paddingBottom="5dp"
                  android:paddingLeft="12dp"
                  android:paddingRight="12dp"
                  android:paddingTop="3dp">
    
        <TextView
            android:id="@+id/tv_left_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="2015-6-6 06:06:06"/>
    
        <RelativeLayout
            android:layout_marginTop="2dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <ImageView
                android:id="@+id/img_ble"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_alignParentLeft="true"
                android:layout_marginRight="4dp"
                android:src="@drawable/ic_ble"/>
    
            <TextView
                android:id="@+id/tv_msg_left"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:textSize="13dp"
                android:layout_marginRight="50dp"
                android:background="@drawable/imageleft"
                android:layout_toRightOf="@id/img_ble"
                android:textColor="@android:color/white"/>
        </RelativeLayout>
    </LinearLayout>
    

    右佈局:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="@android:color/white"
                    android:orientation="vertical"
                    android:paddingBottom="5dp"
                    android:paddingLeft="12dp"
                    android:paddingRight="12dp"
                    android:paddingTop="3dp">
    
        <TextView
            android:id="@+id/tv_right_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="2015-6-6 06:06:06"/>
    
        <RelativeLayout
            android:layout_marginTop="2dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <ImageView
                android:id="@+id/img_phone"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_alignParentRight="true"
                android:layout_marginLeft="4dp"
                android:src="@drawable/ic_phone"/>
    
            <TextView
                android:id="@+id/tv_msg_right"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:textSize="13dp"
                android:layout_marginLeft="50dp"
                android:background="@drawable/imageright"
                android:layout_toLeftOf="@id/img_phone"
                android:textColor="@android:color/black"/>
    
        </RelativeLayout>
    
    
    </LinearLayout>
    
  3. 看一下我們的Msg.java聊天實體,裡面包含了主鍵_id,聊天時間,聊天內容和聊天佈局型別,此類方法是通過greendao註解生成的;

    @Entity
    public class Msg {
    
        public static final int TYPE_BLE = 0;
        public static final int TYPE_PHONE = 1;
    
        @Id(autoincrement = true)
        private Long _id;
        @NotNull
        private String content;
        @NotNull
        private int type;
        @NotNull
        private String time;
        @Generated(hash = 1787798591)
        public Msg(Long _id, @NotNull String content, int type, @NotNull String time) {
            this._id = _id;
            this.content = content;
            this.type = type;
            this.time = time;
        }
        @Generated(hash = 23037457)
        public Msg() {
        }
        public Long get_id() {
            return this._id;
        }
        public void set_id(long _id) {
            this._id = _id;
        }
        public String getContent() {
            return this.content;
        }
        public void setContent(String content) {
            this.content = content;
        }
        public int getType() {
            return this.type;
        }
        public void setType(int type) {
            this.type = type;
        }
        public String getTime() {
            return this.time;
        }
        public void setTime(String time) {
            this.time = time;
        }
    
        @Override
        public String toString() {
            return "Msg{" +
                    "_id=" + _id +
                    ", content='" + content + '\'' +
                    ", type=" + type +
                    ", time='" + time + '\'' +
                    '}';
        }
        public void set_id(Long _id) {
            this._id = _id;
        }
    }
    
  4. 接下來我們要著手編寫聊天的Apater了,在Adapter中我們需要根據Msg的viewType來返回不同的holder,即渲染不同的檢視。下面就是我們ChatAdapter的具體實現;

    public class ChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
        private LayoutInflater mLayoutInflater;
        private Context mContext;
        private List<Msg> mDatas;
    
        public ChatAdapter(Context context, List<Msg> datas) {
            mContext = context;
            mLayoutInflater = LayoutInflater.from(mContext);
            mDatas = datas;
        }
    
        //新增訊息顯示在RecyclerView中
        public void addItem(Msg msg) {
            mDatas.add(msg);
            notifyDataSetChanged();
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == Msg.TYPE_BLE) {
                View view = mLayoutInflater.inflate(R.layout.item_chat_left, parent, false);
                return new ChatLeftViewHolder(view);
            } else {
                View view = mLayoutInflater.inflate(R.layout.item_chat_right, parent, false);
                return new ChatRightViewHolder(view);
            }
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            Msg msg = mDatas.get(position);
            String time = msg.getTime();
            String content = msg.getContent();
            if(holder instanceof ChatLeftViewHolder){
                ((ChatLeftViewHolder) holder).mTvLeftTime.setText(time);
                ((ChatLeftViewHolder) holder).mTvMsgLeft.setText(content);
            }else if(holder instanceof ChatRightViewHolder){
                ((ChatRightViewHolder) holder).mTvRightTime.setText(time);
                ((ChatRightViewHolder) holder).mTvMsgRight.setText(content);
            }
        }
    
        @Override
        public int getItemViewType(int position) {
            return mDatas.get(position).getType();
        }
    
        @Override
        public int getItemCount() {
            return mDatas.size();
        }
    
        static class ChatLeftViewHolder extends RecyclerView.ViewHolder {
            @BindView(R.id.tv_left_time)
            TextView mTvLeftTime;
            @BindView(R.id.tv_msg_left)
            TextView mTvMsgLeft;
    
            ChatLeftViewHolder(View view) {
                super(view);
                ButterKnife.bind(this, view);
            }
        }
    
        static class ChatRightViewHolder extends RecyclerView.ViewHolder{
            @BindView(R.id.tv_right_time)
            TextView mTvRightTime;
            @BindView(R.id.tv_msg_right)
            TextView mTvMsgRight;
    
            ChatRightViewHolder(View view) {
                super(view);
                ButterKnife.bind(this, view);
            }
        }
    }
    
  5. 編寫完上述的一些關於介面顯示的東西后,我們要來繼續完成我們的資料庫操作方法,和這篇教程一樣,我們需要單例模式來封裝一個DaoManager類,基本不怎麼變化,變化的是我們Util,我們針對這次的Msg實體編寫MsgDaoUtil類如下。這裡我們還給其注入了一個監聽器,用來監聽是否資料庫進行資料插入操作了;

    public class MsgDaoUtil {
        private static final String TAG = MsgDaoUtil.class.getSimpleName();
        private DaoManager mManager;
        private OnDbUpdateListener mUpdateListener;
    
        public void setUpdateListener(OnDbUpdateListener updateListener) {
            mUpdateListener = updateListener;
        }
    
        public MsgDaoUtil(Context context){
            mManager = DaoManager.getInstance();
            mManager.init(context);
        }
    
        /**
         * 完成msg記錄的插入,如果表未建立,先建立Msg表
         * @param msg
         * @return
         */
        public boolean insertMsg(Msg msg){
            boolean flag = false;
            flag = mManager.getDaoSession().getMsgDao().insert(msg) == -1 ? false : true;
            if(flag)
                mUpdateListener.onUpdate(msg);
            Log.i(TAG, "insert Msg :" + flag + "-->" + msg.toString());
            return flag;
        }
    
        /**
         * 查詢所有記錄
         * @return
         */
        public List<Msg> queryAllMsg(){
            return mManager.getDaoSession().loadAll(Msg.class);
        }
    }
    
  6. 最後就是在MainActivity.java中完成我們的業務邏輯了。這裡面有幾個細節問題,一個是開啟頁面加載出資料庫裡的聊天記錄,另一個是當下滑RecyclerView時需要隱藏軟鍵盤,還有一個要監聽資料庫的插入操作,當然,RecyclerView佈滿時,來記錄後要自動上滑,顯示最新訊息。

    public class MainActivity extends AppCompatActivity {
    
        private List<Msg> mMsgs;
        private MsgDaoUtil mMsgDaoUtil;
        private ChatAdapter mAdapter;
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        @BindView(R.id.rv_chatList)
        RecyclerView mRvChatList;
        @BindView(R.id.et_content)
        EditText mEtContent;
        @BindView(R.id.bt_send)
        Button mBtSend;
    
        //後臺定時5s傳送資料
        Handler handler = new Handler();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                addMsg(new Msg(null, "來資料了!", Msg.TYPE_BLE, df.format(new Date())));
                handler.postDelayed(this, 5000);
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ButterKnife.bind(this);
    
            mMsgDaoUtil = new MsgDaoUtil(this);
            //載入歷史聊天記錄
            mMsgs = mMsgDaoUtil.queryAllMsg();
    
            LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
            mRvChatList.setLayoutManager(linearLayoutManager);
            mAdapter = new ChatAdapter(this, mMsgs);
            mRvChatList.setAdapter(mAdapter);
            //初試載入歷史記錄呈現最新訊息
            mRvChatList.scrollToPosition(mAdapter.getItemCount() - 1);
    
            mMsgDaoUtil.setUpdateListener(new OnDbUpdateListener() {
                @Override
                public void onUpdate(Msg msg) {
                    mAdapter.addItem(msg);
                    //鋪滿屏幕後呈現最新訊息
                    mRvChatList.scrollToPosition(mAdapter.getItemCount() - 1);
                }
            });
    
            //設定下滑隱藏軟鍵盤
            mRvChatList.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
                    if (dy < -10) {
                        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                        imm.hideSoftInputFromWindow(mEtContent.getWindowToken(), 0);
                    }
                }
            });
    
            handler.postDelayed(runnable, 5000);
        }
    
        private boolean addMsg(Msg msg) {
            return  mMsgDaoUtil.insertMsg(msg);
        }
    
    
        @OnClick(R.id.bt_send)
        public void onViewClicked() {
            String content = mEtContent.getText().toString();
            addMsg(new Msg(null, content, Msg.TYPE_PHONE, df.format(new Date())));
            mEtContent.setText("");
        }
    }
    

四、Demo下載

   原始碼連結

個人公眾號:每日推薦一篇技術部落格,堅持每日進步一丟丟…歡迎關注,想建個微信群,主要討論安卓和Java語言,一起打基礎、用框架、學設計模式,菜雞變菜鳥,菜鳥再起飛,願意一起努力的話可以公眾號留言,謝謝…