Android框架之路——聊天Demo實現
阿新 • • 發佈:2019-02-20
一、所用技術
- GreenDao儲存聊天資料;
- RecyclerView根據viewtype顯示聊天介面;
- butterknife繫結view;
如果這些你還沒有了解,你可以參考這些文章:
二、實現效果
後臺每5s傳送資料過來,儲存到資料庫中,並顯示到介面上,使用者可以傳送文字,儲存到資料庫並顯示。.9的圖片比較醜,缺一個美工姑娘,歡迎聯絡。。。
三、總體思路
通過RecyclerView的viewType來決定載入左右聊天佈局,通過greendao來操作資料庫。一些細節問題較多,需要逐個解決。
編寫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>
看一下我們的聊天佈局,分為左右倆個佈局檔案,一個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>
看一下我們的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; } }
接下來我們要著手編寫聊天的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); } } }
編寫完上述的一些關於介面顯示的東西后,我們要來繼續完成我們的資料庫操作方法,和這篇教程一樣,我們需要單例模式來封裝一個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); } }
最後就是在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(""); } }