Android框架之路——聊天Demo实现

来源:互联网 发布:电脑画质优化软件 编辑:程序博客网 时间:2024/06/16 17:07

一、所用技术

  • GreenDao存储聊天数据;
  • RecyclerView根据viewtype显示聊天界面;
  • butterknife绑定view;

   如果这些你还没有了解,你可以参考这些文章:

  • Android框架之路——GreenDao3.2.2的使用
  • Android框架之路——RecyclerView的使用
  • Android框架之路——ButterKnife的使用

二、实现效果

后台每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注解生成的;

    @Entitypublic 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语言,一起打基础、用框架、学设计模式,菜鸡变菜鸟,菜鸟再起飞,愿意一起努力的话可以公众号留言,谢谢…

0 0