android:手搓一个即时消息聊天框(包含消息记录)
Posted Wenlong Yang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android:手搓一个即时消息聊天框(包含消息记录)相关的知识,希望对你有一定的参考价值。
先看一下效果
1.后端
要实现这个,先说一下后端要实现的接口
1.创建会话id
传入“发送id”和“接收id”给服务端,服务端去创建“会话id”
比如
get请求:http://xxxx:8110/picasso/createSession?fromUserId=1&toUserId=2
返回seesionId,也就是会话id
"code": 200,
"data":
"seesionId": 13
2.获取消息记录
get请求:http://xxxx:8110/picasso/msgList?sessionId=13
返回如下:我让后台按这个来写的,虽然后端表示接口很丑陋~
这个返回的有个要求,就是日期要升序,第一页要显示最新的15条,这个应该能理解吧,聊天框里面最新的日期都是要显示在最下面的,当然,也可以安卓端手动再排序:
Collections.reverse(msgChat1);
"code": 200,
"data":
"total": 30,
"current": 2,
"pages": 2,
"size": 15,
"list": [
"id": 195,
"fromUserId": 2,
"fromUserName": "青山不改",
"toUserId": 1,
"toUserName": "绿水长流",
"content": "123123",
"createTime": "2023-03-30 20:42:21",
"unReadFlag": 1
,
"id": 196,
"fromUserId": 1,
"fromUserName": "青山不改",
"toUserId": 2,
"toUserName": "绿水长流",
"content": "rqwewqeqw",
"createTime": "2023-03-30 20:42:26",
"unReadFlag": 0
,
"id": 197,
"fromUserId": 2,
"fromUserName": "青山不改",
"toUserId": 1,
"toUserName": "绿水长流",
"content": "rwerew",
"createTime": "2023-03-30 20:43:01",
"unReadFlag": 1
,
"id": 198,
"fromUserId": 1,
"fromUserName": "青山不改",
"toUserId": 2,
"toUserName": "绿水长流",
"content": "1---2",
"createTime": "2023-03-30 20:43:11",
"unReadFlag": 0
,
"id": 199,
"fromUserId": 2,
"fromUserName": "青山不改",
"toUserId": 1,
"toUserName": "绿水长流",
"content": "2----1",
"createTime": "2023-03-30 20:43:21",
"unReadFlag": 1
,
"id": 200,
"fromUserId": 2,
"fromUserName": "青山不改",
"toUserId": 1,
"toUserName": "绿水长流",
"content": "23123",
"createTime": "2023-03-30 20:52:27",
"unReadFlag": 1
,
"id": 201,
"fromUserId": 1,
"fromUserName": "青山不改",
"toUserId": 2,
"toUserName": "绿水长流",
"content": "333",
"createTime": "2023-03-30 20:52:31",
"unReadFlag": 0
,
"id": 202,
"fromUserId": 2,
"fromUserName": "青山不改",
"toUserId": 1,
"toUserName": "绿水长流",
"content": "333",
"createTime": "2023-03-30 21:02:27",
"unReadFlag": 1
,
"id": 203,
"fromUserId": 1,
"fromUserName": "青山不改",
"toUserId": 2,
"toUserName": "绿水长流",
"content": "444",
"createTime": "2023-03-30 21:02:42",
"unReadFlag": 0
,
"id": 204,
"fromUserId": 2,
"fromUserName": "青山不改",
"toUserId": 1,
"toUserName": "绿水长流",
"content": "22",
"createTime": "2023-03-30 21:03:57",
"unReadFlag": 1
,
"id": 205,
"fromUserId": 1,
"fromUserName": "青山不改",
"toUserId": 2,
"toUserName": "绿水长流",
"content": "uqwe",
"createTime": "2023-03-30 21:05:36",
"unReadFlag": 0
,
"id": 206,
"fromUserId": 1,
"fromUserName": "青山不改",
"toUserId": 2,
"toUserName": "绿水长流",
"content": "yyy",
"createTime": "2023-03-30 21:07:46",
"unReadFlag": 0
,
"id": 207,
"fromUserId": 2,
"fromUserName": "青山不改",
"toUserId": 1,
"toUserName": "绿水长流",
"content": "2---1",
"createTime": "2023-03-30 21:12:28",
"unReadFlag": 1
,
"id": 208,
"fromUserId": 2,
"fromUserName": "青山不改",
"toUserId": 1,
"toUserName": "绿水长流",
"content": "2----1",
"createTime": "2023-03-30 21:12:35",
"unReadFlag": 1
,
"id": 209,
"fromUserId": 1,
"fromUserName": "青山不改",
"toUserId": 2,
"toUserName": "绿水长流",
"content": "1----2",
"createTime": "2023-03-30 21:12:59",
"unReadFlag": 0
]
3.websocket,让后端按下面这个要求让客户端进行连接的,具体怎么搓的我也不清楚,好像是消息之间的转发
ws://"+IP+":8110/websocket/发送id/会话id
2.APP安卓端
因为安卓端是我写的,所以~代码我可以尽量贴的完整一点
1.聊天的布局:
我这个写法比较简单,就直接将对方的消息和我的消息写在一个item里面了,对比一下数据是不是自己发的,是的话就显示自己的消息ui,不是就隐藏自己的ui
代码布局如下:
<?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:layout_gravity="right"
android:orientation="vertical">
<TextView
android:id="@+id/tv_time"
android:gravity="center"
android:textSize="8dp"
android:text="2022-10-22 12:00"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="@+id/ll_left"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="left"
>
<TextView
android:visibility="gone"
android:id="@+id/to_user_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/to_user_head"
android:layout_margin="10dp"
android:background="@drawable/ic_head_rec_bg"
android:text="LL"
android:gravity="center"
android:textColor="@color/white"
android:layout_width="50dp"
android:layout_height="50dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginRight="50dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="16dp"
android:gravity="left">
<TextView
android:visibility="gone"
android:id="@+id/to_user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#cccccc"
android:textStyle="bold"
android:textSize="12sp"
android:text="demo_9999"
/>
<TextView
android:id="@+id/to_user_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="14sp"
android:gravity="left"
android:paddingTop="6dp"
android:paddingBottom="7dp"
android:paddingRight="10dp"
android:paddingLeft="10dp"
android:background="@drawable/bg_popo_left"
android:text="mmm"
/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_right"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="right"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="16dp"
android:layout_marginRight="5dp"
android:layout_marginLeft="50dp"
android:gravity="right">
<TextView
android:visibility="gone"
android:id="@+id/from_user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#cccccc"
android:textStyle="bold"
android:textSize="12sp"
android:text="demo_9999"
/>
<TextView
android:id="@+id/from_user_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/theme"
android:textSize="14sp"
android:layout_marginBottom="5dp"
android:paddingTop="6dp"
android:paddingBottom="7dp"
android:paddingRight="10dp"
android:paddingLeft="10dp"
android:background="@drawable/bg_popo_right"
android:text="毕加索"
/>
</LinearLayout>
<TextView
android:id="@+id/from_user_head"
android:layout_margin="10dp"
android:background="@drawable/ic_head_bg"
android:text="LL"
android:gravity="center"
android:textColor="@color/white"
android:layout_width="50dp"
android:layout_height="50dp"/>
<TextView
android:id="@+id/from_user_id"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
2.消息框的总布局
这个没啥重要的,只是我额外用了一个自定义的listview,这个是用来监听下拉的,毕竟会有历史消息,下拉就直接加载上一页了
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="-25dp"
android:background="#F6F6F6"
android:fitsSystemWindows="true"
android:orientation="vertical">
<com.picasso.module.ui.CustomRefreshListView
android:id="@+id/msg_list"
android:layout_weight="1"
android:divider="#00000000"
android:scrollbars="none"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff"
android:elevation="5dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="10dp">
<EditText
android:id="@+id/id_input"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginLeft="15dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_weight="1"
android:background="@drawable/bg_round_corner_grey"
android:gravity="center_vertical"
android:hint="来聊吧~"
android:paddingLeft="15dp"
android:textSize="12dp" />
<RelativeLayout
android:layout_width="75dp"
android:layout_height="40dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/bg_btn_orange_back">
<TextView
android:id="@+id/send_btn"
android:layout_width="match_parent"
android:layout_height="40dp"
android:gravity="center"
android:text="发送"
android:textColor="#ffffff"
android:textSize="16sp" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
支持下拉的listview控件
public class CustomRefreshListView extends ListView implements AbsListView.OnScrollListener
/**
* 头布局
*/
private View headerView;
/**
* 头部布局的高度
*/
private int headerViewHeight;
/**
* 头部旋转的图片
*/
private ImageView iv_arrow;
/**
* 头部下拉刷新时状态的描述
*/
private TextView tv_state;
/**
* 下拉刷新时间的显示控件
*/
private TextView tv_time;
/**
* 底部布局
*/
private View footerView;
/**
* 底部旋转progressbar
*/
private ProgressBar pb_rotate;
/**
* 底部布局的高度
*/
private int footerViewHeight;
/**
* 按下时的Y坐标
*/
private int downY;
private final int PULL_REFRESH = 0;//下拉刷新的状态
private final int RELEASE_REFRESH = 1;//松开刷新的状态
private final int REFRESHING = 2;//正在刷新的状态
/**
* 当前下拉刷新处于的状态
*/
private int currentState = PULL_REFRESH;
/**
* 头部布局在下拉刷新改变时,图标的动画
*/
private RotateAnimation upAnimation,downAnimation;
/**
* 当前是否在加载数据
*/
private boolean isLoadingMore = false;
public CustomRefreshListView(Context context)
this(context,null);
public CustomRefreshListView(Context context, AttributeSet attrs)
super(context, attrs);
init();
private void init()
//设置滑动监听
setOnScrollListener(this);
//初始化头布局
initHeaderView();
//初始化头布局中图标的旋转动画
initRotateAnimation();
//初始化为尾布局
initFooterView();
/**
* 初始化headerView
*/
private void initHeaderView()
headerView = View.inflate(getContext(), R.layout.head_custom_listview, null);
iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
pb_rotate = (ProgressBar) headerView.findViewById(R.id.pb_rotate);
tv_state = (TextView) headerView.findViewById(R.id.tv_state);
tv_time = (TextView) headerView.findViewById(R.id.tv_time);
//测量headView的高度
headerView.measure(0, 0);
//获取高度,并保存
headerViewHeight = headerView.getMeasuredHeight();
//设置paddingTop = -headerViewHeight;这样,该控件被隐藏
headerView.setPadding(0, -headerViewHeight, 0, 0);
//添加头布局
addHeaderView(headerView);
/**
* 初始化旋转动画
*/
private void initRotateAnimation()
upAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
upAnimation.setDuration(300);
upAnimation.setFillAfter(true);
downAnimation = new RotateAnimation(-180, -360,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
downAnimation.setDuration(300);
downAnimation.setFillAfter(true);
//初始化底布局,与头布局同理
private void initFooterView()
footerView = View.inflate(getContext(), R.layout.foot_custom_listview, null);
footerView.measure(0, 0);
footerViewHeight = footerView.getMeasuredHeight();
footerView.setPadding(0, -footerViewHeight, 0, 0);
addFooterView(footerView);
@Override
public boolean onTouchEvent(MotionEvent ev)
switch (ev.getAction())
case MotionEvent.ACTION_DOWN:
//获取按下时y坐标
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if(currentState==REFRESHING)
//如果当前处在滑动状态,则不做处理
break;
//手指滑动偏移量
int deltaY = (int) (ev.getY() - downY);
//获取新的padding值
int paddingTop = -headerViewHeight + deltaY;
if(paddingTop>-headerViewHeight && getFirstVisiblePosition()==0)
//向下滑,且处于顶部,设置padding值,该方法实现了顶布局慢慢滑动显现
headerView.setPadding(0, paddingTop, 0, 0);
if(paddingTop>=0 && currentState==PULL_REFRESH)
//从下拉刷新进入松开刷新状态
currentState = RELEASE_REFRESH;
//刷新头布局
refreshHeaderView();
else if (paddingTop<0 && currentState==RELEASE_REFRESH)
//进入下拉刷新状态
currentState = PULL_REFRESH;
refreshHeaderView();
return true;//拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动
break;
case MotionEvent.ACTION_UP:
if(currentState==PULL_REFRESH)
//仍处于下拉刷新状态,未滑动一定距离,不加载数据,隐藏headView
headerView.setPadding(0, -headerViewHeight, 0, 0);
else if (currentState==RELEASE_REFRESH)
//滑倒一定距离,显示无padding值得headcView
headerView.setPadding(0, 0, 0, 0);
//设置状态为刷新
currentState = REFRESHING;
//刷新头部布局
refreshHeaderView();
if(listener!=null)
//接口回调加载数据
listener.onPullRefresh();
break;
return super.onTouchEvent(ev);
/**
* 根据currentState来更新headerView
*/
private void refreshHeaderView()
switch (currentState)
case PULL_REFRESH:
tv_state.setText("下拉刷新");
iv_arrow.startAnimation(downAnimation);
break;
case RELEASE_REFRESH:
// tv_state.setText("松开刷新");
iv_arrow.startAnimation(upAnimation);
break;
case REFRESHING:
iv_arrow.clearAnimation();//因为向上的旋转动画有可能没有执行完
iv_arrow.setVisibility(View.INVISIBLE);
pb_rotate.setVisibility(View.VISIBLE);
// tv_state.setText("正在刷新...");
break;
/**
* 完成刷新操作,重置状态,在你获取完数据并更新完adater之后,去在UI线程中调用该方法
*/
public void completeRefresh()
if(isLoadingMore)
//重置footerView状态
footerView.setPadding(0, -footerViewHeight, 0, 0);
isLoadingMore = false;
else
//重置headerView状态
headerView.setPadding(0, -headerViewHeight, 0, 0);
currentState = PULL_REFRESH;
pb_rotate.setVisibility(View.INVISIBLE);
iv_arrow.setVisibility(View.VISIBLE);
tv_state.setText("下拉刷新");
tv_time.setText("最后刷新:"+getCurrentTime());
/**
* 获取当前系统时间,并格式化
* @return
*/
private String getCurrentTime()
SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
return format.format(new Date());
private OnRefreshListener listener;
public void setOnRefreshListener(OnRefreshListener listener)
this.listener = listener;
public interface OnRefreshListener
void onPullRefresh();
void onLoadingMore();
/**
* SCROLL_STATE_IDLE:闲置状态,就是手指松开
* SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动,就是按着来滑动
* SCROLL_STATE_FLING:快速滑动后松开
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
if(scrollState==OnScrollListener.SCROLL_STATE_IDLE
&& getLastVisiblePosition()==(getCount()-1) &&!isLoadingMore)
isLoadingMore = true;
footerView.setPadding(0, 0, 0, 0);//显示出footerView
setSelection(getCount());//让listview最后一条显示出来,在页面完全显示出底布局
if(listener!=null)
listener.onLoadingMore();
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount)
里面有些控件之类的,可以自己画画补充,我是随便放的
head_custom_listview.xml
<?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="match_parent"
android:orientation="vertical">
<ProgressBar
android:id="@+id/pb_rotate"
android:layout_width="match_parent"
android:layout_height="20dp"/>
<TextView
android:id="@+id/tv_state"
android:layout_width="match_parent"
android:layout_height="20dp"/>
<TextView
android:id="@+id/tv_time"
android:layout_width="match_parent"
android:layout_height="20dp"/>
<ImageView
android:id="@+id/iv_arrow"
android:layout_width="match_parent"
android:layout_height="20dp"/>
</LinearLayout>
3.websocket代码
记住要在清单文件里面添加这一行
<service
android:name=".websocket.JWebSocketClientService1"
android:enabled="true"
android:exported="true" />
下面是我百度来的,改一改可以直接用 JWebSocketClientService1
public class JWebSocketClientService1 extends Service
private String TAG="JWebSocketClientService";
private URI uri;
public JWebSocketClient client;
private JWebSocketClientBinder mBinder = new JWebSocketClientBinder();
//用于Activity和service通讯
public class JWebSocketClientBinder extends Binder
public JWebSocketClientService1 getService()
return JWebSocketClientService1.this;
@Nullable
@Override
public IBinder onBind(Intent intent)
return mBinder;
@Override
public void onCreate()
super.onCreate();
//初始化websocket
initSocketClient();
mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//开启心跳检测
@Override
public int onStartCommand(Intent intent, int flags, int startId)
return super.onStartCommand(intent, flags, startId);
/**
* 初始化websocket连接
*/
private void initSocketClient()
URI uri = URI.create(Constant.webSocketHost+"/"+Constant.SendId+"/"+Constant.ChatId);//测试使用
//Log.e("initSocketClient",Constant.webSocketHost+"/"+Constant.SendId+"/"+Constant.ChatId);
Log.e("SocketInfo-host",Constant.webSocketHost+"/"+Constant.SendId+"/"+Constant.ChatId);
client = new JWebSocketClient(uri)
@Override
public void onMessage(String message)
Log.e("JWebSocketClientService", "收到的消息:" + message);
Intent intent = new Intent();//广播接收到的消息,在Activity接收
intent.setAction("com.picasso.shanghai.activity.ChatActivity$ChatMessageReceiver");
intent.putExtra("message", message);
sendBroadcast(intent);
@Override
public void onOpen(ServerHandshake handshakedata)
super.onOpen(handshakedata);
Log.e("JWebSocketClientService", "websocket连接成功");
;
connect();
/**
* 连接websocket
*/
private void connect()
new Thread()
@Override
public void run()
try
//connectBlocking多出一个等待操作,会先连接再发送,否则未连接发送会报错
client.connectBlocking();
catch (InterruptedException e)
e.printStackTrace();
.start();
/**
* 发送消息
*
* @param msg
*/
public void sendMsg(String msg)
if (null != client)
Log.e("JWebSocketClientService", "发送的消息:" + msg);
client.send(msg);
else
// -------------------------------------websocket心跳检测------------------------------------------------
private static final long HEART_BEAT_RATE = 30 * 1000;//每隔30秒进行一次对长连接的心跳检测
private Handler mHandler = new Handler();
private Runnable heartBeatRunnable = new Runnable()
@Override
public void run()
Log.e("JWebSocketClientService", "心跳包检测websocket连接状态");
if (client != null)
if (client.isClosed())
reconnectWs();
else
Log.e("JWebSocketClientService", "!client.isClosed()");
//业务逻辑 这里如果服务端需要心跳包为了防止断开 需要不断发送消息给服务端
// client.send("");
else
//如果client已为空,重新初始化连接
client = null;
Log.e("JWebSocketClientService", "client = null");
initSocketClient();
//每隔一定的时间,对长连接进行一次心跳检测
mHandler.postDelayed(this, HEART_BEAT_RATE);
;
/**
* 开启重连
*/
private void reconnectWs()
mHandler.removeCallbacks(heartBeatRunnable);
new Thread()
@Override
public void run()
try
Log.e("JWebSocketClientService", "开启重连");
client.reconnectBlocking();
catch (InterruptedException e)
e.printStackTrace();
.start();
@Override
public void onDestroy()
Log.d(TAG, "onDestroy: ....");
super.onDestroy();
JWebSocketClient
public class JWebSocketClient extends WebSocketClient
public JWebSocketClient(URI serverUri)
super(serverUri, new Draft_6455());
@Override
public void onOpen(ServerHandshake handshakedata)
Log.e("JWebSocketClient", "onOpen()");
@Override
public void onMessage(String message)
Log.e("JWebSocketClient", "onMessage()");
@Override
public void onClose(int code, String reason, boolean remote)
Log.e("JWebSocketClient", "onClose()");
@Override
public void onError(Exception ex)
Log.e("JWebSocketClient", "onError()");
4.实体类,和适配器
MsgChat.java
public class MsgChat
private int fromUserId,toUserId,unReadFlag;
private String fromUserName,toUserName,createTime,content;
public int getFromUserId()
return fromUserId;
public void setFromUserId(int fromUserId)
this.fromUserId = fromUserId;
public int getToUserId()
return toUserId;
public void setToUserId(int toUserId)
this.toUserId = toUserId;
public String getCreateTime()
return createTime;
public void setCreateTime(String createTime)
this.createTime = createTime;
public int getUnReadFlag()
return unReadFlag;
public void setUnReadFlag(int unReadFlag)
this.unReadFlag = unReadFlag;
public String getFromUserName()
return fromUserName;
public void setFromUserName(String fromUserName)
this.fromUserName = fromUserName;
public String getToUserName()
return toUserName;
public void setToUserName(String toUserName)
this.toUserName = toUserName;
public String getContent()
return content;
public void setContent(String content)
this.content = content;
MessageAdapter.java
public class MessageAdapter extends BaseAdapter implements ListAdapter
private ArrayList<MsgChat> msgChatArrayList;
private int id;
private Context mcontext;
private LayoutInflater inflater;
public MessageAdapter(int sub_item, Context mcontext, ArrayList<MsgChat> msgChatArrayList)
this.msgChatArrayList = msgChatArrayList;
this.mcontext = mcontext;
this.id = sub_item;
inflater = LayoutInflater.from(mcontext);
@Override
public int getCount()
return msgChatArrayList.size();
@Override
public Object getItem(int i)
return msgChatArrayList.get(i);
@Override
public long getItemId(int i)
return i;
public void addItem(MsgChat msgChat, ArrayList<MsgChat> msgChats)
msgChats.add(msgChat);
this.msgChatArrayList = msgChats;
this.notifyDataSetChanged();
@SuppressLint("WrongConstant")
@Override
public View getView(int position, View view, ViewGroup viewGroup)
LinearLayout ll_left = null;
LinearLayout ll_right = null;
TextView tv_time = null;
TextView from_user_id = null;
TextView from_user_name = null;
TextView from_user_head = null;
TextView from_user_msg = null;
TextView to_user_id = null;
TextView to_user_name = null;
TextView to_user_head = null;
TextView to_user_msg = null;
ViewHolder viewHolder;
if (view == null)
//获取item视图类型
view = inflater.inflate(id, null);
ll_left = (LinearLayout) view.findViewById(R.id.ll_left);
ll_right = (LinearLayout) view.findViewById(R.id.ll_right);
tv_time = (TextView) view.findViewById(R.id.tv_time);
from_user_id = (TextView) view.findViewById(R.id.from_user_id);
from_user_name = (TextView) view.findViewById(R.id.from_user_name);
from_user_head = (TextView) view.findViewById(R.id.from_user_head);
from_user_msg = (TextView) view.findViewById(R.id.from_user_msg);
to_user_id = (TextView) view.findViewById(R.id.to_user_id);
to_user_name = (TextView) view.findViewById(R.id.to_user_name);
to_user_head = (TextView) view.findViewById(R.id.to_user_head);
to_user_msg = (TextView) view.findViewById(R.id.to_user_msg);
view.setTag(new ViewHolder(ll_left,ll_right,tv_time,from_user_id,from_user_head,from_user_name,from_user_msg,to_user_id,to_user_head,to_user_name,to_user_msg));
else
ViewHolder viewHolder1 = (ViewHolder) view.getTag(); // 重新获取ViewHolder
ll_left = viewHolder1.ll_left;
ll_right = viewHolder1.ll_right;
tv_time = viewHolder1.tv_time;
from_user_id = viewHolder1.from_user_id;
from_user_name = viewHolder1.from_user_name;
from_user_head = viewHolder1.from_user_head;
from_user_msg = viewHolder1.from_user_msg;
to_user_id = viewHolder1.to_user_id;
to_user_name = viewHolder1.to_user_name;
to_user_head = viewHolder1.to_user_head;
to_user_msg = viewHolder1.to_user_msg;
MsgChat msgChat = (MsgChat) msgChatArrayList.get(position);
from_user_id.setText(msgChat.getFromUserId()+"");
if(msgChat.getFromUserId()==Constant.SendId)
ll_left.setVisibility(View.GONE);
ll_right.setVisibility(View.VISIBLE);
else
ll_right.setVisibility(View.GONE);
ll_left.setVisibility(View.VISIBLE);
try
from_user_name.setText(msgChat.getFromUserName().substring(0, 2));
catch(Exception e)
from_user_name.setText("NULL");
try
from_user_head.setText(msgChat.getFromUserName().substring(0, 2));
catch(Exception e)
from_user_head.setText("NULL");
from_user_msg.setText(msgChat.getContent()+"");
to_user_id.setText(msgChat.getToUserId()+"");
try
to_user_name.setText(msgChat.getToUserName().substring(0, 2));
catch(Exception e)
to_user_name.setText("NULL");
try
to_user_head.setText(msgChat.getToUserName().substring(0, 2));
catch(Exception e)
to_user_head.setText("NULL");
to_user_msg.setText(msgChat.getContent()+"");
tv_time.setText(msgChat.getCreateTime());
return view;
private final class ViewHolder
LinearLayout ll_left = null;
LinearLayout ll_right = null;
TextView tv_time = null;
TextView from_user_id = null;
TextView from_user_name = null;
TextView from_user_head = null;
TextView from_user_msg = null;
TextView to_user_id = null;
TextView to_user_name = null;
TextView to_user_head = null;
TextView to_user_msg = null;
public ViewHolder(LinearLayout ll_left,LinearLayout ll_right,TextView tv_time,TextView from_user_id,TextView from_user_head,TextView from_user_name,TextView from_user_msg,TextView to_user_id,TextView to_user_head,TextView to_user_name,TextView to_user_msg)
this.ll_left = ll_left;
this.ll_right = ll_right;
this.tv_time = tv_time;
this.from_user_id = from_user_id;
this.from_user_name = from_user_name;
this.from_user_head = from_user_head;
this.from_user_msg = from_user_msg;
this.to_user_id = to_user_id;
this.to_user_name = to_user_name;
this.to_user_head = to_user_head;
this.to_user_msg = to_user_msg;
5.ChatActivity核心代码
public class ChatActivity extends BaseActivity
private JWebSocketClientService1.JWebSocketClientBinder binder;
private JWebSocketClientService1 jWebSClientService;
private JWebSocketClient client;
private ChatMessageReceiver chatMessageReceiver;
private Intent intent;
private TextView send_btn;
private EditText id_input;
private CustomRefreshListView msg_list;
private ArrayList<MsgChat> msgChats = new ArrayList<>();
private MsgChat msgChat;
private MessageAdapter messageAdapter;
private int PAGES = 0;
private int CURRENT = 0;
private boolean StateY = false;//true 下拉,false 上拉
private class ChatMessageReceiver extends BroadcastReceiver
@Override
public void onReceive(Context context, Intent intent)
String message=intent.getStringExtra("message");
Log.e("SocketInfo-Receiver",message);
MsgChat msgChat = new MsgChat();
JSONObject msg = JSONObject.parseObject(message);
msgChat.setFromUserId(msg.getInteger("fromUserId"));
msgChat.setFromUserName(msg.getString("fromUserName"));
msgChat.setToUserId(msg.getInteger("toUserId"));
msgChat.setToUserName(msg.getString("toUserName"));
msgChat.setContent(msg.getString("content"));
msgChat.setCreateTime(msg.getString("createTime"));
msgChat.setUnReadFlag(msg.getInteger("unReadFlag"));
Log.e("====>>", Constant.SendId+"----"+msg.getInteger("fromUserId"));
Log.e("====>>", msg.getString("content"));
messageAdapter.addItem(msgChat,msgChats);
msg_list.setSelection(messageAdapter.getCount()-1);
private void doRegisterReceiver()
Log.e("ChatMessageReceiver", "广播的权限1");
chatMessageReceiver = new ChatMessageReceiver();
IntentFilter filter = new IntentFilter("com.picasso.shanghai.activity.ChatActivity$ChatMessageReceiver");
registerReceiver(chatMessageReceiver, filter);
private ServiceConnection serviceConnection = new ServiceConnection()
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder)
Log.e("this", "服务与活动成功绑定");
binder = (JWebSocketClientService1.JWebSocketClientBinder) iBinder;
jWebSClientService = binder.getService();
client = jWebSClientService.client;
@Override
public void onServiceDisconnected(ComponentName componentName)
Log.e("this", "服务与活动成功断开");
;
@Override
public int getLayout()
return R.layout.activity_chat;
@Override
public void initView()
setStatusBar();
//启动服务
startJWebSClientService();
//绑定服务
bindService();
//注册广播
doRegisterReceiver();
//初始化历史消息
initHistoryMsg(Constant.ChatId,"");
msg_list = findViewById(R.id.msg_list);
msg_list.setOnRefreshListener(new CustomRefreshListView.OnRefreshListener()
@Override
public void onPullRefresh()
if(CURRENT!=PAGES)
CURRENT++;
Log.e("list","xia"+CURRENT);
StateY = true;
initHistoryMsg(Constant.ChatId,String.valueOf(CURRENT));
msg_list.completeRefresh();
@Override
public void onLoadingMore()
msg_list.completeRefresh();
StateY = false;
Log.e("list","shang");
);
// msg_list.setLayoutManager(new LinearLayoutManager(this));
send_btn = findViewById(R.id.send_btn);
id_input = findViewById(R.id.id_input);
send_btn.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View view)
jWebSClientService.sendMsg(id_input.getText().toString());
id_input.setText("");
);
private Handler Thandler = new Handler()
@Override
public void handleMessage(Message msg)
switch (msg.what)
case 1:
messageAdapter = new MessageAdapter(R.layout.item_chat_msg_list,ChatActivity.this,msgChats);
msg_list.setAdapter(messageAdapter);
if(StateY)
//msg_list.setSelection(0);
else
msg_list.setSelection(messageAdapter.getCount()-1);
break;
;
//初始化历史消息
private void initHistoryMsg(int chatId,String pageNum)
ChatListener chatListener = new ChatListener();
chatListener.historyMessage(chatId,pageNum, new RequestCallback()
@Override
public void success(String str)
JSONObject data_key5 = JSONObject.parseObject(str);
String _data = data_key5.getString("data");
JSONObject data_key6 = JSONObject.parseObject(_data);
PAGES = data_key6.getInteger("pages");
CURRENT = data_key6.getInteger("current");
Log.e("list",PAGES+"");
try
String list = data_key6.getString("list");
Log.e("list",list);
List<MsgChat> msgChat1 = JSON.parseArray(list, MsgChat.class);
//数据翻转一下
if(StateY)
Collections.reverse(msgChat1);
for (MsgChat pp : msgChat1)
msgChat = new MsgChat();
int fromUserId = pp.getFromUserId();
String fromUserName = pp.getFromUserName();
int toUserId = pp.getToUserId();
String toUserName = pp.getToUserName();
String createTime = pp.getCreateTime();
String content = pp.getContent();
int unReadFlag = pp.getUnReadFlag();
msgChat.setFromUserId(fromUserId);
msgChat.setFromUserName(fromUserName);
msgChat.setToUserId(toUserId);
msgChat.setToUserName(toUserName);
msgChat.setContent(content);
msgChat.setCreateTime(createTime);
msgChat.setUnReadFlag(unReadFlag);
if(StateY)
msgChats.add(0,msgChat);
else
msgChats.add(msgChat);
catch(Exception e)
Log.e("list","数据为空");
Message msg = new Message();
msg.what = 1;
Thandler.sendMessage(msg);
@Override
public void error(String error)
Message msg = new Message();
msg.what = 1;
Thandler.sendMessage(msg);
);
private void startJWebSClientService()
Log.e("this", "开启服务");
intent = new Intent(ChatActivity.this, JWebSocketClientService1.class);
startService(intent);
private void restartJWebSClientService()
//销毁服务
Log.e("this", "销毁服务");
stopService(intent);
/**
* 绑定服务
*/
private void bindService()
Intent bindIntent = new Intent(ChatActivity.this, JWebSocketClientService1.class);
bindService(bindIntent, serviceConnection, 0000);
/**
* 注销广播
*/
private void unRegisterReceiver()
unregisterReceiver(chatMessageReceiver);
//设置状态栏为透明
protected void setStatusBar()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
@Override
public void onDestroy()
super.onDestroy();
unRegisterReceiver();
unbindService(serviceConnection);
restartJWebSClientService();
@Override
public void initData()
6.常量参数文件
这个值得一说的,跳转之前我把房间号和sendId存在这个文件,比对的时候就直接拿这个文件里面的sendId和fromUserId进行比对,用来区分是自己发送的,还是对方发送的
Constant.java
public class Constant
public final static String IP = "192.168.1.104";
public final static String webSocketHost = "ws://"+IP+":8110/websocket";
public final static String Host = "http://"+IP+":8110/";
public final static String get_picasso_createSession = Host + "picasso/createSession";
public final static String get_picasso_msgList= Host + "picasso/msgList?sessionId=";
public static int ChatId = 0;
public static int SendId = 0;
7.http请求的回调
写的很糙,不看也罢
public class ChatListener
private String TAG = "RequestListener";
private RequestCallback requestListener;
//获取历史消息
public void historyMessage(int chatId,String pageNum, RequestCallback requestListener)
this.requestListener = requestListener;
HistoryMessageRequest(Constant.get_picasso_msgList,chatId,pageNum);
//创建会话
public void createSession(int from_id,int to_id, RequestCallback requestListener)
this.requestListener = requestListener;
CreateSessionRequest(Constant.get_picasso_createSession,from_id,to_id);
public void CreateSessionRequest(String str,int from_id,int to_id)
OkHttpClient client = new OkHttpClient();
Request request1 = new Request.Builder()
.url(str+"?fromUserId="+from_id+"&toUserId="+to_id)
.build();
client.newCall(request1).enqueue(new Callback()
@Override
public void onFailure(Call call, IOException e)
Log.e("CreateSessionRequest==>",e.getMessage());
requestListener.success(e.getMessage());
@Override
public void onResponse(Call call, Response response) throws IOException
String result = response.body().string();
requestListener.success(result);
);
public void HistoryMessageRequest(String str,int chatId,String pageNum)
OkHttpClient client = new OkHttpClient();
Request request1 = new Request.Builder()
.url(str+chatId+"&page="+pageNum)
.build();
client.newCall(request1).enqueue(new Callback()
@Override
public void onFailure(Call call, IOException e)
Log.e("CreateSessionRequest==>",e.getMessage());
requestListener.success(e.getMessage());
@Override
public void onResponse(Call call, Response response) throws IOException
String result = response.body().string();
requestListener.success(result);
);
小程序如何集成即构IM实现即时通讯发消息聊天
以上是关于android:手搓一个即时消息聊天框(包含消息记录)的主要内容,如果未能解决你的问题,请参考以下文章