涉及到的知识点:
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
把四周黑色的线条拉成这样
不然拉伸效果会很难看,我们要的是↓这种效果
另一个气泡同理
https://blog.csdn.net/oudetu/article/details/78968067 这里的气泡教程比较详细
布局
先在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就行了
不过手机跟电脑要在同一个局域网
再打开我们的山寨版微信
大功告成!
下载地址:https://download.csdn.net/download/u010756046/10450105