前言;正常实现聊天功能想必大家都使用三方的Sdk比如环信融云集成的,但是聊天记录的保存只能有三天,想增加保存时长就需要花钱,so 我只好自己想办法实现了,这个demo是类似于留言板,并非即时通讯!只实现了表情文字图文混排,可以通过手动刷新实现即时通讯ok废话少说,先看效果图;
项目下载地址
https://github.com/PangHaHa12138/TestChatdemo
表情下载地址
大体思路:
1.先从布局开始,聊天界面就是多条目的listview,左右各算一种类型,这样正常语音,文字,图文混排,大图片,大表情,视频,都是x2倍的,聊天条目就是listview条目背景透明,然后imageview+给textview设置气泡的聊天背景
不过我现在暂时只实现了文字表情,然后键盘这块是gridview实现,写正则来过滤表情的编码,点击事件判断选中表情还是删除
感谢开源键盘控件:
https://github.com/w446108264/XhsEmoticonsKeyboard
继承XhsEmoticonsKeyboard控件写一个带表情的键盘
2.代码大体逻辑,发送的消息其实就是一个文本内容,通过SpannableStringBuilder和Spannable实现图文混排,其实就是把各种表情序列号也可以说是索引写到一个xml文件里,然后用一个map来存这些图片对应的编码,其实就是图片名字,然后通过正则来找到正确的索引,即xml里存的图片对应文件夹drawable下的
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(content); Spannable spannable = QqFilter.spannableFilter(tv_content.getContext(), spannableStringBuilder, content, EmoticonsKeyboardUtils.getFontHeight(tv_content), null); tv_content.setText(spannable);
然后就是listview的展示了,页面初始化的时候根据服务器返回的字段判断是别人发的在左边,还是我发的在右面,然后在adapter里调用图文混排的方法找到对应表情图片填充条目,发送的时候先检查是否是正确的表情字符,找到对应表情图片的集合map,找到对应的表情名字,然后和文字一起传到服务器,然后进行网络请求,上传成功之后再刷新界面,上拉加载的话是加载历史记录全部的,下拉刷新是请求最新的,每次请求都控制页面显示最多28条数据,也就是4页,上来初始化页面也是,然后可以通过聊完上拉不停的重复手动实现即时通讯
当然这是开玩笑了
下面上代码:
主要布局
<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"> <fragment android:id="@+id/titlefragment" android:name="com.panghaha.it.testchatdemo.Titlefragment" android:layout_width="match_parent" android:layout_height="45dp"/> <!--<TextView--> <!--android:layout_centerInParent="true"--> <!--android:textSize="22sp"--> <!--android:text="在未来的日子里,\n努力让抛弃我的人\n始终觉得她们的决定是正确的"--> <!--android:layout_width="wrap_content"--> <!--android:layout_height="wrap_content" />--> <FrameLayout android:id="@+id/content" android:layout_above="@+id/bottom_navigation_bar" android:layout_below="@+id/titlefragment" android:layout_width="match_parent" android:layout_height="match_parent"> </FrameLayout> <com.ashokvarma.bottomnavigation.BottomNavigationBar android:id="@+id/bottom_navigation_bar" android:layout_gravity="bottom" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="wrap_content"/> </RelativeLayout>主界面,下面四个按钮,切换四个fragment
聊天界面布局 最外层用自己继承
XhsEmoticonsKeyBoard 键盘类
<com.panghaha.it.testchatdemo.common.SimpleUserdefEmoticonsKeyBoard xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:id="@+id/keyboard" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:orientation="vertical" android:background="@drawable/pic_bg3x" android:layout_width="match_parent" android:layout_height="match_parent"> <!--<include layout="@layout/activity_right_toobar"/>--> <android.support.v7.widget.Toolbar android:id="@+id/toobaraaa" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/titbar" android:minHeight="?attr/actionBarSize"> <TextView android:id="@+id/toolbarmtit" style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:lines="1" android:ellipsize="end" android:text="安琪宝贝" android:scrollHorizontally="true" android:textColor="@color/white" android:layout_gravity="center" /> <!--自定义toolbar的title 和subtitle --> </android.support.v7.widget.Toolbar> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/uploadmore" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/lv_chat" android:layout_width="match_parent" android:layout_height="match_parent" android:cacheColorHint="#00000000" android:divider="@null" android:fadingEdge="none" android:fitsSystemWindows="true" android:listSelector="#00000000" android:scrollbarStyle="outsideOverlay" android:scrollingCache="false" android:smoothScrollbar="true" android:stackFromBottom="true" /> </android.support.v4.widget.SwipeRefreshLayout> </LinearLayout> </com.panghaha.it.testchatdemo.common.SimpleUserdefEmoticonsKeyBoard>
键盘类
public class SimpleUserdefEmoticonsKeyBoard extends XhsEmoticonsKeyBoard { public final int APPS_HEIGHT = 120; public SimpleUserdefEmoticonsKeyBoard(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void inflateKeyboardBar(){ mInflater.inflate(R.layout.view_keyboard_userdef, this); } @Override protected View inflateFunc(){ return mInflater.inflate(R.layout.view_func_emoticon_userdef, null); } @Override public void reset() { EmoticonsKeyboardUtils.closeSoftKeyboard(getContext()); mLyKvml.hideAllFuncView(); mBtnFace.setImageResource(R.drawable.chatting_emoticons); } @Override public void onFuncChange(int key) { if (FUNC_TYPE_EMOTION == key) { mBtnFace.setImageResource(R.drawable.chatting_softkeyboard); } else { mBtnFace.setImageResource(R.drawable.chatting_emoticons); } checkVoice(); } @Override public void OnSoftClose() { super.OnSoftClose(); if (mLyKvml.getCurrentFuncKey() == FUNC_TYPE_APPPS) { setFuncViewHeight(EmoticonsKeyboardUtils.dip2px(getContext(), APPS_HEIGHT)); } } @Override protected void showText() { mEtChat.setVisibility(VISIBLE); mBtnFace.setVisibility(VISIBLE); mBtnVoice.setVisibility(GONE); } @Override protected void showVoice() { mEtChat.setVisibility(GONE); mBtnFace.setVisibility(GONE); mBtnVoice.setVisibility(VISIBLE); reset(); } @Override protected void checkVoice() { if (mBtnVoice.isShown()) { mBtnVoiceOrText.setImageResource(R.drawable.chatting_softkeyboard); } else { mBtnVoiceOrText.setImageResource(R.drawable.chatting_vodie); } } @Override public void onClick(View v) { int i = v.getId(); if (i == com.keyboard.view.R.id.btn_voice_or_text) { if (mEtChat.isShown()) { mBtnVoiceOrText.setImageResource(R.drawable.chatting_softkeyboard); showVoice(); } else { showText(); mBtnVoiceOrText.setImageResource(R.drawable.chatting_vodie); EmoticonsKeyboardUtils.openSoftKeyboard(mEtChat); } } else if (i == com.keyboard.view.R.id.btn_face) { toggleFuncView(FUNC_TYPE_EMOTION); } else if (i == com.keyboard.view.R.id.btn_multimedia) { toggleFuncView(FUNC_TYPE_APPPS); setFuncViewHeight(EmoticonsKeyboardUtils.dip2px(getContext(), APPS_HEIGHT)); } }表情过滤和定位类
public class QqFilter extends EmoticonFilter { public static final int WRAP_DRAWABLE = -1; private int emoticonSize = -1; public static final Pattern QQ_RANGE = Pattern.compile("\\[[a-zA-Z0-9\\u4e00-\\u9fa5]+\\]"); public static Matcher getMatcher(CharSequence matchStr) { return QQ_RANGE.matcher(matchStr)