首先创建一个画板类继承容器类,这样可以在画板类中重写容器的paint方法。
public class DrawFrame extends JPanel
再创建一个类写监听器的程序,以及一个类来写画板的内容对象参数保存的方法(可以在最小化以及伸缩窗口的时候使画面内容得到恢复)
public class DrawMouse implements MouseListener, ActionListener,MouseMotionListener
public class Shape
我们可以看到我们会用到鼠标监听器,动作监听器,以及鼠标动作监听器。
我们先简单地介绍一下他们的作用:
1.鼠标监听器可以对于点击,按下,释放等事件做出响应,响应具体内容由程序员自己定义,发生相应的行为时即会触发对应方法,由此可见,监听器的作用很大,可以实现无限的可能。
2.动作监听器我们目前用于响应对按钮的点击事件,读取按钮上的名称或者颜色信息来实现相应的方法。
3鼠标动作监听器可以实现对鼠标进入界面,离开界面,界面内拖拽等功能的实现,极大丰富了界面特殊功能的实现。
接下来进入主程序,主程序很简单,只是一个窗体构造方法的调用。
public static void main(String[] args) {
DrawFrame frame = new DrawFrame();
frame.showUI();
}
javax.swing.JFrame jf = new javax.swing.JFrame();
jf.setSize(800, 800);
jf.getContentPane().setBackground(Color.WHITE);//设置背景色
jf.setTitle("画板1.0");
jf.setDefaultCloseOperation(3);
// 设置居中显示
jf.setLocationRelativeTo(null)
DrawMouse mouse = new DrawMouse();
jf.setLayout(new BorderLayout());
JPanel jp1= new JPanel();
jp1.setBackground(Color.green);
jf.add(jp1,BorderLayout.NORTH);
在showUI里先是对一些基本组件的添加和创建,这里就不再一一赘述。值得一提的是其中容器的添加以及边框布局的规则。jf就是一个巨大的容器对象,一般来说,如果我们不定义流式布局,便会默认边框布局。边框布局可以把区域划分为5个部分,north,south,center,east,west。我们把按钮放在了上方,把画板主题部分放在中间,这样两个区域便互不干扰,是两个独立的容器。这些对象的父类便是JPanel类。
this.setBackground(Color.WHITE);
jf.add(this, BorderLayout.CENTER);
使用this即可调用,默认以容器类来创建对象(容器可以创建多个对象)。
String[] shape ={"直线","矩形","三角形","椭圆","任意边形","曲线","橡皮擦","迭代图像","递归"};
for(int i=0;i<shape.length;i++){
JButton jbu = new JButton(shape[i]);
jp1.add(jbu);
jbu.addActionListener(mouse);
}
Color[] color = {Color.RED,Color.BLUE,Color.BLACK};
for(int i=0;i<color.length;i++){
JButton jbu = new JButton();
jbu.setBackground(color[i]);
jbu.setPreferredSize(new Dimension(30, 30));
jp1.add(jbu);
jbu.addActionListener(mouse);
}
循环创建按钮可以在多按钮需求的时候剩下不少力气。循环创建按钮并为其添加动作监听器。
jf.setVisible(true);
//获取画笔对象:图形画在那个组件上,画笔就从该组件上获取
//从窗体上获取画笔对象
Graphics g = this.getGraphics();
//给窗体添加鼠标监听器方法
this.addMouseListener(mouse);
this.addMouseMotionListener(mouse);
mouse.setGr(g);
mouse.setArrayShape(arrayShape);
窗体可视化并获取画笔对象后我们添加鼠标监听器。这里用到了传对象的方法,来使得在监听器的类中可以使用画笔和对象数组。对象数组的创建是为了保存每一个图画信息的坐标及颜色信息,便于复原。
private Graphics gr;
private int x1, y1, x2, y2, x3, y3;
private double x;
private double y;
private int xf,yf,xl,yl,x0,y0;
private String name;
private int flag=0;
private int flagq=0;
private int step=1;
private int flag_line=0;
private Color color = Color.black;
private double a=-1.2;
private double b=1.6;
private double c=-1;
private double d=-1.5;
private int index=0;
private Shape[] arrayShape;
在监听器里定义了很多属性,这些属性在编程实现特殊功能的时候十分有用,可作为开关,可用于传递对象,可作为方法内的参数来调用等,这里不一一赘述。
public void mouseClicked(MouseEvent e) {
if ("任意边形".equals(name)){
if(flag==0)
{
x0 = e.getX();
y0 = e.getY();
xf = e.getX();
yf = e.getY();
flag=1;
}
else
{
xl = e.getX();
yl = e.getY();
gr.drawLine(xf, yf, xl, yl);
set_shape(xf, yf, xl, yl,"直线",color);
xf=xl;
yf=yl;
if(e.getClickCount()==2)
{
flag=0;
gr.drawLine(x0, y0, xl, yl);
set_shape(x0, y0, xl, yl,"直线",color);
}
}
}
if("三角形".equals(name)){
System.out.println(step);
switch(step)
{
case 1:
x1=e.getX();
y1=e.getY();
step++;
break;
case 2:
x2=e.getX();
y2=e.getY();
gr.drawLine(x1, y1, x2, y2);
step++;
break;
case 3:
x3=e.getX();
y3=e.getY();
set_shape(x1,y1,x2,y2,"直线",color);
set_shape(x2,y2,x3,y3,"直线",color);
set_shape(x1,y1,x3,y3,"直线",color);
step=1;
gr.drawLine(x2, y2, x3, y3);
gr.drawLine(x1, y1, x3, y3);
break;
default:;
}
}
if("迭代图像".equals(name))
{
x=e.getX();
y=e.getY();
iterate(x,y);
//set_shape((int)x, (int)y, 0, 0,"迭代图像",color);
}
if("递归".equals(name))
{
int xa=1+(int)(Math.random()*800);
int ya=1+(int)(Math.random()*800);
int xb=1+(int)(Math.random()*800);
int yb=1+(int)(Math.random()*800);
int xc=1+(int)(Math.random()*800);
int yc=1+(int)(Math.random()*800);
int xp=1+(int)(Math.random()*800);
int yp=1+(int)(Math.random()*800);
int turn=1;
System.out.println("xa"+xa+"ya"+ya+"xb"+xb+"yb"+yb+"xc"+xc+"yc"+yc+"xp"+xp+"yp"+yp);
for(int i=0;i<10000;i++)
{
turn=1+(int)(Math.random()*3);
switch(turn)
{
case 1:
xp=(xa+xp)/2;
yp=(ya+yp)/2;
gr.drawLine(xp, yp, xp, yp);
break;
case 2:
xp=(xb+xp)/2;
yp=(yb+yp)/2;
gr.drawLine(xp, yp, xp, yp);
break;
case 3:
xp=(xc+xp)/2;
yp=(yc+yp)/2;
gr.drawLine(xp, yp, xp, yp);
break ;
}
}
}
}
在click事件里获取坐标值,再判断当前name类型,实现画三角形和画任意边形等操作。click 双击还可自动补全缺线,结束多边形的绘制。因为鼠标事件的实现非常复杂,且与按下,释放联系紧密,单独介绍不便于一一解释,我们就提一些有趣的想法,然后剩下的就留读者思考和设计创造了。
public void mousePressed(MouseEvent e) {
System.out.println("按下");
if("矩形".equals(name)||"椭圆".equals(name))
{
x1 = e.getX();
y1 = e.getY();
}
//System.out.println(name);
if ("曲线".equals(name))
{
flagq=1;
//System.out.println(flagq);
xf=e.getX();
yf=e.getY();
}
if("直线".equals(name))
{
x0=e.getX();
y0=e.getY();
x1=e.getX();
y1=e.getY();
flag_line=1;
}
}
public void mouseReleased(MouseEvent e) {
System.out.println("释放");
if("矩形".equals(name)||"椭圆".equals(name))
{
x2 = e.getX();
y2 = e.getY();
}
/*if ("直线".equals(name)) {
// 画线
gr.drawLine(x1, y1, x2, y2);
}*/
if ("矩形".equals(name)) {
gr.drawRect(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
set_shape(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2),"矩形",color);
}
if("椭圆".equals(name)){
gr.drawOval(Math.min(x1, x2),Math.min(y1, y2), Math.abs(x1-x2),Math.abs(y1-y2));
set_shape(Math.min(x1, x2),Math.min(y1, y2), Math.abs(x1-x2),Math.abs(y1-y2),"椭圆",color);
}
if ("曲线".equals(name))
{
flagq=0;
System.out.println(flagq);
}
if("直线".equals(name))
{
flag_line=0;
set_shape(x0,y0,x2,y2,"直线",color);
}
}
按下和释放可以获取两个坐标,可以轻松地画出直线,矩形等形状,三角形和多边形的实现用到了click事件(这里关于x1,y1,x2,y2,x3,y3变量的使用十分巧妙,可以作为前一个点,和后一个点来理解,实现连续画多线)。
public void actionPerformed(ActionEvent e) {
if("".equals(e.getActionCommand())){
//获取当前事件源
JButton jbu = (JButton)e.getSource();
//获取按钮的背景色
color = jbu.getBackground();
//设置画笔颜色
gr.setColor(color);
}else{
// 获取按钮内容
name = e.getActionCommand();
}
System.out.println("name = " + name);
}
按钮功能的实现代码贴出。
public void mouseDragged(MouseEvent e)
{
//System.out.print("x坐标:"+e.getX()+" y坐标:"+e.getY());
if (flagq==1)
{
xl=e.getX();
yl=e.getY();
gr.drawLine(xf, yf, xl, yl);
set_shape(xf, yf, xl, yl,"直线",color);
xf=xl;
yf=yl;
}
if("橡皮擦".equals(name))
{
x1=e.getX();
y1=e.getY();
gr.setColor(Color.white);
gr.fillRect(x1-5, y1-5, 10, 10);
gr.setColor(color);
//gr.clearRect(x1-5, y1-5, 10, 10);
set_shape(x1-5, y1-5, 10, 10,"橡皮擦",color);
}
if("直线".equals(name)&&flag_line==1)
{
x2=e.getX();
y2=e.getY();
gr.setColor(Color.white);
gr.drawLine(x0, y0, x1, y1);
gr.setColor(color);
gr.drawLine(x0, y0, x2, y2);
x1=x2;
y1=y2;
}
}
这段代码有两个地方很有意思,一个是直线的绘制。我最初使用的是press和release,这样在鼠标拖动的过程中直线是不可见的,我们把其在鼠标拖动事件中实现,及在按下的时候保存坐标初值,然后不断获取鼠标位置,画新的线,用背景色覆盖原先的线,实现直线的实时显示。还有一个就是画曲线,及不断保存前后点的坐标,画很多短线,实现曲线的效果。
private Shape[] arrayShape=new Shape[9000];
public void set_shape(int x1,int y1,int x2,int y2,String name,Color color)
{
Shape shape=new Shape(x1,y1,x2,y2,name,color);
arrayShape[index++] = shape;
}
关于图像的复原,我们的思路是把每一步设置为对象保存在shape中,然后当最小化再打开,或者拖拽窗体时,再调用这个数组取出其中的内容,实现画面的恢复。
public Shape(int x1,int y1,int x2,int y2,String name,Color color)
{
this.x1=x1;
this.y1=y1;
this.x2=x2;
this.y2=y2;
this.name=name;
this.color=color;
}
public void drawShape(Graphics g)
{
switch(name)
{
case "直线":
g.setColor(color);
g.drawLine(x1, y1, x2, y2);
// System.out.println("画");
break;
case "矩形":
g.setColor(color);
g.drawRect(x1, y1, x2, y2);
break;
case "椭圆":
g.setColor(color);
g.drawOval(x1, y1, x2, y2);
break;
case "橡皮擦":
g.setColor(color);
g.clearRect(x1, y1, x2, y2);
break;
}
public void paint(Graphics g)
{
super.paint(g);
for(int i=0;i<arrayShape.length;i++)
{
Shape shape=arrayShape[i];
if(shape!= null)
shape.drawShape(g);
else break;
}
}
这是涉及到参数保存和取出的方法。
以上代码便实现了我们的画板了,以画板为基础我们接下来还可以实现很多界面的设计,在画板的学习过程中充分使用了监听器这个工具,程序的可设计性大大提高了。