涉及到的知识点:
1.9.png的使用,这个用来做气泡的
2.RecyclerView滑动组建的使用,貌似要勾选Android 7.0才能使用 之前一直勾8.0折腾了很久
3.Socket连网通信
4.线程
5.在子线程中更新UI
在AndroidManifest.xml添加连网权限
<uses-permission android:name="android.permission.INTERNET"/>
准备两个气泡图片
气泡是自己用ps画的,长得比较丑。。。
导入图片后 对图片右键。.9.png
把四周黑色的线条拉成这样
不然拉伸效果会很难看,我们要的是↓这种效果
另一个气泡同理
这里的气泡教程比较详细
布局
先在app\build.gradle里添加如下代码
然后开始编辑主Activity布局 activity_main.xml
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.shanzhaibanweixin.liziguo.MainActivity">
<android.support.v7.widget.RecyclerView
android:background="#eeeeee"
android:id="@+id/recy"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="0dp"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/text"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/bt"
android:text="发送"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
大概长这个比样
新建一个布局命名为xx.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:padding="5dp"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/zuo"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_margin="5dp"
android:id="@+id/zuoimg"
android:layout_width="50dp"
android:layout_height="50dp" />
<TextView
android:text="1234567891234568976545678231231231231231231231239231456"
android:id="@+id/zuotext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/zuoimg"
android:layout_margin="5dp"
android:background="@mipmap/qp2"
android:textSize="25sp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/you"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_margin="5dp"
android:id="@+id/youimg"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true" />
<TextView
android:id="@+id/youtext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/youimg"
android:layout_margin="5dp"
android:background="@mipmap/qqq"
android:text="1234567890123413123123123123123568901236547890"
android:textSize="25sp" />
</RelativeLayout>
</LinearLayout>
大概长这个比样,正方形的框框是头像
布局写好了 开始敲代码吧
MainActivity.java的代码
package com.shanzhaibanweixin.liziguo;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private EditText text;
private Button bt;
private RecyclerView recy;
public List list;
private lianjie lj;
public Handler hand = new Handler() {//用于在子线程更新UI,收到信息和连接服务器成功时用到
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
Toast.makeText(getApplicationContext(), "连接服务器成功", Toast.LENGTH_SHORT).show();
break;
case 2:
recy.setAdapter(new adapter(list));//设置适配器
recy.scrollToPosition(list.size() - 1);//将屏幕移动到RecyclerView的底部
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
list = new ArrayList<xx>();
text = findViewById(R.id.text);
bt = findViewById(R.id.bt);
recy = findViewById(R.id.recy);
LinearLayoutManager lin = new LinearLayoutManager(this);
recy.setLayoutManager(lin);
lj = new lianjie(this);
bt.setOnClickListener(new View.OnClickListener() {//给发送按钮设置监听事件
@Override
public void onClick(View v) {
String s = text.getText().toString();
if (s == null || s.equals("")) {
Toast.makeText(getApplicationContext(), "发送消息不能为空", Toast.LENGTH_SHORT).show();
} else {
list.add(new xx(s, R.mipmap.gaara, false));
//new一个xx类,第一个参数的信息的内容,第二个参数是头像的图片id,第三个参数表示左右
//true为左边,false为右
lj.fa(s);//发送消息
recy.setAdapter(new adapter(list));//再把list添加到适配器
text.setText(null);
recy.scrollToPosition(list.size() - 1);//将屏幕移动到RecyclerView的底部
}
}
});
}
}
新建类adapter.java
package com.shanzhaibanweixin.liziguo;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by Liziguo on 2018/5/27.
*/
public class adapter extends RecyclerView.Adapter<adapter.ViewHolder> {//适配器
private List<xx> list;
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView zuoimg, youimg;
TextView zuotext, youtext;
ViewGroup zuolin, youlin;
public ViewHolder(View itemView) {
super(itemView);
zuolin = itemView.findViewById(R.id.zuo);
zuoimg = itemView.findViewById(R.id.zuoimg);
zuotext = itemView.findViewById(R.id.zuotext);
youlin = itemView.findViewById(R.id.you);
youimg = itemView.findViewById(R.id.youimg);
youtext = itemView.findViewById(R.id.youtext);
}
}
public adapter(List l) {
list = l;
}
@Override
public adapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.xx, parent, false);
ViewHolder h = new ViewHolder(v);
Log.d("MainActivity", "onCreate");
return h;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {//滑动RecyclerView出发的事件
xx x = list.get(position);
if (x.zuo) {//判断该信息该信息是显示在左边还是右边,如果要在左边显示则把右边的部分隐藏
holder.zuolin.setVisibility(View.VISIBLE);
holder.youlin.setVisibility(View.GONE);//把右边的隐藏
holder.zuoimg.setImageResource(x.img);
holder.zuotext.setText(x.text);
}else{
holder.youlin.setVisibility(View.VISIBLE);
holder.zuolin.setVisibility(View.GONE);//把左边的隐藏
holder.youimg.setImageResource(x.img);
holder.youtext.setText(x.text);
}
Log.d("MainActivity", "onBind");
}
@Override
public int getItemCount() {//这里要重写一下 不然不会显示任何信息
Log.d("asdasd", "" + list.size());
return list.size();
}
}
class xx {//信息类
public String text;//信息内容
public int img;//头像的图片id
public boolean zuo = true;//控制信息显示在左边还有右边,默认左边
public xx(String s, int i) {
text = s;
img = i;
}
public xx(String s, int i, boolean b) {
text = s;
img = i;
zuo = b;
}
}
新建类lianjie.java 连网类使用Socket进行通信
package com.shanzhaibanweixin.liziguo;
import android.os.Message;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
/**
* Created by Liziguo on 2018/5/28.
*/
public class lianjie implements Runnable {
private MainActivity main;
private Socket so = null;
public lianjie(MainActivity m) {
main = m;
new Thread(this).start();
}
@Override
public void run() {
try {
so = new Socket("xxx.xxx.xxx.xxx", 61666);//第一个参数是你服务器的ip,第二个参数是端口号
//服务器连接成功的话则发一个Message给UI线程 跳到MainActivity.java的第24行
Message msg = new Message();
msg.what = 1;
main.hand.sendMessage(msg);
} catch (IOException e) {
e.printStackTrace();
return;
}
//连接服务器成功之后开始接受消息
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(so.getInputStream(),"UTF-8"));
while (true) {
String s = in.readLine();
if (s == null || s.equals("")) break;
//收到消息之后便new一个xx类,第一个参数是信息内容,第二个参数是头像ID
//第三个参数显示在左边还是右边,没有第三个参数的话默认显示在左边
//我事先准备了两张头像
//最后将他添加到list里
main.list.add(new xx(s, R.mipmap.sz));
//收到消息后就要更新RecyclerView将他们显示出来,跳到MainActivity.java的第24行
Message msg = new Message();
msg.what = 2;
main.hand.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void fa(final String s) {//发送消息
new Thread(new Runnable() {
@Override
public void run() {//我发现有的手机在UI线程发消息会崩溃,而有的不会
// 这里就在一个新的线程发消息
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(so.getOutputStream(),"UTF-8"));
out.write(s + "\r\n");
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
好的,现在已经完成一大半了,接下来就剩服务器端了。服务器端我是用eclipse写的
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class FWQ微信 {//2017.12.3
public static ArrayList<Socket> list = new ArrayList<Socket>();
public static Executor ex = Executors.newCachedThreadPool();
public static void main(String[] args) {
ServerSocket soo = null;
Socket so = null;
try {
soo = new ServerSocket(61666);
String chuangjian;
chuangjian = "创建服务器成功...";
System.out.println(chuangjian);
while (true) {
so = soo.accept();
list.add(so);
ex.execute(new Jie(so));
}
} catch (IOException e) {
System.out.println("创建服务器失败...");
try {
soo.close();
so.close();
} catch (IOException e1) {
}
}
}
}
class Jie implements Runnable {// 用于接收消息
private Socket so;
Jie(Socket so) {
this.so = so;
String lianjie;
lianjie = "已连接 用户IP:" + so.getInetAddress().getHostAddress() + "当前连接数:" + FWQ微信.list.size();
System.out.println(lianjie);
}
public void run() {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(so.getInputStream(), "UTF-8"));
String s;
while ((s = in.readLine()) != null) {
new Thread(new Fa(s, so)).start();// 收到消息之后 就把收到的消息发送给除了发送者之外在所有人
}
FWQ微信.list.remove(so);
String tuichu;
tuichu = "已退出 用户IP:" + so.getInetAddress().getHostAddress() + "当前连接数:" + FWQ微信.list.size();
System.out.println(tuichu);
in.close();
so.close();
} catch (IOException e) {
FWQ微信.list.remove(so);
String tuichu;
tuichu = "已退出 用户IP:" + so.getInetAddress().getHostAddress() + "当前连接数:" + FWQ微信.list.size();
System.out.println(tuichu);
try {
in.close();
so.close();
} catch (IOException e1) {
}
}
}
}
class Fa implements Runnable {// 发送消息
private String s;
private Socket ss;
Fa(String s, Socket so) {
this.s = s;
ss = so;
}
public void run() {
Socket so;
try {
for (int i = 0; i < FWQ微信.list.size(); i++) {
so = FWQ微信.list.get(i);
if (so == ss)
continue;// 收到消息之后 就把收到的消息发送给除了发送者之外在所有人。ss为发送者
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(so.getOutputStream(), "UTF-8"));
out.write(s + "\r\n");
out.flush();
}
} catch (IOException e) {
System.out.println("群发异常");
}
}
}
接下来我们运行一下试试
先是服务器端,这里我已经把服务器打包成jar文件上传到服务器了
只接在eclipse上运行也是可以的 lianjie.java的ip改成你电脑的ip就行了
不过手机跟电脑要在同一个局域网
再打开我们的山寨版微信