现在只能实现群聊,不能实现私聊
图片如下:
登录界面:
聊天界面:
在AndroidStudio中可以开启两个模拟器,也可以手机和模拟器,ip地址是电脑的ipv4地址,可以在cmd通过ipconfig查看自己的ipv4地址
登录界面的布局:
<?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="@drawable/picture"
>
<LinearLayout
android:orientation="vertical"
android:layout_marginTop="280dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="用户名 "
android:textSize="30sp"
android:textColor="#0F0F0F"/>
<EditText
android:id="@+id/input_name"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="ip地址 "
android:textSize="30sp"
android:textColor="#0F0F0F"/>
<EditText
android:id="@+id/ip_address"
android:gravity="center"
android:focusable="false"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="端口号 "
android:textSize="30sp"
android:textColor="#0F0F0F"/>
<EditText
android:id="@+id/port_name"
android:gravity="center"
android:focusable="false"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="登录"
android:layout_marginTop="30dp"
android:textSize="30sp"
android:background="#F0FFFF"
android:textColor="#0F0F0F"/>
</LinearLayout>
</LinearLayout>
登录界面的代码:
package com.example.clientactivity;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
private EditText input_name;
private EditText ip_address;
private EditText port_name;
Button login;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
input_name = findViewById(R.id.input_name);
ip_address = findViewById(R.id.ip_address);
ip_address.setText("192.168.43.92");
port_name = findViewById(R.id.port_name);
port_name.setText("7567");
login = findViewById(R.id.login);
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String name = input_name.getText().toString();
String ip = ip_address.getText().toString();
String port = port_name.getText().toString();
Intent intent = new Intent(MainActivity.this,Chat.class);//开启第二个界面
intent.putExtra("name",name);//信息的传递
intent.putExtra("ip",ip);
intent.putExtra("port",port);
startActivity(intent);
}
});
}
}
聊天界面的布局:
聊天界面使用到了RecyclerView,所以在build.gradle添加
implementation 'com.android.support:recyclerview-v7:29.0.1'
<?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="@drawable/shappp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/back"
android:layout_width="53dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="@drawable/evvv_shape"
android:text="退出" />
<TextView
android:layout_width="0dp"
android:layout_marginTop="20dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center"
android:gravity="center"
android:text="聊天室"
android:textColor="#121212"
android:textSize="20sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/msg_recycler_view"
android:layout_marginTop="15dp"
android:layout_width="match_parent"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_height="0dp"
android:layout_weight="1"/>
<LinearLayout
android:layout_margin="15dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<EditText
android:id="@+id/input_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/ev_shape"//自定义的布局
android:hint="Type something here"
android:maxLines="2"/>
<Button
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/evv_shape"
android:text="发送"
android:layout_marginLeft="5dp"
android:textColor="#FFFFFF"
android:textSize="20sp"/>
</LinearLayout>
</LinearLayout>
子项的布局:
<?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="wrap_content"
android:padding="5dp">
<TextView
android:id="@+id/get_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<LinearLayout
android:id="@+id/left_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:background="@drawable/ppp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:background="@drawable/left">
<TextView
android:id="@+id/left_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:textColor="#000000" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/right_layout"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right">
<LinearLayout
android:layout_width="141dp"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:background="@drawable/right">
<TextView
android:id="@+id/right_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#fff" />
</LinearLayout>
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@drawable/zhan"
android:layout_gravity="center"/>
</LinearLayout>
</LinearLayout>
所用的气泡和图片可自己在网上找
下面消息接收和发送的显示用的是Android第一行代码中的RecyclerView那一章
package com.example.clientactivity;
public class Msg {
public static final int TYPE_RECEIVE = 0;
public static final int TYPE_SENT = 1;
private String content;
private int type;
public Msg(String content,int type) {
this.content = content;
this.type = type;
}
public String getContent() {
return content;
}
public int getType() {
return type;
}
}
适配器:
package com.example.clientactivity;
import android.annotation.SuppressLint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder> {
private List<Msg> mMsgList;
static class ViewHolder extends RecyclerView.ViewHolder {
LinearLayout leftLayout;
LinearLayout rightLayout;
TextView leftMsg;
TextView rightMsg;
TextView getTime;
public ViewHolder(View view) {
super(view);
getTime = view.findViewById(R.id.get_time);
leftLayout = view.findViewById(R.id.left_layout);
rightLayout = view.findViewById(R.id.right_layout);
leftMsg = view.findViewById(R.id.left_msg);
rightMsg = view.findViewById(R.id.right_msg);
}
}
public MsgAdapter(List<Msg> msgList) {
mMsgList = msgList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.msg_item,parent,false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Msg msg = mMsgList.get(position);
holder.getTime.setText(getCurrentTime());//显示时间
if(msg.getType() == Msg.TYPE_RECEIVE) {
holder.leftLayout.setVisibility(View.VISIBLE);
holder.rightLayout.setVisibility(View.GONE);
holder.leftMsg.setText(msg.getContent());
}else if(msg.getType() == Msg.TYPE_SENT) {
holder.rightLayout.setVisibility(View.VISIBLE);
holder.leftLayout.setVisibility(View.GONE);
holder.rightMsg.setText(msg.getContent());
}
}
@Override
public int getItemCount() {
return mMsgList.size();
}
private static String getCurrentTime() {
@SuppressLint("SimpleDateFormat")
String date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
return date;
}
}
客户端:
package com.example.clientactivity;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.StrictMode;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeoutException;
public class Chat extends AppCompatActivity {
private List<Msg> msgList = new ArrayList<>();
private EditText inputText;
private String name;
private String ip;
private String port;
private Button send;
private Button back;
private RecyclerView msgRecyclerView;
private MsgAdapter adapter;
private Socket socket ;
private InputStream receive;
private OutputStream sendOut;
private String content;
private StringBuilder builder = new StringBuilder();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
inputText = findViewById(R.id.input_text);
send = findViewById(R.id.send);
back = findViewById(R.id.back);
msgRecyclerView = findViewById(R.id.msg_recycler_view);
Intent intent = getIntent();//获取登录界面传过来的信息
name = intent.getStringExtra("name");
ip = intent.getStringExtra("ip");
port = intent.getStringExtra("port");
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects().penaltyLog().penaltyDeath().build());
try{
socket = new Socket(ip,Integer.parseInt(port));
sendOut = socket.getOutputStream();
receive = socket.getInputStream();
}catch (Exception e) {
e.printStackTrace();
}
if(socket == null) {
Toast.makeText(this,"连接失败",Toast.LENGTH_SHORT).show();
finish();
}else{
Toast.makeText(this,"登录成功",Toast.LENGTH_SHORT).show();
}
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
msgRecyclerView.setLayoutManager(layoutManager);
adapter = new MsgAdapter(msgList);
msgRecyclerView.setAdapter(adapter);
//客户端接收消息
new Thread(new Runnable() {
public void run() {
try{
byte[] arr = new byte[1024 * 6];
int len;
String receiveMsg;
while(true) {
while((len = receive.read(arr)) != -1) {
receiveMsg = new String(arr,0,len);
final Msg msg = new Msg(receiveMsg,Msg.TYPE_RECEIVE);
runOnUiThread(new Runnable() {
@Override
public void run() {
msgList.add(msg);
adapter.notifyItemInserted(msgList.size() - 1);//有新消息时,刷新RecyclerView中的显示
msgRecyclerView.scrollToPosition(msgList.size() - 1);//将RecyclerView定位到最后一行
}
});
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
}).start();
//退出按钮
back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AlertDialog.Builder dialog = new AlertDialog.Builder(Chat.this);
dialog.setTitle("退出");
dialog.setMessage("退出登录?");
dialog.setCancelable(false);
dialog.setPositiveButton("是", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
finish();
}
});
dialog.setNegativeButton("否", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
dialog.show();
}
});
//客户端发送消息
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try{
content = inputText.getText().toString();
if(!"".equals(content)) {
builder.append(content).append("\n\n").append("from:").append(name);
content = builder.toString();
try{
sendOut.write(content.getBytes("utf-8"));
builder.delete(0,builder.length());
}catch (Exception e) {
e.printStackTrace();
}
Msg msg = new Msg(content,Msg.TYPE_SENT);
msgList.add(msg);
adapter.notifyItemInserted(msgList.size() - 1);
msgRecyclerView.scrollToPosition(msgList.size() - 1);
inputText.setText("");
}
}catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
需注册网络权限:
<uses-permission android:name="android.permission.INTERNET" />
注意:
- 模拟器可以运行,手机端不能正常运行,可以尝试着手机端连接电脑的热点。
- 模拟器必须在有网条件下才能正常运行
- 在接收消息的线程中,必须进行UI的更新。