通过这篇文章的标题可以知道,本篇博文,有两部分的内容,第一部分是用Java的组件做一个简易的画图板,而第二部分就是将第一部分画图板的内容通过BMP格式保存到本地,并且能够让市场上的图片浏览器识别出来并成功读取。那么,我们先看看一个简易的画图板要怎么实现吧。
一、简易画图板
(一)需求分析
首先,画图板应该有一个主体部分,用来绘图的;其次,在画图板的左侧应该有画笔选择工具,也就是可以用来画矩形,圆,铅笔工具和橡皮工具等;然后,在画图板的底侧应该有颜色选择器;最后,画图板应该有菜单项,可以打开已有的bmp格式图片或保存自己绘制的图片。具体的效果图如下所示:
(二)画图板的代码编写——创建一个Java类,为DrawUI,在其不带参数的构造方法中实现以下几部分
1.创建窗体
// 创建窗体 JFrame jf = new JFrame(); jf.setTitle("XP画图板"); jf.setSize(600, 500); jf.setDefaultCloseOperation(3); // 指定布局为边框布局 BorderLayout layout = new BorderLayout(); jf.setLayout(layout);
2.编写左侧画笔工具栏;
// 设置布局左部分的界面 JPanel left = new JPanel(); // 设置大小 Dimension leftDim = new Dimension(100, 1); left.setPreferredSize(leftDim);
3.编写中间的画板部分;
JPanel center = new JPanel(); center.setBackground(Color.GRAY);
4.编写底部颜色选择器栏;
JPanel foot = new JPanel(); Dimension footDim = new Dimension(1, 100); foot.setPreferredSize(footDim); // 指定放的位置[东西南北中] jf.add(left, BorderLayout.WEST); jf.add(center, BorderLayout.CENTER); jf.add(foot, BorderLayout.SOUTH);
(三)第(二)部分完成的是整体框架的代码编写,接下来就是在每个框架里面添加具体的组件和内容了。
由于左侧的画笔工具栏用来了一些图片,我已经打包上传至附件的img.rar了。这部分同样是在DrawUI.java的无参构造方法中实现。
1.为左侧画笔工具栏添加组件和内容
// **************给左边面板添加形状选择工具***************************// // 按钮组对象,给单选按钮进行逻辑分组 ButtonGroup group = new ButtonGroup(); String[] strs = { "0", "1", "easer", "3", "4", "5", "pencial", "brush", "spray", "9", "line", "11", "rect", "polygon", "oval", "roundrect" }; for (int i = 0; i < 16; i++) { JRadioButton btn1 = new JRadioButton(); left.add(btn1);// 加在左边面板 group.add(btn1);// 添加分组 btn1.setActionCommand(strs[i]);// 设置唯一标识 btn1.setSelected(true);// 设置默认选中 // 设置按钮的图片 // 默认时候 ImageIcon defaultIcon = new ImageIcon("img/draw" + i + ".jpg"); btn1.setIcon(defaultIcon); // 鼠标放上去 ImageIcon rolloverIcon = new ImageIcon("img/draw" + i + "-1.jpg"); btn1.setRolloverIcon(rolloverIcon); // 鼠标点击 ImageIcon pressedIcon = new ImageIcon("img/draw" + i + "-2.jpg"); btn1.setPressedIcon(pressedIcon); // 鼠标选中 ImageIcon selectedIcon = new ImageIcon("img/draw" + i + "-3.jpg"); btn1.setSelectedIcon(selectedIcon); }
2.自定义个JPanel类,用于重写其paint方法
/** * 继承JPanel的自定义画板类,用于重写绘图方法 * * @author Bill56 * */ public class MyPanel extends JPanel { @Override public void paint(Graphics g) { // TODO Auto-generated method stub super.paint(g); // 当面板发生改变的时候, // 后面会添加具体的重绘代码 } }
3.为中间的画图画板块添加组件和内容
// **************给中间面板添加绘图面板***************************// MyPanel drawPanel = new MyPanel(); Dimension drawDim = new Dimension(400, 300); drawPanel.setPreferredSize(drawDim); drawPanel.setBackground(Color.WHITE); // 将画板添加到画图板中 // 面板默认的布局是流式布局 // 指定中间面板的布局为流式布局左对齐 FlowLayout fl = new FlowLayout(FlowLayout.LEFT); center.setLayout(fl); center.add(drawPanel);
4.为底部的颜色选择器添加组件和内容
这里的代码中用到了自定义的ColorAdapter监听器的适配类,后面会做介绍。大家在复制过去如果监听器那行报错,可以先注释掉。
// **************给底部面板添加颜色选择工具***************************// JLabel frontLabel = new JLabel(); JLabel backLabel = new JLabel(); // 使用绝对定位布局 foot.setLayout(null); // 定义frontLabel的位置和大小 frontLabel.setBounds(50, 20, 40, 60); backLabel.setBounds(110, 20, 40, 60); // 设置背景颜色 frontLabel.setBackground(Color.BLACK); backLabel.setBackground(Color.WHITE); // 是否允许背景色显示出来 frontLabel.setOpaque(true); backLabel.setOpaque(true); // 将前景色和背景色标签加入到底部颜色选择板 foot.add(frontLabel); foot.add(backLabel); // 添加颜色选择数组 Color[] cs = { Color.BLACK, Color.GRAY, new Color(128, 0, 0), Color.RED, new Color(255, 128, 0), Color.YELLOW, Color.GREEN, new Color(0, 128, 255), Color.BLUE, Color.MAGENTA, new Color(255, 128, 128), new Color(128, 0, 125), new Color(128, 255, 0), new Color(128, 0, 255), new Color(0, 128, 120), new Color(128, 0, 20), new Color(128, 128, 0), new Color(100, 128, 255), new Color(128, 30, 50), new Color(128, 110, 50) }; // 将颜色数组添加至底部的颜色选择板 for (int i = 0; i < cs.length; i++) { JLabel colorLabel = new JLabel(); // 设置颜色选择的鼠标监听事件 colorLabel.addMouseListener(new ColorAdapter(frontLabel, backLabel)); // 放置在第一排 if (i < 10) { colorLabel.setBounds(160 + 30 * i, 20, 25, 25); } else { colorLabel.setBounds(160 + 30 * (i - 10), 50, 25, 25); } colorLabel.setBackground(cs[i]); colorLabel.setOpaque(true); foot.add(colorLabel); } // 在底部添加一个颜色版按钮 JButton jb = new JButton(" 颜色板 "); jb.setBounds(470, 30, 90, 40); jb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Color newcolor = JColorChooser.showDialog(drawPanel, "调色板", drawPanel.getBackground()); if (newcolor != null) { // JFrame类里面只能由内容面板来设定颜色 frontLabel.setBackground(newcolor); } } }); foot.add(jb);
5.为画图板添加菜单
MenuListener为自定义的监听器,如果报错,可以先将其注释掉,后面会做介绍。
// *******************菜单栏的实现****************************************// MenuListener mlis = new MenuListener(drawPanel); // 添加菜单 JMenuBar bar = new JMenuBar(); String[] menus = { "文件", "编辑", "查看", "图像", "关于" }; String[][] items = { { "新建", "打开", "保存", "关闭" }, { "撤销", "复制", "粘贴" }, {}, {}, {} }; for (int i = 0; i < menus.length; i++) { JMenu menu = new JMenu(menus[i]); bar.add(menu); for (int j = 0; j < items[i].length; j++) { JMenuItem item1 = new JMenuItem(items[i][j]); // 设置动作命令 item1.setActionCommand(items[i][j]); menu.add(item1); item1.addActionListener(mlis); } } // 设置窗体的菜单条 jf.setJMenuBar(bar);
6.最后在添加一行让窗体显示的代码:
// 显示窗体 jf.setVisible(true);
7.让DrawUI.java为程序的主类,为其添加一个main方法,然后再main方法中创建窗体,运行看,是否是我们所需要的界面效果。
public static void main(String[] args) { new DrawUI(); }
(四)实现画图功能
1.颜色选择监听器的代码编写
刚刚在第(三)部分说到的ColorAdapter就是一个继承于MouseAdapter的监听器类,主要的作用是,用户在底部选择一个颜色的时候,左键选择的是前景色,右键选择的是背景色,并且会在底部的前景色和背景色标签中通知用户(底部最左边的两个,分别表示当前的前景色和当前的背景色)。完成这部分之后,可以将之前在DrawUI构造方法中注释掉的ColorAdapter部分取消注释。
/** * 采用继承鼠标事件的适配器类才重写需要的方法 * * @author Bill56 * */ public class ColorAdapter extends MouseAdapter { // 前景色标签 private JLabel frontLabel; // 背景色标签 private JLabel backLabel; /** * 构造方法 * * @param fLabel * 画板的前景色标签对象 * @param bLabel * 画板的北京哪个色标签对象 */ public ColorAdapter(JLabel fLabel, JLabel bLabel) { frontLabel = fLabel; backLabel = bLabel; } @Override public void mouseReleased(MouseEvent e) { super.mouseReleased(e); // 获得事件源对象:发生事件的组件 // 由于监听器是加在Jlabel上的,所以事件源对象一定是JLabel JLabel label = (JLabel) e.getSource(); // 获得背景颜色 Color c = label.getBackground(); // 获得鼠标是左键还是右键 int num = e.getButton(); // 如果是左键,就改变其前景色 if (num == 1) { frontLabel.setBackground(c); } else if (num == 3) { // 如果是右键,就改变其背景色 backLabel.setBackground(c); } } }
2.菜单选择监听器的编写
刚刚在第(三)部分说到的MenuListener就是一个继承于ActionListener的监听器类,主要作用是,选择不同的菜单项后会执行不同的命令。具体是要实现文件菜单下的新建、打开、保存和退出这四个菜单项命令。完成这部分之后,可以将之前在DrawUI构造方法中注释掉的MenuListener部分取消注释。
/** * 菜单事件监听类 * * @author Bill56 * */ public class MenuListener implements ActionListener { private MyPanel panel; public MenuListener(MyPanel panel) { this.panel = panel; } public void actionPerformed(ActionEvent e) { // 获得被点击的组件的动作命令 String command = e.getActionCommand(); JFileChooser chooser = new JFileChooser(); if (command.equals("保存")) { // 后面会添加保存成bmp格式文件的代码 } else if (command.equals("打开")) { // 后面会添加读取bmp格式文件的代码 } else if (command.equals("新建")) { // 后面会添加新建画板的代码 } else if (command.equals("关闭")) { System.exit(0); } } }
3.为画图添加事件监听器
上述已经完成了为颜色选择和菜单选择的事件监听,但是当我们选择画笔工具和颜色之后,在画板点击滑动并没有任何效果,是因为我们没有为其实添加鼠标点击,移动,释放等监听方法,所以我们新建一个DrawListener类,实现一些列所需要的监听方法。
/** * 画图的鼠标事件监听类 * * @author Bill56 * */ public class DrawListener extends MouseAdapter { // 保存图像坐标的二维数组 public static int[][] array = null; private int x1, y1, x2, y2, x3, y3; private Graphics g; private JPanel drawJPanel; private ButtonGroup group; private String type = "line"; private Color frontColor = Color.BLACK; private Color backColor = Color.WHITE; private JLabel frontLabel; private JLabel backLabel; public static boolean flag; // 机器人类对象 Robot robot = null; /** * 构造方法 * * @param dp * 监听的面板 * @param bg * 被选中的画笔工具 * @param fLabel * 前景色 * @param bLabel * 背景色 */ public DrawListener(JPanel dp, ButtonGroup bg, JLabel fLabel, JLabel bLabel) { drawJPanel = dp; group = bg; frontLabel = fLabel; backLabel = bLabel; // 获得面板的大小 Dimension dim = drawJPanel.getPreferredSize(); // 根据面板大小创建保存面板数据的二维数组 array = new int[dim.height][dim.width]; // 保存初始颜色 for (int i = 0; i < array.length; i++) { for (int j = 0; j < array[i].length; j++) { array[i][j] = Color.WHITE.getRGB(); } } try { robot = new Robot(); } catch (AWTException e) { e.printStackTrace(); } } // 鼠标按下 public void mousePressed(MouseEvent e) { // 鼠标按下准备绘制图形的时候先获取能绘制的区域[画布] // 获取drawPanel在屏幕上占据的区域,作为可以改变颜色的区域 g = drawJPanel.getGraphics(); frontColor = frontLabel.getBackground(); backColor = backLabel.getBackground(); int num = e.getButton(); // 获得鼠标点击的是左键还是右键 if (num == 1) { g.setColor(frontColor); } else if (num == 3) g.setColor(backColor); // 鼠标按下准备绘制的时候来确定要绘制的图形 // 获得被选中的按钮模型 ButtonModel model = group.getSelection(); // 获得动作命令[每一个按钮的唯一标识] type = model.getActionCommand(); x1 = e.getX(); y1 = e.getY(); }; // 释放 public void mouseReleased(MouseEvent e) { x2 = e.getX(); y2 = e.getY(); int x = x1 < x2 ? x1 : x2;// 取小的 int y = y1 < y2 ? y1 : y2; int width = x1 < x2 ? x2 - x1 : x1 - x2; int height = y1 < y2 ? y2 - y1 : y1 - y2; int arcWidth = 10; int arcHeight = 10; // 直线,矩形,椭圆的绘制 if (type.equals("line")) {// 直线 g.drawLine(x1, y1, x2, y2); } else if (type.equals("rect")) {// 矩形 g.drawRect(x, y, width, height); } else if (type.equals("oval")) {// 椭圆 g.drawOval(x, y, width, height); } else if (type.equals("roundrect")) {// 圆角矩形 g.drawRoundRect(x, y, width, height, arcWidth, arcHeight); } else if (type.equals("polygon")) {// 多边形 if (flag == false) { g.drawLine(x1, y1, x2, y2); x1 = x2; y1 = y2; flag = true; } else if (flag == true) { g.drawLine(x3, y3, x1, y1); x3 = x1; y3 = y1; } } // 刷子 else if (type.equals("brush")) { Graphics2D g2d = (Graphics2D) g;// 为Graphics的一个子类 g2d.setStroke(new BasicStroke(8));// 设置大小 g2d.drawLine(x1, y1, x2, y2); g2d.setStroke(new BasicStroke(1)); } // 橡皮檫 else if (type.equals("easer")) { g.setColor(Color.WHITE); Graphics2D g2d = (Graphics2D) g; g2d.setStroke(new BasicStroke(8)); g2d.drawLine(x1, y1, x2, y2); g2d.setStroke(new BasicStroke(1)); } // 铅笔 else if (type.equals("pencial")) { g.drawLine(x1, y1, x2, y2); } // 喷枪 else if (type.equals("spray")) { g.drawLine(x1, y1, x2, y2); } // 释放一次,就从新保存一次 // 1.截屏 Point point = drawJPanel.getLocationOnScreen(); Dimension dim = drawJPanel.getPreferredSize(); Rectangle screenRect = new Rectangle(point, dim); BufferedImage bufferImg = robot.createScreenCapture(screenRect); // 根据面板大小调整数组大小 array = new int[dim.height][dim.width]; for (int i = 0; i < array.length; i++) { for (int j = 0; j < array[i].length; j++) { array[i][j] = bufferImg.getRGB(j, i); } } }; public void mouseDragged(MouseEvent e) { x2 = e.getX(); y2 = e.getY(); // 绘制图形 // 刷子 if (type.equals("brush")) { Graphics2D g2d = (Graphics2D) g;// 为Graphics的一个子类 g2d.setStroke(new BasicStroke(8));// 设置大小 g2d.drawLine(x1, y1, x2, y2); // 将末位置变成下一次的初始位置 x1 = x2; y1 = y2; g2d.setStroke(new BasicStroke(1)); } // 橡皮擦 else if (type.equals("easer")) { g.setColor(Color.WHITE); Graphics2D g2d = (Graphics2D) g; g2d.setStroke(new BasicStroke(8)); g2d.drawLine(x1, y1, x2, y2); x1 = x2; y1 = y2; g2d.setStroke(new BasicStroke(1)); } // 铅笔 else if (type.equals("pencial")) { g.drawLine(x1, y1, x2, y2); x1 = x2; y1 = y2; } // 喷枪 else if (type.equals("spray")) { Random random = new Random(); for (int i = 0; i < 30; i++) { int x = random.nextInt(10); int y = random.nextInt(10); g.drawLine(x1 + x, y1 + y, x2 + x, y2 + y); x1 = x2; y1 = y2; } } } }
4.重写MyPanel的paint方法。
@Override public void paint(Graphics g) { // TODO Auto-generated method stub super.paint(g); // 当面板发生改变的时候, // 将数组中保存的点的颜色取出来,重新绘制 // System.out.println("变了"); for (int i = 0; i < DrawListener.array.length; i++) { for (int j = 0; j < DrawListener.array[i].length; j++) { Color color = new Color(DrawListener.array[i][j]); g.setColor(color); g.drawLine(j, i, j, i); } } }
5.最后,还需要在DrawUI的无参构造方法中为画板注册这个监听事件类,在jf.setVisibile()后面添加即可。
// 调用创建监听器的方法,并传入参数 DrawListener dlis = new DrawListener(drawPanel, group, frontLabel, backLabel); drawPanel.addMouseListener(dlis); drawPanel.addMouseMotionListener(dlis);
到这里,已经完成了画图板的画画功能了。
如果将画图板的功能实现了,那么接下来就可以往后看怎么实现BMP图片的保存和读取了。
谢谢您的关注和阅读,文章不当之处还请您不吝赐教~~~