演示地址: https://www.bilibili.com/video/BV1Wz4y127HX/
代码
权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
java部分
MainActivity.java
import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.Manifest; import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.provider.MediaStore; import android.util.Base64; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.sql.Date; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import java.util.Locale; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Handler handler; private LayoutInflater inflater; private View layout; private AlertDialog.Builder builder; private TextView titleview; private ListView showmsg; private EditText sendmsgtext; private Button startserver; private Button continueserver; private Button sendmsgbt; private Button sendimg; private int StartPort; private boolean isContinue = true,isServer = false; private String message = "",userSendMsg = "",titletext = ""; private String[] ContinueServerData = new String[2];// 0.ipv4 1.端口号 private Long mID = 0L; private List<MessageInfor> datas = new ArrayList<MessageInfor>(); private SimpleDateFormat simpleDateFormat; private MessageAdapte messageAdapte; private static Socket socket = null;//用于与服务端通信的Socket private static ServerSocket server; private static List<PrintWriter> allOut; //存放所有客户端的输出流的集合,用于广播 private static final int IMAGE = 1;//调用系统相册-选择图片 private static String[] PERMISSIONS_STORAGE = { //依次权限申请 Manifest.permission.INTERNET, Manifest.permission.READ_EXTERNAL_STORAGE }; /** * 作者 LinOwl * 2021.02.17 */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getSupportActionBar().hide();//隐藏标题栏 applypermission(); InitView(); handler = new Handler(){ @Override public void handleMessage(@NonNull Message msg) { if(msg.what == 1){ titleview.setText(titletext); }else if(msg.what == 2){ titleview.setText("当前在线人数["+(allOut.size()+1)+"]"); } super.handleMessage(msg); } }; } /** * 初始化控件 */ private void InitView() { titleview = (TextView) findViewById(R.id.titleview); showmsg = (ListView) findViewById(R.id.showmsg); sendmsgtext = (EditText) findViewById(R.id.sendmsgtext); startserver = (Button) findViewById(R.id.startserver); continueserver = (Button) findViewById(R.id.continueserver); sendmsgbt = (Button) findViewById(R.id.sendmsgbt); sendimg = (Button) findViewById(R.id.sendimg); simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); messageAdapte = new MessageAdapte(); showmsg.setAdapter(messageAdapte); startserver.setOnClickListener(this); continueserver.setOnClickListener(this); sendmsgbt.setOnClickListener(this); sendimg.setOnClickListener(this); } //定义判断权限申请的函数,在onCreat中调用就行 public void applypermission(){ if(Build.VERSION.SDK_INT>=23){ boolean needapply=false; for(int i=0;i<PERMISSIONS_STORAGE.length;i++){ int chechpermission= ContextCompat.checkSelfPermission(getApplicationContext(), PERMISSIONS_STORAGE[i]); if(chechpermission!= PackageManager.PERMISSION_GRANTED){ needapply=true; } } if(needapply){ ActivityCompat.requestPermissions(MainActivity.this,PERMISSIONS_STORAGE,1); } } } @Override public void onClick(View view) { switch (view.getId()){ case R.id.startserver: //加载布局 inflater = LayoutInflater.from(this); layout = inflater.inflate(R.layout.start_server,null); //通过对 AlertDialog.Builder 对象调用 setView() builder = new AlertDialog.Builder(MainActivity.this); builder.setView(R.layout.start_server); builder.setCancelable(false);//是否为可取消 //加载控件 EditText editprot = (EditText) layout.findViewById(R.id.editprot); new AlertDialog.Builder(MainActivity.this) .setView(layout) //设置显示内容 .setPositiveButton("开启", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { StartPort = Integer.valueOf(editprot.getText().toString()); mID = System.currentTimeMillis(); ServerInit(); } }) .setNegativeButton("取消", null) .setCancelable(false) //按回退键不可取消该对话框 .show(); break; case R.id.continueserver: //加载布局 inflater = LayoutInflater.from(this); layout = inflater.inflate(R.layout.continue_server,null); //通过对 AlertDialog.Builder 对象调用 setView() builder = new AlertDialog.Builder(MainActivity.this); builder.setView(R.layout.continue_server); builder.setCancelable(false);//是否为可取消 //加载控件 EditText editipv4text = (EditText) layout.findViewById(R.id.editipv4text); EditText editprottext = (EditText) layout.findViewById(R.id.editprottext); new AlertDialog.Builder(MainActivity.this) .setView(layout) //设置显示内容 .setPositiveButton("连接", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ContinueServerData[0] = editipv4text.getText().toString(); ContinueServerData[1] = editprottext.getText().toString(); ContinueSever(); } }) .setNegativeButton("取消", null) .setCancelable(false) //按回退键不可取消该对话框 .show(); break; case R.id.sendmsgbt://发送消息 if(isServer){//服务器 message = sendmsgtext.getText().toString(); if(message==null||"".equals(message)){ Toast.makeText(MainActivity.this,"发送消息不能为空",Toast.LENGTH_LONG).show(); return ; } long Ltimes = System.currentTimeMillis(); message = sendmsgtext.getText().toString(); datas.add(new MessageInfor(message,Ltimes,mID,"1")); sendMessage("{\"isimg\":\"1\",\"msg\":\""+message+"\",\"times\":\""+Ltimes+"\",\"id\":\""+mID+"\",\"peoplen\":\""+"当前在线人数["+(allOut.size()+1)+"]"+"\"}"); sendmsgtext.setText(""); }else {//客户端 sendMsgText(); } break; case R.id.sendimg: //调用相册 Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(intent, IMAGE); break; } } /** * 服务器端 * @param out */ //将给定的输出流放入集合 private synchronized void addOut(PrintWriter out){ allOut.add(out); } //将给定的输出流移出集合 private synchronized void removeOut(PrintWriter out){ allOut.remove(out); } //将给定的消息发给客户端 private void sendMessage(String message) { Thread sendmsg = new Thread(new Runnable() { @Override public void run() { for(PrintWriter out:allOut) { out.println(message); } } }); sendmsg.start(); } //服务器初始化 public void ServerInit() { try { server = new ServerSocket(StartPort); allOut = new ArrayList<PrintWriter>(); isServer = true; } catch (Exception e) { e.printStackTrace(); } new Thread( new Runnable() { @Override public void run() { while(true) { Socket socket1 = null; try { socket1 = server.accept(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } ClientHandler hander = new ClientHandler(socket1); Thread t = new Thread(hander); t.start(); } } }).start(); } //该线程类是与指定的客户端进行交互工作 class ClientHandler implements Runnable{ //当前线程客户端的Socket private Socket socket; //该客户端的地址 private String host; public ClientHandler(Socket socket) { this.socket=socket; InetAddress address = socket.getInetAddress(); //获取ip地址 host = address.getHostAddress(); } @Override public void run() { PrintWriter pw = null; try { //有用户加入 sendMessage("["+host+"]加入聊天!"); OutputStream out = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8"); pw = new PrintWriter(osw,true); //将该客户的输出流存入共享集合,以便消息可以广播给该客户端 addOut(pw); handler.sendEmptyMessage(2); //处理来自客户端的数据 InputStream in = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(in,"utf-8"); BufferedReader br = new BufferedReader(isr); String message = null; while((message = br.readLine())!=null) { try { JSONObject json = new JSONObject(message); if(json.getString("isimg").equals("1")){//不为图片 datas.add(new MessageInfor(json.getString("msg"),Long.valueOf(json.getString("times")),Long.valueOf(json.getString("id")),"1")); }else if(json.getString("isimg").equals("0")){//为图片 datas.add(new MessageInfor(json.getString("msg"),Long.valueOf(json.getString("times")),Long.valueOf(json.getString("id")),"0")); } titletext = json.getString("peoplen"); handler.sendEmptyMessage(1); //messageAdapte.notifyDataSetChanged();//通知数据源发生变化 }catch (JSONException e){ e.printStackTrace(); } sendMessage(message); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { //将该客户端的输出流从共享集合中删除 removeOut(pw); //有用户退出 sendMessage("["+host+"]退出聊天!"); handler.sendEmptyMessage(2); try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } /** * 客户端 * @return */ public boolean ContinueSever(){ Thread continuethread = new Thread( new Runnable() { @Override public void run() { try { //localhost 127.0.0.1 socket = new Socket(ContinueServerData[0],Integer.valueOf(ContinueServerData[1])); mID = System.currentTimeMillis(); } catch (Exception e) { isContinue = false; isServer = false; e.printStackTrace(); } } } ); continuethread.start(); while(isContinue){ if(socket != null){ break; } } if(isContinue) { new Thread( new Runnable() { @Override public void run() { /* * 客户端开始工作的方法 */ try { //启动用于读取服务端发送消息的线程 ServerHandler handler = new ServerHandler(); //ServerHandler是自己写的类,实现Runnable接口,有多线程功能 Thread t = new Thread(handler); t.start(); //将数据发送到服务端 OutputStream out = socket.getOutputStream();//获取输出流对象 OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8");//转化成utf-8格式 PrintWriter pw = new PrintWriter(osw,true); while(true) { if(userSendMsg != "" && userSendMsg!=null){ pw.println(userSendMsg);//把信息输出到服务端 userSendMsg = ""; } } } catch (Exception e) { e.printStackTrace(); } } }).start(); } return isContinue; } class ServerHandler implements Runnable{ /** * 读取服务端发送过来的消息 */ @Override public void run() { try { InputStream in = socket.getInputStream();//输入流 InputStreamReader isr = new InputStreamReader(in,"UTF-8");//以utf-8读 BufferedReader br = new BufferedReader(isr); String message1=br.readLine(); while(message1!=null) { Log.i("测试4",message1); try { JSONObject json = new JSONObject(message1); if(json.getLong("id") != mID){ if(json.getString("isimg").equals("1")){//不为图片 datas.add(new MessageInfor(json.getString("msg"),Long.valueOf(json.getString("times")),Long.valueOf(json.getString("id")),"1")); }else if(json.getString("isimg").equals("0")){//为图片 datas.add(new MessageInfor(json.getString("msg"),Long.valueOf(json.getString("times")),Long.valueOf(json.getString("id")),"0")); } } titletext = json.getString("peoplen"); handler.sendEmptyMessage(1); //messageAdapte.notifyDataSetChanged();//通知数据源发生变化 }catch (JSONException e){ e.printStackTrace(); } message1=br.readLine(); } } catch (Exception e) { e.printStackTrace(); } } } /** * 发送消息 */ private void sendMsgText(){ message = sendmsgtext.getText().toString(); if(message==null||"".equals(message)){ Toast.makeText(MainActivity.this,"发送消息不能为空",Toast.LENGTH_LONG).show(); return ; } long Ltimes = System.currentTimeMillis(); MessageInfor m = new MessageInfor(message,Ltimes,mID,"1");//消息 时间戳 id userSendMsg = "{\"isimg\":\"1\",\"msg\":\""+sendmsgtext.getText().toString()+"\",\"times\":\""+Ltimes+"\",\"id\":\""+mID+"\"}"; datas.add(m); messageAdapte.notifyDataSetChanged();//通知数据源发生变化 sendmsgtext.setText(""); } class MessageAdapte extends BaseAdapter { @Override public int getCount() { return datas.size(); } @Override public MessageInfor getItem(int i) { return datas.get(i); } @Override public long getItemId(int i) { Long id = datas.get(i).getUserID(); return id==null?0:id; } @Override public View getView(int i, View view, ViewGroup viewGroup) { MessageHolder holder = null; if(view == null){ view = LayoutInflater.from(MainActivity.this).inflate(R.layout.chart_item,null); holder = new MessageHolder(); holder.left = (TextView) view.findViewById(R.id.itemleft); holder.right = (TextView) view.findViewById(R.id.itemright); holder.lefttime = (TextView) view.findViewById(R.id.itemtimeleft); holder.righttime = (TextView) view.findViewById(R.id.itemtimeright); holder.rightimgtime = (TextView) view.findViewById(R.id.rightimgtime); holder.leftimgtime = (TextView) view.findViewById(R.id.leftimgtime); holder.rightimg = (ImageView) view.findViewById(R.id.rightimg); holder.leftimg = (ImageView) view.findViewById(R.id.leftimg); view.setTag(holder); }else { holder = (MessageHolder) view.getTag(); } MessageInfor mi = getItem(i); //显示 if (mi.getUserID() == mID){//id相等 if(mi.getType().equals("0")){//图片 holder.leftimg.setVisibility(View.GONE); holder.leftimgtime.setVisibility(View.GONE); holder.rightimg.setVisibility(View.VISIBLE); holder.rightimgtime.setVisibility(View.VISIBLE); holder.rightimg.setImageBitmap(convertStringToIcon(mi.getMsg())); holder.rightimgtime.setText(simpleDateFormat.format(new Date(mi.getTime()))); holder.left.setVisibility(View.GONE); holder.lefttime.setVisibility(View.GONE); holder.right.setVisibility(View.GONE); holder.righttime.setVisibility(View.GONE); }else if(mi.getType().equals("1")){//消息 holder.leftimg.setVisibility(View.GONE); holder.leftimgtime.setVisibility(View.GONE); holder.rightimg.setVisibility(View.GONE); holder.rightimgtime.setVisibility(View.GONE); holder.left.setVisibility(View.GONE); holder.lefttime.setVisibility(View.GONE); holder.right.setVisibility(View.VISIBLE); holder.righttime.setVisibility(View.VISIBLE); holder.right.setText(mi.getMsg()); holder.righttime.setText(simpleDateFormat.format(new Date(mi.getTime()))); } }else { if(mi.getType().equals("0")){//图片 holder.leftimg.setVisibility(View.VISIBLE); holder.leftimgtime.setVisibility(View.VISIBLE); holder.rightimg.setVisibility(View.GONE); holder.rightimgtime.setVisibility(View.GONE); holder.leftimg.setImageBitmap(convertStringToIcon(mi.getMsg())); holder.leftimgtime.setText(simpleDateFormat.format(new Date(mi.getTime()))); holder.left.setVisibility(View.GONE); holder.lefttime.setVisibility(View.GONE); holder.right.setVisibility(View.GONE); holder.righttime.setVisibility(View.GONE); }else if(mi.getType().equals("1")){//消息 holder.leftimg.setVisibility(View.GONE); holder.leftimgtime.setVisibility(View.GONE); holder.rightimg.setVisibility(View.GONE); holder.rightimgtime.setVisibility(View.GONE); holder.left.setVisibility(View.VISIBLE); holder.lefttime.setVisibility(View.VISIBLE); holder.right.setVisibility(View.GONE); holder.righttime.setVisibility(View.GONE); holder.left.setText(mi.getMsg()); holder.lefttime.setText(simpleDateFormat.format(new Date(mi.getTime()))); } } return view; } } class MessageHolder{ public TextView left; public TextView right; public TextView lefttime; public TextView righttime; private TextView rightimgtime; private TextView leftimgtime; private ImageView rightimg; private ImageView leftimg; } public void onActivityResult(int requestCode, int resultCode, final Intent data) { //获取图片路径 if (requestCode == IMAGE && resultCode == Activity.RESULT_OK && data != null) { Uri selectedImage = data.getData(); String[] filePathColumns = {MediaStore.Images.Media.DATA}; Cursor c = getContentResolver().query(selectedImage, filePathColumns, null, null, null); c.moveToFirst(); int columnIndex = c.getColumnIndex(filePathColumns[0]); String imagePath = c.getString(columnIndex); activityImage(imagePath); c.close(); } super.onActivityResult(requestCode, resultCode, data); } /** * 处理图片发送 * @param imaePath 图片路径 */ private void activityImage(String imaePath){ Bitmap bm = BitmapFactory.decodeFile(imaePath); bm = resizeBitmap(bm,400,400,true); long Ltimes = System.currentTimeMillis(); String imgString = convertIconToString(bm); imgString = imgString.replace("\n",""); datas.add(new MessageInfor(imgString,Ltimes,mID,"0")); if(isServer){//服务器 sendMessage("{\"isimg\":\"0\",\"msg\":\""+imgString+"\",\"times\":\""+Ltimes+"\",\"id\":\""+mID+"\"}"); }else {//客户端 userSendMsg = "{\"isimg\":\"0\",\"msg\":\""+imgString+"\",\"times\":\""+Ltimes+"\",\"id\":\""+mID+"\"}"; } } /** * 图片转成string * @param bitmap * @return */ private String convertIconToString(Bitmap bitmap){ ByteArrayOutputStream baos = new ByteArrayOutputStream();// outputstream bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); byte[] appicon = baos.toByteArray();// 转为byte数组 return Base64.encodeToString(appicon, Base64.DEFAULT); } /** * string转成bitmap * @param st * @return */ private Bitmap convertStringToIcon(String st){ Bitmap bitmap = null; try { byte[] bitmapArray; bitmapArray = Base64.decode(st, Base64.DEFAULT); bitmap = BitmapFactory.decodeByteArray(bitmapArray, 0,bitmapArray.length); return bitmap; } catch (Exception e) { return null; } } /** * 处理图片 * @param bitmap 图片bitmap * @param MaxWidth 最大长 * @param MaxHeight 最大宽 * @param filter 是否过滤 * @return 处理后的bitmap */ private Bitmap resizeBitmap(Bitmap bitmap,int MaxWidth,int MaxHeight,boolean filter){ Float ScalingNumber; Bitmap reBitmap; Matrix matrix = new Matrix(); ScalingNumber = Float.valueOf(scalingNumber(bitmap.getWidth(),bitmap.getHeight(),MaxWidth,MaxHeight)); matrix.setScale(1/ScalingNumber, 1/ScalingNumber); reBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),bitmap.getHeight(), matrix, filter); return reBitmap; } /** * 计算缩放比例 * @param oldWidth 原长 * @param oldHeight 原宽 * @param MaxWidth 最大长 * @param MaxHeight 最大宽 * @return 缩放比系数 */ private int scalingNumber(int oldWidth,int oldHeight,int MaxWidth,int MaxHeight){ int scalingN = 1; if(oldWidth > MaxWidth || oldHeight > MaxHeight){ scalingN = 2; while((oldWidth/scalingN > MaxWidth) || (oldHeight/scalingN > MaxHeight)){ scalingN*=2; } } return scalingN; } }
MessageInfor.java
package com.example.socketlw;
public class MessageInfor {
private int ID;
private String msg;
private Long time;
private Long userID;
/**
* 作者 LinOwl
* 2021.02.17
*/
public MessageInfor(String msg, Long time, Long userID, String type) {
this.msg = msg;
this.time = time;
this.userID = userID;
this.type = type;
}
public int getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Long getTime() {
return time;
}
public void setTime(Long time) {
this.time = time;
}
public Long getUserID() {
return userID;
}
public void setUserID(Long userID) {
this.userID = userID;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
private String type;
}
drawable部分
button_broder.xml
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<layer-list>
<item android:width="56dp" android:height="56dp">
<shape android:shape="oval">
<gradient android:endColor="@color/tv_style_color_seconde" android:gradientRadius="28dp" android:startColor="@color/tv_style_color_main" android:type="radial" />
</shape>
</item>
<item android:width="56dp" android:height="56dp" android:left="40dp">
<shape android:shape="oval">
<gradient android:endColor="@color/tv_style_color_seconde" android:gradientRadius="28dp" android:startColor="@color/tv_style_color_main" android:type="radial" />
</shape>
</item>
<item android:width="40dp" android:height="28dp" android:left="28dp">
<shape android:shape="rectangle">
<gradient android:angle="90" android:endColor="#FAF3FF" android:startColor="@color/tv_style_color_main" android:type="linear" />
</shape>
</item>
<item android:width="40dp" android:height="28dp" android:left="28dp" android:top="28dp">
<shape android:shape="rectangle">
<gradient android:angle="270" android:endColor="@color/tv_style_color_seconde" android:startColor="@color/tv_style_color_main" android:type="linear" />
</shape>
</item>
<item android:width="88dp" android:height="44dp" android:left="4dp" android:top="6dp">
<shape android:shape="rectangle">
<corners android:radius="24dp" />
<solid android:color="#8A73F5" />
</shape>
</item>
</layer-list>
</item>
<item android:state_pressed="false">
<layer-list>
<item android:width="56dp" android:height="56dp">
<shape android:shape="oval">
<gradient android:endColor="@color/tv_style_color_seconde" android:gradientRadius="28dp" android:startColor="@color/tv_style_color_main" android:type="radial" />
</shape>
</item>
<item android:width="56dp" android:height="56dp" android:left="40dp">
<shape android:shape="oval">
<gradient android:endColor="@color/tv_style_color_seconde" android:gradientRadius="28dp" android:startColor="@color/tv_style_color_main" android:type="radial" />
</shape>
</item>
<item android:width="40dp" android:height="28dp" android:left="28dp">
<shape android:shape="rectangle">
<gradient android:angle="90" android:endColor="#FAF3FF" android:startColor="@color/tv_style_color_main" android:type="linear" />
</shape>
</item>
<item android:width="40dp" android:height="28dp" android:left="28dp" android:top="28dp">
<shape android:shape="rectangle">
<gradient android:angle="270" android:endColor="@color/tv_style_color_seconde" android:startColor="@color/tv_style_color_main" android:type="linear" />
</shape>
</item>
<item android:width="88dp" android:height="44dp" android:left="4dp" android:top="6dp">
<shape android:shape="rectangle">
<corners android:radius="24dp" />
<solid android:color="#ffffff" />
</shape>
</item>
</layer-list>
</item>
</selector>
button_broder_bk.xml
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#62BBF6">
<item>
<shape android:shape="rectangle">
<solid android:color="@null" />
<corners android:radius="4dp" />
</shape>
</item>
<item android:drawable="@drawable/button_broder" />
</ripple>
item_left.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<layer-list>
<item>
<shape>
<corners android:bottomLeftRadius="5dp" android:bottomRightRadius="5dp" android:topRightRadius="5dp" />
<solid android:color="#9EDEF1" />
<padding android:left="5dp" android:right="5dp" android:bottom="5dp" android:top="5dp"/>
</shape>
</item>
</layer-list>
</item>
</selector>
item_right.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<layer-list>
<item>
<shape>
<corners android:bottomLeftRadius="5dp" android:bottomRightRadius="5dp" android:topRightRadius="5dp" />
<solid android:color="#F3E8DB" />
<padding android:left="5dp" android:right="5dp" android:bottom="5dp" android:top="5dp"/>
</shape>
</item>
</layer-list>
</item>
</selector>
layout部分
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".MainActivity">
<TextView
android:id="@+id/titleview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="NULL"
android:gravity="center"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/startserver"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="5dp"
android:background="@drawable/button_broder_bk"
android:text="开启服务器"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/continueserver"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@drawable/button_broder_bk"
android:text="连接服务器"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:orientation="horizontal"
app:layout_constraintBottom_toTopOf="@+id/startserver"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<EditText
android:id="@+id/sendmsgtext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="12"
android:inputType="textPersonName" />
<Button
android:id="@+id/sendmsgbt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="发送" />
<Button
android:id="@+id/sendimg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="发送图片" />
</LinearLayout>
<ListView
android:id="@+id/showmsg"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="客户端"
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleview"
android:transcriptMode="alwaysScroll"
android:divider="#00000000"/>
</androidx.constraintlayout.widget.ConstraintLayout>
chart_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="match_parent">
<TextView
android:id="@+id/itemleft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:background="@drawable/item_left"
android:gravity="center" />
<TextView
android:id="@+id/itemtimeleft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/itemleft"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:gravity="center"
android:textColor="#A7A9AA"/>
<TextView
android:id="@+id/itemright"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:background="@drawable/item_right" />
<TextView
android:id="@+id/itemtimeright"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/itemright"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:gravity="center"
android:textColor="#A7A9AA"/>
<ImageView
android:id="@+id/rightimg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"/>
<ImageView
android:id="@+id/leftimg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"/>
<TextView
android:id="@+id/rightimgtime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_below="@+id/rightimg"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:textColor="#A7A9AA"/>
<TextView
android:id="@+id/leftimgtime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_below="@+id/leftimg"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:textColor="#A7A9AA"/>
</RelativeLayout>
continue_server.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#F3F3F6">
<TextView
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="#FFFFBB33"
android:text="连接服务器"
android:textColor="#090909"
android:gravity="center"
android:textSize="24dp"
android:textStyle="bold"
android:scaleType="center" />
<EditText
android:id="@+id/editipv4text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="ipv4"
android:gravity="center"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"/>
<EditText
android:id="@+id/editprottext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:layout_marginTop="10dp"
android:layout_marginRight="50dp"
android:layout_marginBottom="10dp"
android:gravity="center"
android:hint="端口号" />
</LinearLayout>
start_server.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#F3F3F6">
<TextView
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="#FFFFBB33"
android:text="开启服务器"
android:textColor="#090909"
android:gravity="center"
android:textSize="24dp"
android:textStyle="bold"
android:scaleType="center" />
<EditText
android:id="@+id/editprot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="端口号"
android:gravity="center"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"/>
</LinearLayout>
values部分
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="funtion_bt_false">#DCD7D7</color>
<color name="funtion_bt_true">#C5C5C5</color>
<color name="colorPress">#ffffff</color>
<color name="colorNormal">#8A73F5</color>
<color name="tv_style_color_main">#7A63E5</color>
<color name="tv_style_color_seconde">#FFFFFF</color>
</resources>
themes.xml
源代码分享
链接:https://pan.baidu.com/s/159nd330OR55lKEQiAvHetw
提取码:oz7v
PS:部分机型存在差异,有些功能无法实现,以自己实际验证效果为准。