Android聊天室(客户端)

作者:黑衣侠客


一.前言:

这是我目前写的第二个Android App,但是目前因为知识有限,所以对一些代码不能解释到位,其中有些代码形式参考了网上的一些部分,后期慢慢我会将本篇博客进行修改和完善,此次Android网络聊天室会分成3篇博客进行讲解《Android聊天室(客户端)》,《Android聊天室(服务器)》,《Android聊天室(源码)》。由于目前git使用还不太熟,学习进度又很急,因此这次就以源码形式提供代码。另外,如果电脑配置不太高的话,可以尝试着给电脑安装内存条,这样AndroidStudio和虚拟机的运行都会大大加快,提高了编程效率。

二.操作方法:

现在介绍一下,当服务器客户端都写好了之后,如何进行使用。
1.首先利用真机安装好App,运行App出现登录界面。
2.运行服务器(注意:服务器我写到了idea中)
3.在App的登录端输入用户名,然后连接进入聊天界面。

三.准备:

1.编译器:AndroidStudio(有很多功能待我们发现)
2.所需知识点:
  • Java网络编程(很重要)
  • 《Android第一行代码》第三章(UI的基础)
    剩下的所用知识点还有很多,我会在下面依次介绍。
    (另外,不懂的知识点需要用百度/谷歌之类的搜索)

Android聊天室(客户端)_第1张图片
Android聊天室(客户端)_第2张图片 Android聊天室(客户端)_第3张图片


四.代码部分:

首先加入网络权限:

AndroidMainifest.xml中加入这一行代码:
在这里插入图片描述
例如我,加到了这里:
Android聊天室(客户端)_第4张图片
然后,在grade(app)的dependencies中,导入Recyclerview的依赖在这里插入图片描述
这里再次介绍一下grade(app):
Android聊天室(客户端)_第5张图片

activity_main.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"//父布局    android:background="@drawable/picture">//登录界面的背景图片    <androidx.appcompat.widget.Toolbar//启用Toolbar工具(菜单导航栏)        android:id="@+id/toolbar"        android:layout_width="match_parent"//父布局        android:layout_height="?attr/actionBarSize"//引用attr属性值        android:background="#3FA2F8">//背景颜色        <TextView//在Toolbar中显示文本字体            android:id="@+id/tv_room"            android:layout_width="wrap_content"//宽度适应字体            android:layout_height="wrap_content"//高度适应字体            android:layout_gravity="center"//居中排布            android:text="登陆聊天室"            android:textColor="#F3F4F5"            android:textSize="20sp" />//字体大小    </androidx.appcompat.widget.Toolbar>//Toolbar结束使用    <!--<View--> //注意大写,否则闪退    <!--android:id="@+id/ver_view"-->    <!--android:layout_toLeftOf="@+id/text_ip"-->    <!--android:layout_width="0dp"-->    <!--android:layout_height="match_parent"-->    <!--/>-->    <TextView        android:id="@+id/tv_name"        android:text="用户名"        android:textSize="20sp"        android:gravity="center"        android:layout_marginBottom="8dp"//距离底部有8dp的边距(注意这个底部不是父布局的底部,而是IP的顶部,如果不理解可以调节为0dp,进行观察)        android:layout_above="@+id/et_ip"        android:layout_marginLeft="45dp"//这个是左侧边距,是相对于父布局来说的(也就是手机的左侧边缘)        android:layout_width="wrap_content"//适应字体大小        android:layout_height="wrap_content" />    <EditText//用户名文本框        android:id="@+id/et_name"        android:layout_toRightOf="@id/tv_name"//在用户名为tv_name的右侧        android:layout_above="@id/et_ip"//用户名文本框放在IP输入的文本框的上面        android:layout_width="150dp"//文本横线的宽度        android:gravity="center"//布局于中心        android:layout_height="wrap_content" />//适应内容的大小    <TextView//IP的text        android:id="@+id/text_ip"        android:text="IP"        android:textSize="20sp"        android:layout_toLeftOf="@+id/et_ip"        android:layout_marginTop="5dp"        android:layout_width="60dp"        android:layout_height="wrap_content"        android:layout_below="@+id/tv_name"        />    <EditText//IP的文本框        android:id="@+id/et_ip"        android:layout_width="150dp"        android:layout_height="wrap_content"        android:layout_centerInParent="true"//水平垂直都居中(父类为RelativeLayout)        android:text="192.168.1.66"//可修改        />    <TextView//端口的textview        android:id="@+id/tv_port"        android:layout_below="@+id/text_ip"        android:layout_marginLeft="45dp"        android:text="端口"        android:textSize="20sp"        android:layout_marginTop="20dp"//他的顶部是IP的底部        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <EditText//端口的文本框        android:id="@+id/et_port"        android:layout_width="150dp"        android:layout_height="wrap_content"        android:text="6666"//可修改        android:gravity="center"        android:layout_below="@+id/et_ip"        android:layout_marginLeft="20dp"        android:layout_toRightOf="@id/tv_port"        />    <Button//button连接按键(用于连接客户端与客户端的控件)        android:id="@+id/btn_cnt"        android:layout_width="100dp"        android:layout_height="wrap_content"        android:layout_below="@id/et_port"        android:layout_marginLeft="130dp"        android:layout_marginTop="30dp"//距离端口的文本框的距离(也就是说,端口文本框的底部是button的顶部)        android:background="#07D0F3"//设置控件颜色        android:textColor="#ffffff"//设置字体颜色        android:text="连接" /></RelativeLayout>

MainActivity

package com.example.my_chatroom;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;import android.widget.TextView;import android.widget.Toast;import java.io.OutputStream;import java.net.Socket;public class MainActivity extends AppCompatActivity implements View.OnClickListener{//承接View.OnClickListener接口    private OutputStream outputStream=null;    private Socket socket=null;    private String ip="192.168.1.66";    private Button btn_cnt;    private EditText et_ip;    private EditText et_name;    private EditText et_port;    private TextView myName;    @Override    protected void onCreate(Bundle savedInstanceState) {//这个方法是系统生成Activity之后自带的,是一个生命周期的开始        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btn_cnt = (Button)findViewById(R.id.btn_cnt);//获取到布局中id为btn_cnt的button,并赋值给声明为Button类型的变量btn_cnt(连接控件)        et_ip=findViewById(R.id.et_ip);        et_port=findViewById(R.id.et_port);        et_name=findViewById(R.id.et_name);        myName=findViewById(R.id.my_name);        btn_cnt.setOnClickListener(MainActivity.this);//setOnClickListener的参数要求是一个实现了OnClickListener接口的对象实体,它可以是任何类的实例,只要该类实现了OnClickListener.    }    public void onClick(View view){        String name = et_name.getText().toString();        if("".equals(name)){            Toast.makeText(this, "请输入用户名:", Toast.LENGTH_SHORT).show();//如果输入的用户名为空的话,那么下端会出现提示        }else{//反之            Intent intent = new Intent(MainActivity.this,ChatRoom.class);            //intent相当于传播的一种媒介,本行的意思是:使内部代码从MainActivity.this到ChatRoom.class进行转变,但只是设置了目标,还未开始执行            intent.putExtra("name",et_name.getText().toString());//这个意思相当于分类处理,在从Activity到ChatRoom,需要将数据送过去,那么就需要对即将传送的数据进行分类处理,使name,ip,port分别对应指定内容            intent.putExtra("ip",et_ip.toString());            intent.putExtra("port",et_port.toString());            startActivity(intent);//开始执行        }    }}

activity_chat_room

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:orientation="vertical"    android:background="#d8e0e8"    android:layout_width="match_parent"    android:layout_height="match_parent">    <androidx.appcompat.widget.Toolbar        android:id="@+id/toolbar"        android:layout_width="match_parent"        android:layout_height="?attr/actionBarSize"        android:background="#187C8F">        <Button            android:id="@+id/back"            android:layout_width="50dp"            android:layout_height="wrap_content"            android:background="@drawable/back" />        <TextView            android:id="@+id/text_room"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_gravity="center"            android:text="聊天室"            android:textColor="#F3F4F5"            android:textSize="20sp" />    </androidx.appcompat.widget.Toolbar>    <androidx.recyclerview.widget.RecyclerView        android:id="@+id/msg_recycler_view"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1"        android:background="@drawable/background" />    <LinearLayout        android:orientation="horizontal"        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="#ffffff"            />        <Button            android:id="@+id/send"            android:layout_width="wrap_content"            android:layout_height="50dp"            android:background="#07D0F3"            android:text="发送"            android:textColor="#ffffff" />    </LinearLayout></LinearLayout>

ChatRoom

package com.example.my_chatroom;import androidx.annotation.NonNull;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.Handler;import android.os.Looper;import android.os.Message;import android.text.TextUtils;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;import android.widget.Toast;import java.io.DataInput;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.net.Socket;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.List;public class ChatRoom extends AppCompatActivity implements View.OnClickListener{    private List<Msg> msgList = new ArrayList<>();    private EditText inputText;    private Button send;    private Button back;    private RecyclerView msgRecyclerView;    private MsgAdapter adapter;    private Socket socketSend;    private String ip="192.168.1.66";    private String port="6666";    DataInputStream dis;    DataOutputStream dos;    boolean isRunning = false;    private TextView myName;    private String recMsg;    private boolean isSend=false;    private String name;    private Handler handler = new Handler(Looper.myLooper()){//获取当前进程的Looper对象传给handler        @Override        public void handleMessage(@NonNull Message msg){//?            if(!recMsg.isEmpty()){                addNewMessage(recMsg,Msg.TYPE_RECEIVED);//添加新数据            }        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_chat_room);        Intent intent =getIntent();        name=intent.getStringExtra("name");        inputText = findViewById(R.id.input_text);        send=findViewById(R.id.send);        send.setOnClickListener(this);        back = findViewById(R.id.back);        back.setOnClickListener(new View.OnClickListener(){            @Override            public void onClick(View view){                AlertDialog.Builder dialog= new AlertDialog.Builder(ChatRoom.this);                dialog.setTitle("退出");                dialog.setMessage("退出登录?");                dialog.setCancelable(false);                dialog.setPositiveButton("是", new DialogInterface.OnClickListener() {                    @Override                    public void onClick(DialogInterface dialogInterface, int i) {                        finish();//finish()是在程序执行的过程中使用它来将对象销毁,finish()方法用于结束一个Activity的生命周期                    }                });                dialog.setNegativeButton("否", new DialogInterface.OnClickListener() {                    @Override                    public void onClick(DialogInterface dialogInterface, int i) {                    }                });                dialog.show();//让返回键开始启动            }        });        runOnUiThread(new Runnable() {            @Override            public void run() {                LinearLayoutManager layoutManager = new LinearLayoutManager(ChatRoom.this);                msgRecyclerView= findViewById(R.id.msg_recycler_view);                msgRecyclerView.setLayoutManager(layoutManager);                adapter = new MsgAdapter(msgList);                msgRecyclerView.setAdapter(adapter);            }        });        new Thread(new Runnable(){            @Override            public void run(){                try{                    if((socketSend = new Socket(ip,Integer.parseInt(port)))==null){                        Log.d("ttw","发送了一条消息1");                    }                    else{                        isRunning = true;                        Log.d("ttw","发送了一条消息2");                        dis = new DataInputStream(socketSend.getInputStream());                        dos = new DataOutputStream(socketSend.getOutputStream());                        new Thread(new receive(),"接收线程").start();                        new Thread(new Send(),"发送线程").start();                    }                }catch(Exception e){                    isRunning = false;                    e.printStackTrace();                    Looper.prepare();                    Toast.makeText(ChatRoom.this, "连接服务器失败!!!", Toast.LENGTH_SHORT).show();                    Looper.loop();                    try{                        socketSend.close();                    }catch(IOException e1){                        e1.printStackTrace();                    }                    finish();                }            }        }).start();    }    public void addNewMessage(String msg,int type){        Msg message = new Msg(msg,type);        msgList.add(message);        adapter.notifyItemInserted(msgList.size()-1);        msgRecyclerView.scrollToPosition(msgList.size()-1);    }    class receive implements Runnable{        public void run(){            recMsg = "";            while(isRunning){                try{                    recMsg = dis.readUTF();                    Log.d("ttw","收到了一条消息"+"recMsg: "+ recMsg);                }catch(Exception e){                    e.printStackTrace();                }                if(!TextUtils.isEmpty(recMsg)){                    Log.d("ttw","inputStream:"+dis);                    Message message = new Message();                    message.obj=recMsg;                    handler.sendMessage(message);                }            }        }    }    @Override    public void onClick(View view){        String content = inputText.getText().toString();        @SuppressLint("SimpleDateFormat")        String date = new SimpleDateFormat("hh:mm:ss").format(new Date());        StringBuilder sb = new StringBuilder();        sb.append(content).append("\n\n"+date);        content = sb.toString();        if(!"".equals(content)){            Msg msg = new Msg(content,Msg.TYPE_SENT);            msgList.add(msg);            adapter.notifyItemInserted(msgList.size()-1);            msgRecyclerView.scrollToPosition(msgList.size()-1);            isSend = true;        }        sb.delete(0,sb.length());    }    class Send implements Runnable{        @Override        public void run(){            while(isRunning){                String content = inputText.getText().toString();                Log.d("ttw","发了一条消息");                if(!"".equals(content)&&isSend){                    @SuppressLint("SimpleDateFormat")                    String date = new SimpleDateFormat("hh:mm:ss").format(new Date());                    StringBuilder sb = new StringBuilder();                    sb.append(content).append("\n\n来自:").append(name).append("\n"+date);                    content = sb.toString();                    try{                        dos.writeUTF(content);                        sb.delete(0,sb.length());                        Log.d("ttw","发送了一条消息");                    }catch(IOException e){                        e.printStackTrace();                    }                    isSend = false;                    inputText.setText("");                }            }        }    }}

Msg(类)

package com.example.my_chatroom;public class Msg {    public static final int TYPE_RECEIVED=0;    public static final int TYPE_SENT =1;    public String getContent(){        return content;    }    public int getType(){        return type;    }    private String content;    private int type;    public Msg(String content,int type){        this.content=content;        this.type=type;    }}

MsgAdapter(类)

package com.example.my_chatroom;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.util.List;public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder> {    private List<Msg> mMsgList;    @NonNull    @Override    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,int viewType){        //ViewHolder通常出现在适配器里,为的是listview滚动的时候快速设置值,而不必每次都重新创建很多对象,从而提升性能。        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.msg_item,parent,false);        //LayoutInflat.from()从一个Context中,获得一个布局填充器,这样你就可以使用这个填充器来把xml布局文件转为View对象了。        //LayoutInflater.from(parent.getContext()).inflate(R.layout.msg_item,parent,false);这样的方法来加载布局msg_item.xml        return new ViewHolder(view);    }    @Override    public void onBindViewHolder(@NonNull ViewHolder holder,int position){        Msg msg =mMsgList.get(position);        if(msg.getType()==Msg.TYPE_RECEIVED){            holder.leftLayout.setVisibility(View.VISIBLE);            holder.rightLayout.setVisibility(View.GONE);            holder.leftMsg.setText(msg.getContent());        }else if(msg.getType()==Msg.TYPE_SENT){            holder.leftLayout.setVisibility(View.GONE);            holder.rightLayout.setVisibility(View.VISIBLE);            holder.rightMsg.setText(msg.getContent());        }    }    @Override    public int getItemCount(){        return mMsgList.size();    }    static class ViewHolder extends RecyclerView.ViewHolder{        LinearLayout leftLayout;        LinearLayout rightLayout;        TextView leftMsg;        TextView rightMsg;        public ViewHolder(@NonNull View view){            super(view);            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;    }}

msg_item.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="wrap_content"    android:padding="10dp"    >    <LinearLayout        android:id="@+id/left_layout"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="left"        >        <ImageView            android:id="@+id/iv_head_others"            android:background="@drawable/headofothers"            android:layout_marginTop="20dp"            android:layout_width="60dp"            android:layout_height="60dp" />        <LinearLayout            android:orientation="vertical"            android:layout_width="wrap_content"            android:layout_height="wrap_content">            <TextView                android:id="@+id/others_name"                android:layout_gravity="left"                android:layout_width="wrap_content"                android:layout_height="wrap_content" />            <TextView                android:id="@+id/left_msg"                android:textStyle="bold"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_marginTop="5dp"                android:layout_marginBottom="5dp"                android:background="@drawable/qipao_2"                android:layout_gravity="center"                android:textColor="#B955B9" />        </LinearLayout>    </LinearLayout>    <LinearLayout        android:id="@+id/right_layout"        android:layout_gravity="right"        android:layout_width="wrap_content"        android:layout_height="wrap_content">        <LinearLayout            android:orientation="vertical"            android:layout_width="wrap_content"            android:layout_height="wrap_content">            <TextView                android:id="@+id/my_name"                android:layout_gravity="right"                android:layout_width="wrap_content"                android:layout_height="wrap_content" />            <TextView                android:id="@+id/right_msg"                android:textStyle="bold"                android:layout_gravity="center"                android:background="@drawable/qipao_1"                android:layout_width="wrap_content"                android:layout_height="wrap_content" />        </LinearLayout>        <ImageView            android:id="@+id/iv_head_my"            android:background="@drawable/headofmy"            android:layout_marginTop="20dp"            android:layout_width="60dp"            android:layout_height="60dp" />    </LinearLayout></LinearLayout>

一些特效代码:

shape.xml

<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android">    <solid        android:color="#07D0F3"/></selector>

previous.xml

<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android">    <solid        android:color="#0fffff"/></selector>

click.xml

<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android">    <corners        android:radius="20dp"/>    <item android:drawable="@drawable/shape"        android:state_enabled="true"        android:state_pressed="true"/>    <item android:drawable="@drawable/previous"        android:state_enabled="true"        android:state_pressed="false"/></selector>

然后选几张自己喜欢的图片,放在此项目的drawable文件夹中,然后照着代码重命名一下,特别注意,聊天气泡别忘了设置为.9文件(具体看一下第二版《第一行代码》的3.7)链接
另外我收藏了几篇对于阅读本篇博客有很多帮助的文章
对Activity.runOnUiThread的理解
Android Handler详解
android的MainActivity中setOnClickListener(this)中的this指代
Toolbar中的attr/actionBarSize
onCreate方法
到此为止,所有的客户端代码已全部写入,然后还有些代码的注解还没有写,后期我会对本篇博客进行补充和完善。

更多相关文章

  1. 【Android开发学习25】界面布局之相对布局RelativeLayout
  2. Android布局优化(二)优雅获取界面布局耗时
  3. RelativeLayout布局的对齐属性
  4. Android布局优化(五)绘制优化—避免过度绘制
  5. Android 线性布局详解
  6. Android的相对布局属性的解释
  7. 相对布局RelativeLayout的妙用:提示标签在输入框内

随机推荐

  1. Android(安卓)-- 程序判断手机ROOT状态,
  2. android的开发----环境搭配(原创)
  3. 学习android listview组件
  4. android 控件翻转切换布局
  5. android 报错java.io.IOException: Permi
  6. android开发之下载文本、音乐文件
  7. AlertDailog弹框显示问题
  8. Android(安卓)Retrofit 源码系列(二)~ 自定
  9. Android中CookieManager的底层实现
  10. Android(安卓)从 JNI 中传入 UTF8 的字符