android:手搓一个即时消息聊天框(包含消息记录)

news/2024/11/20 7:03:16/

先看一下效果

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"><TextViewandroid: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"/><LinearLayoutandroid:id="@+id/ll_left"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="left"><TextViewandroid:visibility="gone"android:id="@+id/to_user_id"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid: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"/><LinearLayoutandroid: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"><TextViewandroid: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"/><TextViewandroid: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><LinearLayoutandroid:id="@+id/ll_right"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="right"><LinearLayoutandroid: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"><TextViewandroid: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"/><TextViewandroid: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><TextViewandroid: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"/><TextViewandroid: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.CustomRefreshListViewandroid:id="@+id/msg_list"android:layout_weight="1"android:divider="#00000000"android:scrollbars="none"android:layout_width="match_parent"android:layout_height="wrap_content" /><LinearLayoutandroid: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"><EditTextandroid: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" /><RelativeLayoutandroid:layout_width="75dp"android:layout_height="40dp"android:layout_marginLeft="15dp"android:layout_marginRight="15dp"android:background="@drawable/bg_btn_orange_back"><TextViewandroid: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);}@Overridepublic 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){//仍处于下拉刷新状态,未滑动一定距离,不加载数据,隐藏headViewheaderView.setPadding(0, -headerViewHeight, 0, 0);}else if (currentState==RELEASE_REFRESH) {//滑倒一定距离,显示无padding值得headcViewheaderView.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:快速滑动后松开*/@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {if(scrollState==OnScrollListener.SCROLL_STATE_IDLE && getLastVisiblePosition()==(getCount()-1) &&!isLoadingMore){isLoadingMore = true;footerView.setPadding(0, 0, 0, 0);//显示出footerViewsetSelection(getCount());//让listview最后一条显示出来,在页面完全显示出底布局if(listener!=null){listener.onLoadingMore();}}}@Overridepublic 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"><ProgressBarandroid:id="@+id/pb_rotate"android:layout_width="match_parent"android:layout_height="20dp"/><TextViewandroid:id="@+id/tv_state"android:layout_width="match_parent"android:layout_height="20dp"/><TextViewandroid:id="@+id/tv_time"android:layout_width="match_parent"android:layout_height="20dp"/><ImageViewandroid:id="@+id/iv_arrow"android:layout_width="match_parent"android:layout_height="20dp"/></LinearLayout>

3.websocket代码

记住要在清单文件里面添加这一行

        <serviceandroid: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@Overridepublic IBinder onBind(Intent intent) {return mBinder;}@Overridepublic void onCreate() {super.onCreate();//初始化websocketinitSocketClient();mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//开启心跳检测}@Overridepublic 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) {@Overridepublic 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);}@Overridepublic void onOpen(ServerHandshake handshakedata) {super.onOpen(handshakedata);Log.e("JWebSocketClientService", "websocket连接成功");}};connect();}/*** 连接websocket*/private void connect() {new Thread() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void run() {try {Log.e("JWebSocketClientService", "开启重连");client.reconnectBlocking();} catch (InterruptedException e) {e.printStackTrace();}}}.start();}@Overridepublic void onDestroy() {Log.d(TAG, "onDestroy: ....");super.onDestroy();}
JWebSocketClient
public class JWebSocketClient extends WebSocketClient {public JWebSocketClient(URI serverUri) {super(serverUri, new Draft_6455());}@Overridepublic void onOpen(ServerHandshake handshakedata) {Log.e("JWebSocketClient", "onOpen()");}@Overridepublic void onMessage(String message) {Log.e("JWebSocketClient", "onMessage()");}@Overridepublic void onClose(int code, String reason, boolean remote) {Log.e("JWebSocketClient", "onClose()");}@Overridepublic 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);}@Overridepublic int getCount() {return msgChatArrayList.size();}@Overridepublic Object getItem(int i) {return msgChatArrayList.get(i);}@Overridepublic long getItemId(int i) {return i;}public void addItem(MsgChat msgChat, ArrayList<MsgChat> msgChats) {msgChats.add(msgChat);this.msgChatArrayList = msgChats;this.notifyDataSetChanged();}@SuppressLint("WrongConstant")@Overridepublic 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(); // 重新获取ViewHolderll_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 {@Overridepublic 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() {@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {Log.e("this", "服务与活动成功绑定");binder = (JWebSocketClientService1.JWebSocketClientBinder) iBinder;jWebSClientService = binder.getService();client = jWebSClientService.client;}@Overridepublic void onServiceDisconnected(ComponentName componentName) {Log.e("this", "服务与活动成功断开");}};@Overridepublic int getLayout() {return R.layout.activity_chat;}@Overridepublic void initView() {setStatusBar();//启动服务startJWebSClientService();//绑定服务bindService();//注册广播doRegisterReceiver();//初始化历史消息initHistoryMsg(Constant.ChatId,"");msg_list = findViewById(R.id.msg_list);msg_list.setOnRefreshListener(new CustomRefreshListView.OnRefreshListener() {@Overridepublic void onPullRefresh() {if(CURRENT!=PAGES){CURRENT++;Log.e("list","xia"+CURRENT);StateY = true;initHistoryMsg(Constant.ChatId,String.valueOf(CURRENT));}msg_list.completeRefresh();}@Overridepublic 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() {@Overridepublic void onClick(View view) {jWebSClientService.sendMsg(id_input.getText().toString());id_input.setText("");}});}private Handler Thandler = new Handler() {@Overridepublic 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() {@Overridepublic 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);}@Overridepublic 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);}}@Overridepublic void onDestroy() {super.onDestroy();unRegisterReceiver();unbindService(serviceConnection);restartJWebSClientService();}@Overridepublic 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() {@Overridepublic void onFailure(Call call, IOException e) {Log.e("CreateSessionRequest==>",e.getMessage());requestListener.success(e.getMessage());}@Overridepublic 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() {@Overridepublic void onFailure(Call call, IOException e) {Log.e("CreateSessionRequest==>",e.getMessage());requestListener.success(e.getMessage());}@Overridepublic void onResponse(Call call, Response response) throws IOException {String result = response.body().string();requestListener.success(result);}});}}


http://www.ppmy.cn/news/37713.html

相关文章

Python 自动化指南(繁琐工作自动化)第二版:十一、调试

原文&#xff1a;https://automatetheboringstuff.com/2e/chapter11/ 既然你已经知道了足够多的知识来编写更复杂的程序&#xff0c;你可能会开始发现其中不那么简单的错误。这一章介绍了一些工具和技术&#xff0c;用于查找程序中错误的根本原因&#xff0c;帮助您更快、更省力…

2021年05月软件设计师全套真题精讲

请点击↑关注、收藏&#xff0c;本博客免费为你获取精彩知识分享&#xff01;有惊喜哟&#xff01;&#xff01; 1. 在 CPU 中&#xff0c;用&#xff08; &#xff09;给出将要执行的下一条指令在内存中的地址。 A.程序计数器 B.指令寄存器 C.主存地址寄存器 D.状态条件寄存…

vue按需引入

在main.js中 import Vue.use&#xff08;&#xff09;中 写入组件名称 Pagination, Dialog, Autocomplete, Dropdown, DropdownMenu, DropdownItem, Menu, Submenu, MenuItem, MenuItemGroup, Input, InputNumber, Radio, RadioGroup, RadioButton, Checkbox, Checkb…

技术宅小伙:女程序员的日常穿搭分享

我想跟大家分享一下我上班时的日常穿搭&#xff0c;更多细节可以在这篇文章里找到。 首先&#xff0c;我要解释一下我上班之前的状态。我一般都会选择穿得舒适一点&#xff0c;因为整天坐在电脑前很累。我上班的时候&#xff0c;我的整体穿搭包括帽子和一个我已经用了两年的帆…

Zookeeper和Redis分布式锁对比

1、什么是分布式锁 锁&#xff0c;解决的是多线程或多进程情况下的数据一致性问题&#xff1b;分布式锁&#xff0c;解决的是分布式集群下的数据一致性问题。 为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行&#xff0c;在传统单体应用单机部署的情况下&…

使用docker配置服务器环境

go的环境配置 安装 下载golang的安装包 wget https://studygolang.com/dl/golang/go1.20.2.linux-amd64.tar.gz解压go的.tar.gz压缩包 tar -zxvf go1.20.2.linux-amd64.tar.gz解压之后go文件夹的位置一般放置到/usr/local/go 环境配置 将GOROOT和PATH配置到 /etc/profile…

从本质上来看,所谓的即时零售,其实依然是电商进化的产物

在电商的发展业已进入到深水区的大背景下&#xff0c;我们看到的是&#xff0c;越来越多的电商玩家开始将关注的焦点聚焦在了全新的领域里。如果对于这些全新的领域进行深度分析的话&#xff0c;我们可以看出&#xff0c;不断地梳理和优化传统电商的内在运行逻辑&#xff0c;不…

【数据结构与算法】图(Graph)【详解】

文章目录图图的基本概念一、图的定义二、图的基本概念和术语1、有向图2、无向图3、简单图4、多重图5、完全图&#xff08;也称简单完全图&#xff09;6、子图7、连通、连通图和连通分量8、强连通图、强连通分量9、生成树、生成森林10、顶点的度、入度和出度11、边的权和网12、稠…