Windows的畫圖板相信很多人都用過,這次我們就來講講Java版本的簡易畫板的實現。
基本的思路是這樣的:畫板實現大致分三部分:一是畫板界面的實現,二是畫板的監聽以及畫圖的實現,三是畫板的重繪。(文章較長,但是代碼是逐步遞進的,可以按三部分分開來看,實現了當前部分再去看下一部分。)首先是畫板的界面實現,因為我沒有去找具體的圖標,界面上的所有組件都是Swing的自帶組件,所以界面略微有點簡陋,不過如果想要優化也簡單,把界面上的組件都改成自定義的圖標即可。界面實現后,就可以考慮給界面的組件加上監聽,不同的圖形根據具體情況添加不同的監聽方法。然后編寫事件處理類依據不同的圖形編寫畫圖的具體算法。一個簡易版本的畫圖板基本就差不多可以實現了。重繪這里先不提放到后面再講。
先來看看畫圖界面的實現:
實現畫圖界面需要用的API類主要有:FlowLayout,GridLayout,Color,Dimension,JButton,JFrame,JPanel。
定義Draw類,讓Draw類繼承JFrame。設置它的大小,標題,可見性等。需要注意的是這里如果添加的按鈕如果比較多,建議使用數組來完成按鈕的添加,因為如果直接一個一個的加按鈕,不僅會使得代碼量增大,而且不利於查找、添加和代碼的維護。為了使得界面不至於顯得那么簡陋,這里使用了幾個Jpanel,和不同的布局管理器。主窗體使用的是流式布局管理器,然后使用三個面板,分別承裝圖形按鈕,顏色按鈕和畫布。承裝圖形按鈕和顏色按鈕的面板都使用表格布局。然后界面的實現就基本完成了。

package Cbs; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; /** * Draw類,用於界面的初始化 * @author CBS */ @SuppressWarnings("serial") public class Draw extends JFrame { // 界面初始化方法
public void showUI() { setTitle("畫圖");//窗體名稱
setSize(1200, 900);//窗體大小
setDefaultCloseOperation(3); setLocationRelativeTo(null);//窗體居中 //流式布局左對齊
FlowLayout layout = new FlowLayout(FlowLayout.LEFT); setLayout(layout);//窗體使用流式布局管理器
this.setResizable(false);//窗體大小不變 //使用數組保存按鈕名
String buttonName[] = { "畫直線", "畫橢圓", "畫曲線", "多邊形", "橡皮擦", "拖動線","三角形", "畫球形", "筆刷", "噴槍", "色子", "立體矩形", "立體圓", "立體三角","迭代分形", "現代分形", "楓葉", "畫樹", "mandelbrot集", "L-System", "迭代畫線","迭代三角形", "謝爾賓斯基地毯", "畫字符", "清空", "吸管" ,"矩形","五角星","多線","字符"}; //用於保存圖形按鈕,使用網格布局
JPanel jp1=new JPanel(new GridLayout(15, 2,10,10)); jp1.setPreferredSize(new Dimension(200, 800)); //循環為按鈕面板添加按鈕
for (int i = 0; i < buttonName.length; i++) { JButton jbutton = new JButton(buttonName[i]); jp1.add(jbutton); } JPanel jp2=new JPanel();//畫布面板
jp2.setPreferredSize(new Dimension(970, 800)); jp2.setBackground(Color.WHITE); // 定義Color數組,用來存儲按鈕上要顯示的顏色信息
Color[] colorArray = { Color.BLUE, Color.GREEN, Color.RED, Color.BLACK,Color.ORANGE,Color.PINK,Color.CYAN, Color.MAGENTA,Color.DARK_GRAY,Color.GRAY, Color.LIGHT_GRAY,Color.YELLOW}; //用於保存顏色按鈕的面板
JPanel jp3=newJPanel(newGridLayout(1,colorArray.length,3,3)); // 循環遍歷colorArray數組,根據數組中的元素來實例化按鈕對象
for (int i = 0; i < colorArray.length; i++) { JButton button = new JButton(); button.setBackground(colorArray[i]); button.setPreferredSize(new Dimension(30, 30)); jp3.add(button); } //將面板添加到主窗體
this.add(jp1); this.add(jp2); this.add(jp3); //添加按鈕,作為當前顏色
JButton nowColor=new JButton(); nowColor.setPreferredSize(new Dimension(40,40)); nowColor.setBackground(Color.BLACK);//默認黑色
add(nowColor); //設置窗體的組件可見,如果為FALSE就看不到任何組件
setVisible(true); } }
這里還要一點要注意的地方,Jpanel面板的添加先后順序不要改變,這是根據流式布局算出來的面板大小,讀者可以自行更改調試。還有一個就是窗體的setSize方法只對窗體本身有效,如果要改變其他組件的大小要用setPreferredSize方法。這樣畫圖板的基本界面就實現。界面的按鈕和面板可以根據自身需要更改。
這是界面的大概樣子:

監聽的實現:
當然我們空有個界面並沒有什么用,我們需要的是點擊不同的按鈕能夠實現不同的功能。這里就需要用到事件的監聽機制了。實現監聽的主要API類有: ActionListener,MouseListener,MouseMotionListener。添加事件監聽的方法根以前的步驟是一樣的:確定事件源對象,編寫事件處理類,添加監聽方法。畫圖板中的事件源對象有兩種,一種是按鈕,另一種就是畫布的面板,按鈕使用的是ActionListener,而畫布面板因為負責的是繪圖,所以使用的是MouseListener和MouseMotionListener。為了實現事件的監聽,我們需要定義一個事件處理類DrawListener,該類實現了以上的三個事件接口。之后在重寫的方法中實現不同圖形的繪制。圖形的繪制都可以通過Graphics對象的方法來實現。但是這里有一個問題,如何分辨在Draw類中按下的是哪個按鈕呢?在定義Draw類的時候,我們使用了圖形按鈕和顏色按鈕,圖形類的按鈕在實例化對象的時候是使用了帶參數的構造方法的,也就是圖形按鈕都是有文字的,而顏色按鈕則沒有。依據這一點就可以很容易的區分不同的按鈕了。
DrawListener類:

package Cbs; import java.awt.Color; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import javax.swing.JButton; public class DrawListener implements ActionListener, MouseListener, MouseMotionListener { private Color color;//顏色屬性
private Graphics g;//畫筆屬性
private String str;//保存按鈕上的字符串,區分不同的按鈕
private int x1,y1,x2,y2;//(x1,y1),(x2,y2)分別為鼠標的按下和釋放時的坐標
private JButton nowColor;//當前顏色按鈕 //獲取Draw類的畫筆對象
public void setG(Graphics g) { this.g = g; } //獲取當前顏色按鈕
public void setNowColor(JButton nowColor) { this.nowColor = nowColor; } @Override //鼠標拖動的方法
public void mouseDragged(MouseEvent e) { //畫曲線的方法
if ("畫曲線".equals(str)) { int x, y; x = e.getX(); y = e.getY(); g.drawLine(x, y, x1, y1); x1 = x; y1 = y; } } @Override //鼠標移動方法
public void mouseMoved(MouseEvent e) { } @Override //鼠標單擊方法
public void mouseClicked(MouseEvent e) { } @Override //鼠標按下方法
public void mousePressed(MouseEvent e) { g.setColor(color);//改變畫筆的顏色
x1=e.getX();//獲取按下時鼠標的x坐標
y1=e.getY();//獲取按下時鼠標的y坐標
} @Override //鼠標釋放方法
public void mouseReleased(MouseEvent e) { x2=e.getX();//獲取釋放時鼠標的x坐標
y2=e.getY();//獲取釋放時鼠標的y坐標 //畫直線的方法
if ("畫直線".equals(str)) { g.drawLine(x1, y1, x2, y2); } } @Override //鼠標進入方法
public void mouseEntered(MouseEvent e) { } @Override //鼠標退出方法
public void mouseExited(MouseEvent e) { } @Override //處理按鈕上的鼠標點擊動作
public void actionPerformed(ActionEvent e) { //判斷是顏色按鈕還是圖形按鈕
if ("".equals(e.getActionCommand())) { JButton jb = (JButton) e.getSource(); color = jb.getBackground(); nowColor.setBackground(color);//處理當前顏色
} else { str = e.getActionCommand(); } } }
Draw類也要做一些修改,為按鈕和面板添加監聽:

1 package Cbs; 2
3 import java.awt.Color; 4 import java.awt.Dimension; 5 import java.awt.FlowLayout; 6 import java.awt.Graphics; 7 import java.awt.GridLayout; 8
9 import javax.swing.JButton; 10 import javax.swing.JFrame; 11 import javax.swing.JPanel; 12
13 /**
14 * Draw類,用於界面的初始化 15 * @author CBS 16 */
17 @SuppressWarnings("serial") 18 public class Draw extends JFrame { 19 private DrawListener dl; 20 private Graphics g; 21 // 界面初始化方法
22 public void showUI() { 23 setTitle("畫圖");//窗體名稱
24 setSize(1200, 900);//窗體大小
25 setDefaultCloseOperation(3); 26 setLocationRelativeTo(null);//窗體居中 27 //流式布局左對齊
28 FlowLayout layout = new FlowLayout(FlowLayout.LEFT); 29 setLayout(layout);//窗體使用流式布局管理器
30 this.setResizable(false);//窗體大小不變 31
32 //使用數組保存按鈕名
33 String buttonName[] = { "畫直線", "畫橢圓", "畫曲線", "多邊形", 34 "橡皮擦", "拖動線","三角形", "畫球形", "筆刷", "噴槍", 35 "色子", "立體矩形", "立體圓", "立體三角","迭代分形", 36 "現代分形", "楓葉", "畫樹", "mandelbrot集", "L-System", 37 "迭代畫線","迭代三角形", "謝爾賓斯基地毯", "畫字符", "清空", 38 "吸管" ,"矩形","五角星","多線","字符"}; 39 //用於保存圖形按鈕,使用網格布局
40 JPanel jp1=new JPanel(new GridLayout(15, 2,10,10)); 41 jp1.setPreferredSize(new Dimension(200, 800)); 42
43 //實例化DrawListener對象
44 dl=new DrawListener(); 45 //循環為按鈕面板添加按鈕
46 for (int i = 0; i < buttonName.length; i++) { 47 JButton jbutton = new JButton(buttonName[i]); 48 jbutton.addActionListener(dl);//為按鈕添加監聽
49 jp1.add(jbutton); 50 } 51
52 JPanel jp2=new JPanel();//畫布面板
53 jp2.setPreferredSize(new Dimension(970, 800)); 54 jp2.setBackground(Color.WHITE); 55
56
57 // 定義Color數組,用來存儲按鈕上要顯示的顏色信息
58 Color[] colorArray = { Color.BLUE, Color.GREEN, 59 Color.RED, Color.BLACK,Color.ORANGE,Color.PINK,Color.CYAN, 60 Color.MAGENTA,Color.DARK_GRAY,Color.GRAY,Color.LIGHT_GRAY, 61 Color.YELLOW}; 62 //用於保存顏色按鈕的面板
63 JPanel jp3=new JPanel(new GridLayout(1,colorArray.length,3,3)); 64 // 循環遍歷colorArray數組,根據數組中的元素來實例化按鈕對象
65 for (int i = 0; i < colorArray.length; i++) { 66 JButton button = new JButton(); 67 button.setBackground(colorArray[i]); 68 button.setPreferredSize(new Dimension(30, 30)); 69 button.addActionListener(dl);//為按鈕添加監聽
70 jp3.add(button); 71 } 72 //將面板添加到主窗體
73 this.add(jp1); 74 this.add(jp2); 75 this.add(jp3); 76 //添加按鈕,作為當前顏色
77 JButton nowColor=new JButton(); 78 nowColor.setPreferredSize(new Dimension(40,40)); 79 nowColor.setBackground(Color.BLACK);//默認黑色
80 add(nowColor); 81 //設置窗體的組件可見,如果為FALSE就看不到任何組件
82 setVisible(true); 83 //獲取畫筆對象
84 g=jp2.getGraphics(); 85 dl.setG(g); 86 dl.setNowColor(nowColor); 87 //為面板添加鼠標監聽,用於繪制圖形
88 jp2.addMouseListener(dl); 89 jp2.addMouseMotionListener(dl); 90 } 91
92 }
drawDrawListener里面只寫了畫直線和曲線的方法,讀者可以根據自己的需求添加,思路和方式都是一樣的。Draw類里面有些需要注意的地方在這里提一下:一個是畫筆g的獲取一定要在窗體的可見之后采取獲取,不然獲取的畫筆對象返回值會是null。二是要為圖形按鈕添加監聽,DrawListener的實例化需要在setVisible方法之前,所以不建議使用構造方法直接傳入g畫筆參數,我使用的是set方法。最后是注意一下使用哪個添加方法,按鈕使用的是addActionListener方法,畫板面板使用的是addMouseListener和addMouseMotionListener方法。使用畫板面板來獲取畫筆並給畫面面板添加監聽是為了讓繪圖的時候圖形不會跑出面板外,這里的畫筆和監聽都由主窗體獲得也是可以的,不過繪制時會出現線畫出面板的問題。
畫板的重繪:
到這里畫板的制作已經基本實現了,我們已經可以在上面繪制各種各樣的圖形了。但是細心的人可能會發現一個問題,那就是如果把窗體最小化之后再次打開,畫板上原本已經畫好的東西會全部都消失了。這樣子肯定是不行的,辛辛苦苦畫的“大作”怎么能說說沒就沒了呢。那么為什么會出現這樣的問題呢?要回答這個問題我們就需要先了解Java的繪圖機制。做畫圖板我們使用的是Swing組件,這套組件是基於原先的AWT組件開發,在繪制的時候會調用系統的畫圖函數,這就是為什么我們可以從面板或者是窗體中獲取畫筆對象的原因。這也就是說Java中你所能夠看到窗體,按鈕或者其它的所有組件其實都是畫出來。所以當我們點擊窗體使它最小化或者改變大小的時候,原來的畫的窗體就不能適應需要了,這時系統會調用JFrame的paint方法實現窗體的重繪,也就是再次畫了一個新的窗體,而JFrame的paint方法只對窗體已經添加的組件有效,我們自己繪制的東西並沒有寫在paint方法里面,所以窗體重繪之后,我們原先繪制的圖形也就消失了。要解決這個問題我們需要重寫父類的paint方法。但是這樣的話問題又來了,畫圖是在DrawListener類里面實現的,要怎么把它們弄到paint方法里面去呢?
當然方法可能有很多,這里我只介紹我所知道的:要把畫出來的圖形在paint方法中再次畫出來,就需要有東西來保存畫過的圖形。保存可以使用數組或者集合,這里推薦使用集合,可以很方便的實現添加,而不需要去考慮大小的問題。數組的實現也大同小異,這里就不多做介紹。確定了使用集合,那么集合內保存什么類型的數據呢?毫無疑問應該保存的是圖形的數據,但是集合使用泛型的話也只能保存同一種類型的數據,我們卻有那么多種圖形?這里就可以使用接口或者抽象類,我們只需要創建不同得圖形類,讓它繼承抽象類或者是實現接口。然后每畫一個圖形就實例化一個圖形的對象,存入集合中,最后在paint方法中遍歷集合,重新繪制圖形即可。
下面直接貼最終代碼(仍然只寫了直線和曲線),只是添加了幾行代碼,注意與前面比較。

//圖形接口 package Cbs; //圖形集合 public interface NetJavaShape { public abstract void draw(); } //直線類 package Cbs; import java.awt.Color; import java.awt.Graphics; import Cbs.NetJavaShape; public class ImpLine implements NetJavaShape{ Graphics g; int x1, y1,x2, y2; Color c; public ImpLine(Graphics g,int x1,int y1,int x2,int y2,Color c){ this.g=g; this.c=c; this.x1=x1; this.y1=y1; this.x2=x2; this.y2=y2; } public void draw() { g.setColor(c); g.drawLine(x1, y1, x2, y2); } } //DrawListener類 package Cbs; import java.awt.Color; import java.awt.Graphics; import java.util.List; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.ArrayList; import Cbs.NetJavaShape; import javax.swing.JButton; public class DrawListener implements ActionListener, MouseListener, MouseMotionListener { private Color color=Color.BLACK;//顏色屬性,初始值為黑色 private Graphics g;//畫筆屬性 private String str;//保存按鈕上的字符串,區分不同的按鈕 private int x1,y1,x2,y2;//(x1,y1),(x2,y2)分別為鼠標的按下和釋放時的坐標 private JButton nowColor;//當前顏色按鈕 //保存圖形對象的集合 private List<NetJavaShape> shapesArray = new ArrayList<NetJavaShape>(); //圖形 private NetJavaShape shape; //在draw類中獲取集合 public List<NetJavaShape> getShapesArray() { return shapesArray; } //獲取Draw類的畫筆對象 public void setG(Graphics g) { this.g = g; } //獲取當前顏色按鈕 public void setNowColor(JButton nowColor) { this.nowColor = nowColor; } @Override //鼠標拖動的方法 public void mouseDragged(MouseEvent e) { //畫曲線的方法 if ("畫曲線".equals(str)) { int x, y; x = e.getX(); y = e.getY(); //實例化對象,曲線也是直線畫的所以不同新建一個曲線類了 shape=new ImpLine(g,x,y,x1,y1,color); //調用畫圖方法 shape.draw(); //將圖形存入集合中 shapesArray.add(shape); // g.drawLine(x, y, x1, y1); x1 = x; y1 = y; } } @Override //鼠標移動方法 public void mouseMoved(MouseEvent e) { } @Override //鼠標單擊方法 public void mouseClicked(MouseEvent e) { } @Override //鼠標按下方法 public void mousePressed(MouseEvent e) { g.setColor(color);//改變畫筆的顏色 x1=e.getX();//獲取按下時鼠標的x坐標 y1=e.getY();//獲取按下時鼠標的y坐標 } @Override //鼠標釋放方法 public void mouseReleased(MouseEvent e) { x2=e.getX();//獲取釋放時鼠標的x坐標 y2=e.getY();//獲取釋放時鼠標的y坐標 //畫直線的方法 if ("畫直線".equals(str)) { //實例化對象, shape=new ImpLine(g,x1,y1,x2,y2,color); //調用畫圖方法 shape.draw(); //將圖形存入集合中 shapesArray.add(shape); // g.drawLine(x1, y1, x2, y2); } } @Override //鼠標進入方法 public void mouseEntered(MouseEvent e) { } @Override //鼠標退出方法 public void mouseExited(MouseEvent e) { } @Override //處理按鈕上的鼠標點擊動作 public void actionPerformed(ActionEvent e) { if ("".equals(e.getActionCommand())) { JButton jb = (JButton) e.getSource(); color = jb.getBackground(); nowColor.setBackground(color);//處理當前顏色 } else { str = e.getActionCommand(); } } } //draw類 package Cbs; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.GridLayout; import java.util.ArrayList; import java.util.List; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; /** * Draw類,用於界面的初始化 * @author CBS */ @SuppressWarnings("serial") public class Draw extends JFrame { private DrawListener dl; private Graphics g; //保存圖形對象的集合 private List<NetJavaShape> shapesArray = new ArrayList<NetJavaShape>(); // 界面初始化方法 public void showUI() { setTitle("畫圖");//窗體名稱 setSize(1200, 900);//窗體大小 setDefaultCloseOperation(3); setLocationRelativeTo(null);//窗體居中 FlowLayout layout = new FlowLayout(FlowLayout.LEFT);//流式布局左對齊 setLayout(layout);//窗體使用流式布局管理器 this.setResizable(false);//窗體大小不變 //使用數組保存按鈕名 String buttonName[] = { "畫直線", "畫橢圓", "畫曲線", "多邊形", "橡皮擦", "拖動線","三角形", "畫球形", "筆刷", "噴槍", "色子", "立體矩形", "立體圓", "立體三角","迭代分形", "現代分形", "楓葉", "畫樹", "mandelbrot集", "L-System", "迭代畫線","迭代三角形", "謝爾賓斯基地毯", "畫字符", "清空","吸管" ,"矩形","五角星","多線","字符"}; JPanel jp1=new JPanel(new GridLayout(15, 2,10,10));//用於保存圖形按鈕,使用網格布局 jp1.setPreferredSize(new Dimension(200, 800)); //實例化DrawListener對象 dl=new DrawListener(); //循環為按鈕面板添加按鈕 for (int i = 0; i < buttonName.length; i++) { JButton jbutton = new JButton(buttonName[i]); jbutton.addActionListener(dl);//為按鈕添加監聽 jp1.add(jbutton); } JPanel jp2=new JPanel();//畫布面板 jp2.setPreferredSize(new Dimension(970, 800)); jp2.setBackground(Color.WHITE); // 定義Color數組,用來存儲按鈕上要顯示的顏色信息 Color[] colorArray = { Color.BLUE, Color.GREEN, Color.RED, Color.BLACK,Color.ORANGE,Color.PINK,Color.CYAN,Color.MAGENTA,Color.DARK_GRAY,Color.GRAY,Color.LIGHT_GRAY,Color.YELLOW}; //用於保存顏色按鈕的面板 JPanel jp3=new JPanel(new GridLayout(1,colorArray.length,3,3)); // 循環遍歷colorArray數組,根據數組中的元素來實例化按鈕對象 for (int i = 0; i < colorArray.length; i++) { JButton button = new JButton(); button.setBackground(colorArray[i]); button.setPreferredSize(new Dimension(30, 30)); button.addActionListener(dl);//為按鈕添加監聽 jp3.add(button); } //將面板添加到主窗體 this.add(jp1); this.add(jp2); this.add(jp3); //添加按鈕,作為當前顏色 JButton nowColor=new JButton(); nowColor.setPreferredSize(new Dimension(40,40)); nowColor.setBackground(Color.BLACK);//默認黑色 add(nowColor); //設置窗體的組件可見,如果為FALSE就看不到任何組件 setVisible(true); //獲取畫筆對象 g=jp2.getGraphics(); dl.setG(g); dl.setNowColor(nowColor); //獲取保存的集合 shapesArray=dl.getShapesArray(); //為面板添加鼠標監聽,用於繪制圖形 jp2.addMouseListener(dl); jp2.addMouseMotionListener(dl); } @Override //重寫paint方法 public void paint(Graphics g) { //調用父類的paint方法,繪制界面上的組件 super.paint(g); //foreach遍歷集合 for (NetJavaShape l : shapesArray) { l.draw(); } } }
這里使用集合添加圖形實現畫板的重繪時,我是每實現一個圖形就會新建一個類來保存圖形的信息,這樣圖形類就會有很多。如果不想創建那么多的圖形類可以把它們都放到一個類里面,設置一個type的參數,賦上按鈕的名稱。然后在draw方法中依據這個值判斷是什么圖形實現不同的畫圖方法。這樣畫板的所有功能基本就實現了,畫板的項目也就到這里。
總結:
畫圖板的制作重要用到了Swing的組件,事件監聽機制,Graphics繪圖和畫板的重繪以及集合的使用,抽象類或者是接口作為規范圖形類的作用。