本篇仅介绍实现聊天室的前端,也就是安卓端代码,后端的实现看链接说明
链接说明
1.后端使用了spring boot 框架,若不熟悉,有关spring boot 入门教程请戳此链接使用Intellij IDEA开发第一个spring boot项目
2.websocket后端实现细节戳此链接spring boot练习–利用websocket实现QQ聊天室
界面展示
说明
有两个界面,第一个是登陆界面,我借用了我之前实现的登陆界面,并做了一些微调。需要输入ID和名字,测试的时候输入的ID不能重复,第二个是名字用于界面展示,登陆后跳转入第二个界面,就可以在聊天室里聊天了。
下面图模拟了一组场景,大青儿先进入聊天室,然后小明进入,互相发一段消息后,小明退出聊天室。
大青儿界面变化
小明界面变化
代码部分
这里仅展示安卓的代码,后端代码详见上面链接说明。
文件的简单介绍
Activity文件:LoginActivity.java,ChatActivity.java,与其对应布局文件activity_login.xml,activity_chat.xml
模型文件:Msg.java,User.java
适配器:UserAdapter
子布局文件:user_item.xml
第一步修改.gradle文件,添加以下内容
implementation 'com.squareup.okhttp3:okhttp:3.8.1'
implementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
implementation 'com.alibaba:fastjson:1.2.10'
implementation 'com.android.support:recyclerview-v7:28.0.0'
第二步,修改AndroidManifest.xml文件,添加以下内容
<activity android:name=".ChatActivity">
</activity>
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
以及添加这行,注意添加的位置的区别
<uses-permission android:name="android.permission.INTERNET" />
第三步具体功能代码
LoginActivity.java
public class LoginActivity extends Activity {
private EditText mEditTextName,mEditTextId;
private Button mButtonLogin;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
init();
mButtonLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String userName = mEditTextName.getText().toString();
String userId = mEditTextId.getText().toString();
User user = new User(userId,userName);
Intent intent = ChatActivity.newIntent(LoginActivity.this,user.toString());
startActivity(intent);
LoginActivity.this.finish();
}
});
}
private void init(){
mEditTextName = findViewById(R.id.nickname_editText);
mEditTextId = findViewById(R.id.id_editText);
mEditTextName.setCompoundDrawables(initDrawable(R.drawable.nickname),null,null,null);
mEditTextId.setCompoundDrawables(initDrawable(R.drawable.id),null,null,null);
mButtonLogin = findViewById(R.id.login);
}
/**
* 设置EditText左边图片的大小
* */
private Drawable initDrawable(int res){
Drawable drawable = getResources().getDrawable(res);
//距离左边距离,距离上边距离,长,宽
drawable.setBounds(0,0,100,100);
return drawable;
}
}
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background">
<!-- https://www.runoob.com/w3cnote/android-tutorial-relativelayout.html-->
<RelativeLayout
android:id="@+id/viewTop"
android:layout_width="match_parent"
android:layout_height="240dp"
android:background="@color/mihuang">
<ImageView
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="10dp"
android:src="@drawable/boy" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/viewCenter"
android:paddingTop="20dp"
android:layout_centerHorizontal="true"
android:layout_width="280dp"
android:layout_height="wrap_content"
android:layout_below="@+id/viewTop"
android:paddingBottom="70dp">
<EditText
android:id="@+id/id_editText"
android:layout_width="match_parent"
android:layout_height="60dp"
android:drawablePadding="10dp"
android:drawableLeft="@drawable/id"
android:hint="请输入Id"
android:textSize="16sp"
android:layout_marginLeft="10dp"/>
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:src="@drawable/down"
android:paddingRight="10dp"
android:paddingTop="20dp"/>
<EditText
android:layout_below="@+id/id_editText"
android:id="@+id/nickname_editText"
android:layout_width="match_parent"
android:layout_height="60dp"
android:drawablePadding="10dp"
android:drawableLeft="@drawable/nickname"
android:hint="请输入昵称"
android:layout_marginLeft="10dp"
android:textSize="16sp"/>
</RelativeLayout>
<Button
android:layout_below="@+id/viewCenter"
android:layout_centerHorizontal="true"
android:id="@+id/login"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text="登陆"
android:textColor="@color/text"
android:background="@color/login"
android:textSize="25dp" />
<RelativeLayout
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp">
<TextView
android:text="无法登陆?"
android:paddingLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="新用户注册"
android:paddingRight="10dp"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
</RelativeLayout>
ChatActivity.java
public class ChatActivity extends Activity {
private ArrayList<User> mUserArrayList = new ArrayList<>();//定义一个存储信息的列表
private EditText mInputText;//输入框
private Button mSend;//发送按钮
private RecyclerView mRecyclerView;//滑动框
private UserAdapter mAdapter;//适配器
private boolean backFlag = false;
private WebSocket mSocket;
private User mUser;;//全局User
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
initView();
String data = getIntent().getStringExtra("data");
if (!data.equals("")){
mUser = JSON.parseObject(data,User.class);
}else {
mUser = new User("0001","测试名字",R.drawable.boy);
}
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(layoutManager);
mAdapter = new UserAdapter(mUserArrayList);
mRecyclerView.setAdapter(mAdapter);
//开启连接
start(mUser.getUserId());
mSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String content = mInputText.getText().toString();
if (!"".equals(content)) {
Msg msg = new Msg(true,content,false);
User tempUser = new User(mUser.getUserId(),mUser.getUserName(),R.drawable.boy,msg);
mSocket.send(tempUser.toString());
mUserArrayList.add(tempUser);
updateRecyclerView();//刷新RecyclerView
//清空输入栏
mInputText.setText("");
}
}
});
}
/**
* 刷新view
* */
private void updateRecyclerView(){
//当有新消息时,刷新RecyclerView中的显示
mAdapter.notifyItemInserted(mUserArrayList.size() - 1);
//将RecyclerView定位到最后一行
mRecyclerView.scrollToPosition(mUserArrayList.size() - 1);
}
/**
* 开启web socket连接
* */
private void start(String userId) {
OkHttpClient mOkHttpClient = new OkHttpClient.Builder()
.readTimeout(300, TimeUnit.SECONDS)//设置读取超时时间
.writeTimeout(300, TimeUnit.SECONDS)//设置写的超时时间
.connectTimeout(300, TimeUnit.SECONDS)//设置连接超时时间
.build();
//定义request
Request request = new Request.Builder().url("ws://192.168.5.10:8080/test/"+userId).build();
//绑定回调接口
mOkHttpClient.newWebSocket(request, new EchoWebSocketListener());
mOkHttpClient.dispatcher().executorService().shutdown();
}
/**
* 显示内容
* */
private void output(final User user) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mUserArrayList.add(user);
updateRecyclerView();
}
});
}
/**
* 初始化界面
* */
private void initView(){
mInputText = findViewById(R.id.input_text);
mSend = findViewById(R.id.send);
mRecyclerView = findViewById(R.id.msg_recycler_view);
}
/**
* 静态方法返回一个能启动自己的intent
* */
public static Intent newIntent(Context context,String data){
Intent intent = new Intent(context,ChatActivity.class);
intent.putExtra("data",data);
return intent;
}
/**
* 对返回键的处理
* */
@Override
public boolean onKeyDown(int keyCode, KeyEvent event){
if(keyCode==KeyEvent.KEYCODE_BACK&&!backFlag){
Toast.makeText(ChatActivity.this,"再按一次退出程序",Toast.LENGTH_SHORT).show();
backFlag = true;
return true;
}else {
return super.onKeyDown(keyCode, event);
}
}
/**
* 内部类,监听web socket回调
* */
private final class EchoWebSocketListener extends WebSocketListener {
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
mSocket = webSocket; //实例化web socket
User user = new User();
user.setUserMsg(new Msg(false,"连接成功",true));
output(user);
}
@Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
User user = JSON.parseObject(text, User.class);
output(user);
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
super.onClosed(webSocket, code, reason);
User user = new User();
user.setUserMsg(new Msg(false,"关闭连接",true));
output(user);
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
User user = new User();
user.setUserMsg(new Msg(false,"连接失败:"+t.getMessage(),true));
output(user);
}
}
}
activity_chat.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#d8e0e8">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/msg_recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/input_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="2"
android:hint="请输入……"
/>
<Button
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送"/>
</LinearLayout>
</LinearLayout>
Msg.java
public class Msg {
private boolean send;//是否是发送的消息
private String content;//发送的内容
private boolean system;//是否是系统消息
public Msg(boolean send, String content) {
this.send = send;
this.content = content;
this.system = false;
}
public Msg() {
}
public Msg(boolean send, String content, boolean system) {
this.send = send;
this.content = content;
this.system = system;
}
public boolean isSystem() {
return system;
}
public void setSystem(boolean system) {
this.system = system;
}
public boolean isSend() {
return send;
}
public void setSend(boolean send) {
this.send = send;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
User.java
public class User {
private String userId;
private String userName;
private int userImg;
private Msg userMsg;
public int getUserImg() {
return userImg;
}
public void setUserImg(int userImg) {
this.userImg = userImg;
}
public User(String userId, String userName, int userImg) {
this.userId = userId;
this.userName = userName;
this.userImg = userImg;
}
public User() {
}
public User(String userId, String userName) {
this.userId = userId;
this.userName = userName;
}
public User(String userId, String userName, Msg userMsg) {
this.userId = userId;
this.userName = userName;
this.userMsg = userMsg;
}
public User(String userId, String userName, int userImg, Msg userMsg) {
this.userId = userId;
this.userName = userName;
this.userImg = userImg;
this.userMsg = userMsg;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Msg getUserMsg() {
return userMsg;
}
public void setUserMsg(Msg userMsg) {
this.userMsg = userMsg;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
UserAdapter.java
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder>{
private ArrayList<User> mUsers;
public UserAdapter(ArrayList<User> users) {
mUsers = users;
}
@NonNull
@Override
public UserAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.user_item,parent,false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull UserAdapter.ViewHolder holder, int position) {
User user = mUsers.get(position);
if (user.getUserMsg().isSystem()){//系统消息
holder.mLinearLayoutSystem.setVisibility(View.VISIBLE);
holder.mRelativeLayoutReceive.setVisibility(View.GONE);
holder.mRelativeLayoutSend.setVisibility(View.GONE);
holder.mTextViewSystemText.setText(user.getUserMsg().getContent());
}else{
if (user.getUserMsg().isSend()){//是发送消息
holder.mLinearLayoutSystem.setVisibility(View.GONE);
holder.mRelativeLayoutReceive.setVisibility(View.GONE);
holder.mRelativeLayoutSend.setVisibility(View.VISIBLE);
holder.mImageViewSend.setImageResource(user.getUserImg());
holder.mTextViewNameSend.setText(user.getUserName());
holder.mTextViewContentSend.setText(user.getUserMsg().getContent());
}else { //接收的消息
holder.mLinearLayoutSystem.setVisibility(View.GONE);
holder.mRelativeLayoutReceive.setVisibility(View.VISIBLE);
holder.mRelativeLayoutSend.setVisibility(View.GONE);
holder.mImageViewReceive.setImageResource(user.getUserImg());
holder.mTextViewNameReceive.setText(user.getUserName());
holder.mTextViewContentReceive.setText(user.getUserMsg().getContent());
}
}
}
@Override
public int getItemCount() {
return mUsers.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
//绑定所有视图
RelativeLayout mRelativeLayoutSend,mRelativeLayoutReceive;
LinearLayout mLinearLayoutSystem;
ImageView mImageViewReceive,mImageViewSend;
TextView mTextViewNameReceive,mTextViewNameSend,mTextViewContentReceive,mTextViewContentSend,mTextViewSystemText;
public ViewHolder(@NonNull View itemView) {
super(itemView);
mRelativeLayoutReceive = itemView.findViewById(R.id.receive_relative);
mRelativeLayoutSend = itemView.findViewById(R.id.send_relative);
mLinearLayoutSystem = itemView.findViewById(R.id.system_layout);
mImageViewReceive = itemView.findViewById(R.id.image_receive);
mImageViewSend = itemView.findViewById(R.id.image_send);
mTextViewNameReceive = itemView.findViewById(R.id.text_name_receive);
mTextViewNameSend = itemView.findViewById(R.id.text_name_send);
mTextViewContentReceive = itemView.findViewById(R.id.text_content_receive);
mTextViewContentSend = itemView.findViewById(R.id.text_content_send);
mTextViewSystemText = itemView.findViewById(R.id.system_text);
}
}
}
user_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_marginBottom="10dp">
<RelativeLayout
android:id="@+id/receive_relative"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_alignParentLeft="true"
android:id="@+id/image_receive"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="20dp"
android:layout_marginLeft="10dp"
android:src="@drawable/boy"/>
<LinearLayout
android:layout_toRightOf="@id/image_receive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/text_name_receive"
android:layout_width="50dp"
android:layout_height="20dp"
android:layout_gravity="left"
android:text="大青儿"
android:textSize="15dp"
android:textColor="#131313"
android:layout_marginLeft="20dp"/>
<LinearLayout
android:id="@+id/layout_content_receive"
android:layout_gravity="right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/message_left">
<TextView
android:id="@+id/text_content_receive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:textSize="20dp"
android:textColor="#F5EFEF"
android:text="我是一段文字一段文"
/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/send_relative"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_alignParentRight="true"
android:id="@+id/image_send"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="20dp"
android:layout_marginRight="10dp"
android:src="@drawable/boy"/>
<LinearLayout
android:layout_toLeftOf="@id/image_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/text_name_send"
android:layout_width="50dp"
android:layout_height="20dp"
android:layout_gravity="right"
android:text="大青儿"
android:textSize="15dp"
android:textColor="#131313"
android:layout_marginRight="20dp"/>
<LinearLayout
android:id="@+id/layout_content_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:background="@drawable/message_right">
<TextView
android:id="@+id/text_content_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:text="我是一段文字一段文"
android:textSize="20dp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:id="@+id/system_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/system_text"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:text="系统消息:"
android:layout_gravity="center"
android:textColor="#F17070"/>
</LinearLayout>
</RelativeLayout>
图片资源
应用中大部分图片资源都是只起装饰性作用,可以在阿里巴巴矢量图标库获取,当然包裹聊天文字的图片是.9格式图片,可以参考:Android Studio制作.9图片
提醒一哈
先配置好后端再进行前端测试,不然没连上服务器会报错并闪退。因为当你点发送的时候,有一段代码中websocket并没有被实例化。