画板间即时通讯demo

当我们做项目时肯定会用到即时通讯技术,当然网上第三方已经提供了许多即时通讯的接口,但是一味的使用别人的做好的产品是多么无趣,今天就做了一个关于多个画板间的即时通讯简单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

画板间即时通讯demo的相关文章

Indy10 即时通讯Demo

最近闲来无事,重新学习了Indy10,顺手写了一段即时通讯代码.与上次写的笔记有不同之处,但差别不大. 未研究过TCP打洞技术,所以下面的代码采用的是  客户端--服务器--客户端  模式,也就是服务器端转发消息的模式. 客户端模仿了QQ,可以在屏幕四周停靠自动隐藏 program Server; uses Forms, UntMain in 'UntMain.pas' {Form2}, Unit2 in 'Unit2.pas', Unit4 in 'Unit4.pas'; {$R *.res}

iOS 即时通讯SDK的集成,快速搭建自己的聊天系统

现在的外包项目需求变态的各种各样,今天要做社交,明天要加电商,后天又要加直播了,这些系统如果要自己开发,除非大公司技术和人力都够,不然短时间是几乎实现不了的.所以学会灵活利用市面上的各种SDK是灰常重要的技能. 最近继续在做的项目是一个气象救灾类APP,里面需要进行聊天的即时通讯模块.目前已经实现,效果如下: 一.市面上的即时通讯SDK 目前市面上的即时通讯SDK大概有:融云.网易云信.容联云等.非常多. 较为稳定.功能较全的应该是网易云信了,界面如下: 但是我们的应用需要的即时通讯是一个模块,

即时通讯(III)

即时通讯的开源库 目前及时通讯可以使用环信.柔云.腾讯云,这些都是基于TCP连接的,UI也是高度定制的,而且它们的技术也是比较成熟的. XMPP比较早,是开源的,但是坑也比较多.传输的数据是XML,造成了很多流量的雍余. 数据格式 Socket通讯报文是没有结束标识的,通讯报文保留前8个字节的,给我们字节扩展用的.可以利用这前八个字节做些事情.比如: 我们传一个图片,首先要知道它怎么结束,可以给他一个结束标识,如,"/n/n".第一个4字节我们可以把图片的总长度传过去,根据这个长度判断

java SSM框架 多数据源 代码生成器 websocket即时通讯 shiro redis 后台框架源码

获取[下载地址]   QQ: 313596790官网 http://www.fhadmin.org/A 调用摄像头拍照,自定义裁剪编辑头像,头像图片色度调节B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本,处理类,service等完整模块C 集成阿里巴巴数据库连接池druid  数据库连接池  阿里巴巴的 druid.Druid在监控.可扩展性.稳定性和性能方面都

XMPP即时通讯

XMPP:XMPP是基于XML的点对点通讯协议,The Extensible Messaging and Presence Protocol(可扩展通讯和表示协议). XMPP可用于服务类实时通讯,表示和需求响应服务中的XML数据元流失传输.XMPP以Jabber协议为基础,而Jabber是即时通讯中常用的开放式协议. 基本结构. XML是一个典型的C/S架构,而不是像大多数即时通讯软件一样,使用P2P客户端到客户端的架构,也就是说在大多数情况下,当两个客户端进行通讯时,他们的消息都是通过服务器

java代码编辑器 pdf文件预览 主流SSM 代码生成器 shrio redis websocket即时通讯

A代码编辑器,在线模版编辑,仿开发工具编辑器,pdf在线预览,文件转换编码 B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本,处理类,service等完整模块 C 集成阿里巴巴数据库连接池druid  数据库连接池  阿里巴巴的 druid.Druid在监控.可扩展性.稳定性和性能方面都有明显的优势 D 集成安全权限框架shiro   Shiro 是一个用 Jav

解读企业即时通讯

企业即时通信,简称EIM(Enterprise Instant Messaging),它是一种面向企业终端使用者的网络沟通工具服务,使用者可以通过安装了即时通信的终端机进行两人或多人之间的实时沟通.交流内容包括文字.界面.语音.视频及文件互发等. 市场分析 1.市场规模 根据调研机构调查数据显示,截止到2015年底,企业即时通讯市场规模达到10个亿美元左右.其中,中国企业即时通讯软件市场预计到2015年底达到4至6亿美元.根据某调研机构的预测2011年开始,即时通讯工具将取代声音.视频和文本,成

GGTalk ——C#开源即时通讯系统

http://www.cnblogs.com/justnow/ GGTalk ——C#开源即时通讯系统 下载中心 GGTalk(简称GG)是可在广域网部署运行的QQ高仿版,2013.8.7发布GG V1.0版本,至今最新是5.5版本,关于GG更详细的介绍,可以查看 可在广域网部署运行的QQ高仿版 -- GGTalk总览. GGMeeting是可在广域网部署运行的视频会议系统Demo,2015.05.11发布V1.0版本,关于GGMeeting更详细的介绍,可以查看 打造自己的视频会议系统 GGM

iOS 即时通讯,从入门到 “放弃”?

原文链接:http://www.jianshu.com/p/2dbb360886a8 本文会用实例的方式,将 iOS 各种 IM 的方案都简单的实现一遍.并且提供一些选型.实现细节以及优化的建议. —— 由宇朋Look分享 前言 本文会用实例的方式,将iOS各种IM的方案都简单的实现一遍.并且提供一些选型.实现细节以及优化的建议. 注:文中的所有的代码示例,在github中都有demo:iOS即时通讯,从入门到“放弃”?(demo)可以打开项目先预览效果,对照着进行阅读. 言归正传,首先我们来总