Android聊天室(客户端)

作者:黑衣侠客


一.前言:

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

二.操作方法:

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

三.准备:

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



四.代码部分:

首先加入网络权限:

AndroidMainifest.xml中加入这一行代码:

例如我,加到了这里:

然后,在grade(app)的dependencies中,导入Recyclerview的依赖
这里再次介绍一下grade(app):

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:组件化方案
  2. 在代码中实现android:tint效果
  3. android单元测试
  4. Android(安卓)要想美化就用Shape
  5. Android与js交互实例
  6. Android中的Binder详解
  7. Android与js交互实例
  8. Android(安卓)Wi-Fi工作原理
  9. Android(安卓)全屏

随机推荐

  1. ubuntu10.04下载android4.0的源码
  2. 进程方法Android进程与线程基本知识
  3. Android Wifi模块分析(四)
  4. Android 架构设计 本科《毕业论文》
  5. appium for windows 环境搭建
  6. Android(安卓)Audio Debug相关方法
  7. Gallery练习(android)
  8. 在android中利用多线程实现对控件的更新(
  9. 零基础入门Linux
  10. 使用科大讯飞提供的SDK实现android语音识