画板间即时通讯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

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值