当我们做项目时肯定会用到即时通讯技术,当然网上第三方已经提供了许多即时通讯的接口,但是一味的使用别人的做好的产品是多么无趣,今天就做了一个关于多个画板间的即时通讯简单DEMO,用到了socket+多线程联 + handler + message联合应用的技术,废话少说,先贴图。如需下载源码,在文章最后已贴出连接。
1、项目目录结构
2、当在一个画板上画图时,另一个画板也会自动画出相应图案
3、长按可弹出菜单栏
4、选择画笔颜色
5、不同颜色绘画出的图形
6、可设置画笔宽度
7、不同宽度的画笔
8、可设置填充样式
9、保存画图
10、图片保存在sd卡的根目录下picture的目录中
相信看客看完图已经心动了,这就贴出干货仅供参考
11、服务器端
/**
* socket端口逻辑处理类
*
* @author 康驻关
*/
public class AndroidRunable implements Runnable {
// 用来保存每个接入该端口的socket
public static HashMap<String, Socket> people = new HashMap<String, Socket>();
// 当前socket
Socket socket = null;
// 给当前socket一个标识名
String name = null;
public AndroidRunable(Socket socket, String name) {
this.socket = socket;
this.name = name;
}
public void run() {
people.put(name, socket);
// 向android客户端输出结果
System.out.println(name);
// 读取客户端数据
DataInputStream input = null;
String clientInputStr = null;
try {
input = new DataInputStream(socket.getInputStream());
clientInputStr = input.readUTF();// 这里要注意和客户端输出流的写方法对应,否则会抛
// EOFException
} catch (IOException e1) {
e1.printStackTrace();
}
// 判断客户端传来的信息,如果不是 end 就循环接收客户端发来的信息,如果是 end 则结束循环,关闭当前socket对象
while (!clientInputStr.equals("end")) {
try {
Set<String> keyname = people.keySet();
// 给所有接入该端口的socket对象发送消息
for (String pubname : keyname) {
// if(pubname.equals(name)) break;
Socket pubsocket = people.get(pubname);
DataOutputStream pubDos = new DataOutputStream(pubsocket.getOutputStream());
pubDos.writeUTF(clientInputStr);
pubDos.flush();
// System.out.println(pubname+"--"+pubsocket.toString());
}
clientInputStr = input.readUTF();
} catch (Exception e) {
e.printStackTrace();
}
}
//关闭当前socket对象
close();
}
/**
* 将当前socket对象关闭并移除
*/
private void close() {
try {
socket.close();
System.out.println(name + "退出");
people.remove(name);
} catch (IOException e) {
e.printStackTrace();
}
}
}
12、客户端
1,继承view类实现一个画板View
/**
* 实现画板功能的View
* @author 康驻关
*/
public class HuaBanView extends View {
/**缓冲位图*/
private Bitmap cacheBitmap;
/**缓冲位图的画板*/
public Canvas cacheCanvas;
/**缓冲画笔*/
public Paint paint;
/**实际画笔*/
private Paint BitmapPaint;
/**保存绘制曲线路径*/
public Path path;
/**画布高*/
private int height;
/**画布宽*/
private int width;
/**保存上一次绘制的终点横坐标*/
public float pX;
/**保存上一次绘制的终点纵坐标*/
public float pY;
/**画笔初始颜色*/
private int paintColor = Color.RED;
/**线状状态*/
private static Paint.Style paintStyle = Paint.Style.STROKE;
/**画笔粗细*/
private static int paintWidth = 3;
private Canvas canvas;
/**获取View实际宽高的方法*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
height = h;
width = w;
init();
}
private void init(){
cacheBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
cacheCanvas = new Canvas(cacheBitmap);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
path = new Path();
BitmapPaint = new Paint();
updatePaint();
}
private void updatePaint(){
paint.setColor(paintColor);
paint.setStyle(paintStyle);
paint.setStrokeWidth(paintWidth);
}
public HuaBanView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HuaBanView(Context context){
super(context);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
path.moveTo(event.getX(), event.getY());
pX = event.getX();
pY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
path.quadTo(pX, pY, event.getX(), event.getY());
pX = event.getX();
pY = event.getY();
break;
case MotionEvent.ACTION_UP:
cacheCanvas.drawPath(path, paint);
path.reset();
break;
}
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
this.canvas = canvas;
BitmapPaint = new Paint();
canvas.drawBitmap(cacheBitmap, 0,0, BitmapPaint);
canvas.drawPath(path, paint);
}
/**更新画笔颜色*/
public void setColor(int color){
paintColor = color;
updatePaint();
}
/**设置画笔粗细*/
public void setPaintWidth(int width){
paintWidth = width;
updatePaint();
}
public static final int PEN = 1;
public static final int PAIL = 2;
/**设置画笔样式*/
public void setStyle(int style){
switch(style){
case PEN:
paintStyle = Paint.Style.STROKE;
break;
case PAIL:
paintStyle = Paint.Style.FILL;
break;
}
updatePaint();
}
/**清空画布*/
public void clearScreen(){
if(canvas != null){
Paint backPaint = new Paint();
backPaint.setColor(Color.WHITE);
canvas.drawRect(new Rect(0, 0, width, height), backPaint);
cacheCanvas.drawRect(new Rect(0, 0, width, height), backPaint);
}
invalidate();
}
/**获得位图*/
public Bitmap getBitmap(){
return this.cacheBitmap;
}
}
2,主界面逻辑操作
/**
* 实现画图界面的ACTIVITY
*
* @author 康驻关
*/
public class Drewing extends Activity {
/** socket对象 **/
private Socket socket = null;
/** 输出流对象 **/
private DataOutputStream out = null;
/** 记录当前点信息 **/
private float startX, startY, stopX, stopY;
// private int RandomID = new Random().nextInt(10);
/** 画画的内容区 */
private HuaBanView hbView;
/** 设置粗细的Dialog */
private AlertDialog dialog;
/** 调节画笔大小的layout **/
private View dialogView;
/** 显示当前画笔宽度 **/
private TextView shouWidth;
/** 调节画笔大小SeekBar **/
private SeekBar widthSb;
/** 记录当前画笔宽度 **/
private int paintWidth;
/** 用来计时 **/
private int currentTime;
private Timer timer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drawing);
// 初始化各View对象
InitView();
// 开启读取服务器端数据线程
new Mythread().start();
}
/**
* 对每个View对象初始化
*/
public void InitView() {
hbView = (HuaBanView) findViewById(R.id.huaBanView1);
hbView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 如果socket没有连接成功则通知客户端
if (out == null) {
Toast.makeText(Drewing.this, "正在连接服务器,请稍后!", Toast.LENGTH_SHORT).show();
return false;
}
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
changeTime();// 点击屏幕时开启一个Timer用于记录当前点击时间
hbView.path.moveTo(event.getX(), event.getY());// 当当前画笔位置移到该坐标处
// 记录开始移动的坐标
hbView.pX = event.getX();
hbView.pY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
timer.cancel();// 滑动关闭当前Timer对象
// 获取手移动后的坐标将其发送给服务器
appendText(hbView.pX + "," + hbView.pY + "," + event.getX() + "," + event.getY());
hbView.path.moveTo(hbView.pX, hbView.pY);
// 从起始点移动到终点,若在调用path.quadTo方法前没有调用path.moveTo方法,则起始位置默认从
// x0,y=0开始
// 即hbView.pX, hbView.pY所在的参数默认等于0 , 0
hbView.path.quadTo(hbView.pX, hbView.pY, event.getX(), event.getY());
// 记录滑动到的当前点坐标
hbView.pX = event.getX();
hbView.pY = event.getY();
break;
case MotionEvent.ACTION_UP:
timer.cancel();// 点击离开屏幕时关闭当前Timer对象
// 当点击事件离开时,将所绘画的路径保存到hbview中
hbView.cacheCanvas.drawPath(hbView.path, hbView.paint);
// 清除走过的路径,重置路径
hbView.path.reset();
break;
}
// 刷新界面
hbView.invalidate();
return true;
}
});
// 映射出 dialog_width_set 布局文件
dialogView = getLayoutInflater().inflate(R.layout.dialog_width_set, null);
shouWidth = (TextView) dialogView.findViewById(R.id.textView1);
widthSb = (SeekBar) dialogView.findViewById(R.id.seekBar1);
// SeekBar添加监听事件
widthSb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// 改变画笔宽度
shouWidth.setText("当前选中宽度:" + (progress + 1));
paintWidth = progress + 1;
}
});
// 初始化AlertDialog
dialog = new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_info).setTitle("设置画笔宽度")
.setView(dialogView) // 将 dialog_width_set 布局绑定在Dialog中
.setPositiveButton("确定", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 改变画笔宽度
hbView.setPaintWidth(paintWidth);
}
}).setNegativeButton("取消", null).create();
}
/** 用于改变UI内容的Handler **/
@SuppressLint("HandlerLeak")
Handler changeUI = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x11) {// 将服务器端的数据绘制到View上
handleDraws(msg);
} else if (msg.what == 0x12) { // 打开菜单
// dialog.show();
openOptionsMenu();
} else if (msg.what == 0x13) { // 提示保存图片信息
Toast.makeText(Drewing.this, "图片保存成功", Toast.LENGTH_LONG).show();
;
}
}
};
/**
* 通过服务器端传过来的数据给imagview绘画
*
* @param msg
* 服务器传来的信息绑定在message
*/
@SuppressLint("UseValueOf")
private synchronized void handleDraws(Message msg) {
Bundle bundle = msg.getData();
String string = bundle.getString("msg").trim();
// 将接收的参数转换成坐标
String[] str = string.split(",");
if (str.length == 4) {
startX = new Float(str[0]);
startY = new Float(str[1]);
stopX = new Float(str[2]);
stopY = new Float(str[3]);
// 在开始和结束坐标间画一条线
hbView.path.moveTo(startX, startY);
hbView.path.quadTo(startX, startY, stopX, stopY);
hbView.cacheCanvas.drawPath(hbView.path, hbView.paint);
hbView.path.reset();
hbView.invalidate();
}
// Log.i("String"+RandomID, string);
}
/**
* 此线程用于从服务器端接受数据更新UI
*
* @author 康驻关
*/
public class Mythread extends Thread {
@Override
public void run() {
// 链接服务器
conn();
// 通知服务器绘画开始
if (out != null) {
// 绘画开始
appendText("begin");
}
Bundle bundle = new Bundle();
bundle.clear();
try {
// 读取服务器端数据
DataInputStream input = new DataInputStream(socket.getInputStream());
String ret = input.readUTF();
while (true) {
// 定义消息
Message msg = new Message();
msg.what = 0x11;
// 将数据绑定在message中
bundle.putString("msg", ret);
msg.setData(bundle);
// 发送消息 修改UI线程中的组件
changeUI.sendMessage(msg);
// 循环读服务器端发来的数据
ret = input.readUTF();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 连接服务器
*/
private void conn() {
try {
// 连接服务器 并设置连接超时为5秒
socket = new Socket();
socket.connect(new InetSocketAddress(Utils.MyIP, 30000), 5000);
// 向服务器端发送数据
out = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
// 连接超时 在UI界面显示消息
Log.i("IOException", "链接超时");
}
}
/**
* 给服务器发送信息
*
* @param str
* 给服务器发送的消息
*/
private void appendText(String str) {
try {
if (out != null) {
out.writeUTF(str);
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 关闭各种资源
*/
private void close() {
try {
if (out != null) {
// 给服务器发送 end 结束标示
appendText("end");
out.close();
}
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void onStop() {
super.onStop();
close();
}
@Override
protected void onDestroy() {
super.onDestroy();
close();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
SubMenu colorSm = menu.addSubMenu(1, 1, 1, "选择画笔颜色");
colorSm.add(2, 200, 200, "红色");
colorSm.add(2, 210, 210, "绿色");
colorSm.add(2, 220, 220, "蓝色");
colorSm.add(2, 230, 230, "紫色");
colorSm.add(2, 240, 240, "黄色");
colorSm.add(2, 250, 250, "黑色");
menu.add(1, 2, 2, "设置画笔粗细");
SubMenu widthSm = menu.addSubMenu(1, 3, 3, "设置画笔样式");
widthSm.add(3, 300, 300, "线状画笔");
widthSm.add(3, 301, 301, "填充画笔");
// menu.add(1, 4, 4, "清空画布");
menu.add(1, 5, 5, "保存画布");
menu.add(1, 6, 6, "退出应用");
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int index = item.getItemId();
switch (index) {
case 200:
hbView.setColor(Color.RED);
break;
case 210:
hbView.setColor(Color.GREEN);
break;
case 220:
hbView.setColor(Color.BLUE);
break;
case 230:
hbView.setColor(Color.MAGENTA);
break;
case 240:
hbView.setColor(Color.YELLOW);
break;
case 250:
hbView.setColor(Color.BLACK);
break;
case 2:
dialog.show();
break;
case 300:
hbView.setStyle(HuaBanView.PEN);
break;
case 301:
hbView.setStyle(HuaBanView.PAIL);
break;
case 4:
hbView.clearScreen();
break;
case 5: { // 保存图片
Utils.saveBitmap(null, hbView.getBitmap(), "test");
// 定义消息
Message msg = new Message();
msg.what = 0x13;
// 发送消息 修改UI线程中的组件
changeUI.sendMessage(msg);
}
break;
case 6:
finish();
break;
}
return true;
}
// 在事件中调用该方法,可弹出Menu
@Override
public void openOptionsMenu() {
super.openOptionsMenu();
}
// 用于记录长按屏幕的时间
private void changeTime() {
// 初始化当前时间为0
currentTime = 0;
// 实例化一个timer对象,用于记录时间
timer = new Timer();
TimerTask task = new TimerTask() {
public void run() {
// 当前时间超过2秒则向UI发送消息
if (++currentTime > 1) {
Log.d("The Time:", currentTime + "");
Message message = new Message();
message.what = 0x12;
changeUI.sendMessage(message);
// 结束该timer
timer.cancel();
}
}
};
// 延迟每次延迟10 毫秒 隔1秒执行一次
timer.schedule(task, 0, 1000);
}
}
3,布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.example.drawingdemo.HuaBanView
android:id="@+id/huaBanView1"
android:background="#ffffffff"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
当然这只是简单的socket通信,同时也存在许多BUG,欢迎指正
如需下载源码,请点击此链接 http://download.csdn.net/detail/kzg_ip/9581482
时间: 2024-10-27 12:31:09