Android Studio续socket通信

演示地址: 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:部分机型存在差异,有些功能无法实现,以自己实际验证效果为准。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇