对仿XP画板的一些总结
经过半个多月的学习,对JAVA有了一个初步的了解,并且也做出了我的第一个还比较像样的小程序---仿XP的画图板。在感觉到成功的喜悦的同时,觉得有必要对这一个阶段做一个总结。
1.关于JAVA的基础认识
JAVA是一种类C语言的编程语言,但是不同于C的是它是面向对象的,而C是面向过程的。从这一点来说它应该比C语言具有天生的优势,因为在现实的世界中对事情的认识和分析是按照这种面向对象的方式进行的。面向过程就是主要分析做事情的过程,把一件事情划分成几个重要的步骤。一旦步骤定下来,执行的结果就确定了,换句话说,就是执行的结果与输入的变量没有关系。而面向对象主要就是要确定是谁去执行一件事情,虽然也要知道做事情的步骤,但是已不是最主要的。在面向对象的程序中,对于不同的输入,即不同的对象,按照同一个过程去执行能得到不同的结果。
2.JAVA中一些基础代码的书写
在说这个问题之前,不得不先提到两个概念:类和对象。简单的说,类就是一类经过总结的具有共同的属性和方法的一类事物的集合,它是抽象的,在JAVA里面它可以作为对象创建模版,也可以作为数据的类型声明变量;而对象呢,它是类中的具体的一个个体,我们能明确的指出它,这是它一个非常重要的特点,对象是具体的,在JAVA中,它是任何过程的具体的执行者。
一个完整的JAVA程序可以说是由若干个类组合在一起组成的,类是JAVA程序的基本单元。在JAVA中类是以这样的形式定义的 访问限定符(public/private等)+ class + 类名
有了类,我们就可以以类为模版来创建对象了,对象是这样声明的 A a = new A(); 其中A是类名,a是具体的对象的名称
有了具体的对象,它们就可以执行具体的过程了,也就是调用它们的属性和方法。属性的定义 访问限定符 + 变量类型 + 属性名称 如 public int number; private String name等
方法的定义 访问限定符 + 返回值类型 + 方法名 如 public void study(){} public int score(){}等
下面还要简单讲讲子类和父类的问题。类与类之间不光可以是简单的平行并列的关系,有时候它们之间也存在着继承与被继承的关系,也就有了子类和父类的说法。父类和子类在范围上是一个包含与被包含的关系,比如说学生和大学生就是一个典型的父类和子类的关系。在JAVA中,子类是可以继承父类中已经定义的方法和属性。继承方法就是直接使用父类中的方法,不必再重新定义;当然子类也可以重写父类中的方法,这样在继承后调用方法的时候,子类就是使用自己改写后的方法了。在JAVA中还规定,一个子类只能继承一个父类,即单根继承。下面,对这个为题进行简单的代码示例
//定义学生类,作为主类
public class Student {
public String name ;
public Student() {
System.out.println("学生");
}
public void setName(String name) {
//this 指本内存区域中的同名对象
this.name = name;
}
public void study() {
System.out.println(name+"在学习");
}
}
/*定义一个大学生类,并使它作为子类,继承学生类*/
//使用extends关键字
public class UNStudent extends Student {
public UNStudent() {
System.out.println("大学生");
}
//可以定义子类特有的方法
public void cet4() {
System.out.println(name+"可以考4级");
}
//子类可以重写父类已经定义的方法
public void study() {
System.out.println(name+"在大学的教室里面学习");
}
}
有了父类和子类,通俗的说是通过范围的大小来区分的。这样,在类型转换的过程中就出现了有大范围转到小范围和由小范围转到大范围的问题。在JAVA中由小转大是可以自动进行的,因为在这种情况下永远也不会出现错误;而由大转小的时候,就需要人为的进行了。下面进行代码示例
//正确的声明变量的方法
Student stu1 = new Student();
UNStudent stu2 = new UNStudent();
Student stu3 = new UNStudent(); //自动转型
//错误的声明变量
UNStudent stu4 = new Student(); //不能自动的由小转大
//如何完成这部操作
UNStudent stu4 = (UNStudent)stu1; //强制转型
其实出了类,JAVA里面还有其它的类型可以使用的。比如说,接口,抽象类等。接口比较抽象,它没有构造方法,不能实例化对象;如果继承了接口,它里面的所有方法全部都要被继承者重写并实现,这是它比较麻烦的地方。而抽象类是一种比较特殊的类,它与普通的类的不同点就是它也不能实例化对象。而且抽象类中的方法没有定义方法体,被子类继承后需要子类先重写在使用。不过它与接口的不同是,继承抽象类的子类只需要重写用到的方法,没有使用到的不需要重写。
最后,要说说JAVA一个最大的特点,那就是它的多态性。多态是面向过程与面向对象编程最大的不同点了。个人觉得就是为当一件事情的执行者不同时能产生不同的结果提供了可能,相同的数据类型,相同的方法却能够产生不同的结果。这在面向过程的语言中是不能做到的。代码示例
1.Student stu = new Student();
stu.setName("AA");
stu.study(); //执行结果:AA在学习
2.UNStudent stu1 = new UNStudent();
stu1.setName("BB");
stu1.study(); //执行结果:BB在大学的教室里面学习
3.Student stu2 = new UNStudent();
stu2.setName("CC");
stu2.study(); //执行结果:CC在大学的教室里面学习
//1和3具有相同的Student类型,都执行study方法,结果却不同。体现出多态性
有了这一些基础的知识后,就可以开始进入画板的制作了
3.画板窗体
进入画图板的程序,首先就是要有一个窗体。在 javax.swing.JFrame包里面定义了要初始化一个面板常用的方法。所以我们只需要让我们的画板窗体类继承JFrame类就可以了。通常情况下,窗体的初始化只用到下面的几种方法。
代码示例
public class DrawUI extends JFrame{
public void main(String args[]){
DrawUI du = new DrawUI();
du.initDrawUI();
}
//窗体初始化的函数
public void initDrawUI(){
this.setTitle("我的画板"); //名称
this.setSize(800,600); //大小
this.setDefaultCloseOperation(3); //点击关闭时的动作
this.setLocationRelativeTo(null); //位置
}
这样,窗体的初始化就完成了。不过它这时候并不能显示出来。需要使用 this.setVisiable(true); 来使它显示在屏幕上。
4.画板的一些组件和工具
有了窗体,下面就要仿照XP画板的模样加组件和制作工具了。在这里只想讲讲在做的过程中有比较深的印象的几个方面。(里面的截图会打包上传)
首先,是有关于布局的方面。在制作的过程中,主要用到了流式布局、边框布局以及布局为null的三种情况。
第一,先说说流式布局。我感觉流式布局不仅在这次的画板制作中,在平时也应该是一种比较常用的布局方式。流式布局简单地说就是将各个组件按照从左至右,从上至下的规则放置在存放组件的容器上。JAVA中,流式布局的无参构造方法默认的是居中对齐,水平、垂直间隙都是5个像素。当然,我们可以通过向构造方法中传参数的方法来指定对其方式以及水平的垂直的间隙。
//代码示例
//窗体布局的方法
java.awt.FlowLayout fl = new java.awt.FlowLayout();
this.setLayout(fl);
//给窗体加组件
javax.swing.JLabel namelabel = new javax.swing.JLabel("帐号");
this.add(namelabel);
javax.swing.JTextField namefield = new javax.swing.JTextField(25);
this.add(namefield);
javax.swing.JLabel numlabel = new javax.swing.JLabel("密码");
this.add(numlabel);
javax.swing.JTextField numfield = new javax.swing.JTextField(25);
this.add(numfield);
javax.swing.JButton loginbut = new javax.swing.JButton("登录");
this.add(loginbut);
javax.swing.JLabel msgLabel = new javax.swing.JLabel();
this.add(msgLabel);
//这样,上面加到窗体上的组件就是按照默认的流式布局放置的
//传参的布局方式
//给左边的工具栏设置布局
left.setLayout(new java.awt.FlowLayout(FlowLayout.CENTER,0,0)); //居中对齐,间距(0,0)
第二,在说说边框布局。个人觉得边框布局在画板的制作中有着非常重要的作用,主要体现在窗口的大小变化的过程中。边框布局能使组件的大小随着容器的大小变化,让窗体不会因为大小的变化而变得失去原来设计好的模样。边框布局默认的是各组件之间没有间隙,我们也可以传参来设置间隙。这里还要说说边框布局的规则。在一个边框布局的容器中,一共能设置5个组件,它们安放的位置分别以东、西、南、北、中来代指。在划分五个区域时,先把整个容器划分为上、中、下三部分,再将中间划分为左、中、右三部分。在这个里面,中间的部分是其它的部分划分完之后剩下的部分,在一般的情况下应该是最大的那一块。在应用的时候可以根据需要灵活的运用。
//代码示例
//设置底部面板的对其方式为边框对其方式
java.awt.BorderLayout fb = new java.awt.BorderLayout();
footpanel.setLayout(fb);
//新建颜色工具条对象,并初始化
javax.swing.JToolBar colorbar = new javax.swing.JToolBar();
colorbar.setPreferredSize(new java.awt.Dimension(200,60));
//将颜色工具条加到底部面板上
footpanel.add(colorbar,BorderLayout.CENTER);
//新建状态栏对象,并进行初始化
javax.swing.JPanel statuspanel = new javax.swing.JPanel();
statuspanel.setPreferredSize(new java.awt.Dimension(200,20));
statuspanel.setBackground(java.awt.Color.pink);
//将状态栏加到底部面板上
footpanel.add(statuspanel,BorderLayout.SOUTH);
//设置状态栏的布局方式
java.awt.BorderLayout fb1 = new java.awt.BorderLayout();
statuspanel.setLayout(fb1);
//新建标签和面板对象,并把它们加到状态栏上
javax.swing.JLabel leftLabel = new javax.swing.JLabel("山寨产品,仅供娱乐");
statuspanel.add(leftLabel,BorderLayout.CENTER);
javax.swing.JPanel panel1 = new javax.swing.JPanel();
panel1.setPreferredSize(new java.awt.Dimension(200,20));
panel1.setBackground(java.awt.Color.white);
statuspanel.add(panel1,BorderLayout.EAST);
//设置panel1的布局方式
java.awt.BorderLayout fb2 = new java.awt.BorderLayout();
panel1.setLayout(fb2);
//新建两个用来显示坐标的标签对象,并把它们加到panel1上
javax.swing.JLabel centerLabel = new javax.swing.JLabel("");
javax.swing.JLabel rightLabel = new javax.swing.JLabel("");
panel1.add(centerLabel,BorderLayout.CENTER);
panel1.add(rightLabel,BorderLayout.EAST);
第三,说说布局为null的时候的情况。这种布局其实还挺好用的。在这次的画板的制作中,主要用在了底部颜色工具条前景色和背景色的设置中。
代码示例:
//为了让图片重合,把布局设为null
leftpanel.setLayout(null);
//前景色
frontlabel.setBounds(5,5,16,16); //在容器x轴和y轴上的起点,以及组建的长度和宽度
frontlabel.setOpaque(true); //设置不透明,让背景色不能透出来
frontlabel.setBackground(java.awt.Color.BLACK);
frontlabel.setBorder(new javax.swing.border.BevelBorder(BevelBorder.RAISED,java.awt.Color.WHITE,java.awt.Color.GRAY));
//背景色
backlabel.setBounds(10,10,16,16);
backlabel.setOpaque(true);
backlabel.setBackground(java.awt.Color.WHITE);
backlabel.setBorder(new javax.swing.border.BevelBorder(BevelBorder.RAISED,java.awt.Color.WHITE,java.awt.Color.GRAY));
//将组件加到leftpanel上
leftpanel.add(frontlabel);
leftpanel.add(backlabel);
然后,说说在做画图工具时的一些体会。在做画图工具时,调用函数后,就需要考虑对数学公式的使用了。在JAVA里面,有专门定义过的数学公式包。使用它们的时候,比较需要注意的是点的相对位置,自己需要分析画图的时候图形的起始点的坐标有什么规律。在虚拟机的默认中,最左上角的点为坐标原点,这与平时的习惯可能不太一样,要注意。
代码示例:
private int x1, y1, x2, y2;
//图形的选择
if(type1.equals("line")) //画直线
g.drawLine(x1, y1, x2, y2);
else if(type1.equals("rect")) //画矩形
g.drawRect(Math.min(x1, x2),Math.min(y1, y2),Math.abs(x2-x1),Math.abs(y1-y2));
else if(type1.equals("oval")) //画椭圆
g.drawOval(Math.min(x1, x2),Math.min(y1, y2),Math.abs(x2-x1),Math.abs(y1-y2));
else if(type1.equals("roundrect")) //画圆角矩形
g.drawRoundRect(Math.min(x1, x2),Math.min(y1, y2),Math.abs(x1-x2-1),Math.abs(y1-y2-1),20,40);
最后,说一说监听器。监听器也叫Listener,它是一种能监听用户操作并自发的触发一些操作的类。基本上,为了有人机的互动,程序中都需要监听器。在这次画板的制作中,主要用到了鼠标监听器。在系统中,有给它定义的接口,也有虚拟类。在自己需要的方法在上面的接口或类中都有定义时,继承类可能更为方便一些,因为这时我们只需要重写用到的方法,而对没用到的方法不用理会。监听器还有另一个重要的作用,那就是传参。就是说,当两个类中需要使用到同一个参数时,可以通过重写监听器的构造方法,把参数传进来。这种传参的方式在这次画板的制作中经常使用到。
代码示例:
//构造函数重载
public DrawListener(java.awt.Graphics g,javax.swing.ButtonGroup group1,javax.swing.JLabel frontlabel,javax.swing.JLabel centerLabel,javax.swing.JLabel rightLabel,java.awt.Color c){
this.g = g;
this.group1 = group1;
this.centerLabel = centerLabel;
this.rightLabel = rightLabel;
this.frontlabel = frontlabel;
this.c = c;
}
创建监听器方法1 // 通过匿名内部类创建鼠标监听器对象
java.awt.event.MouseAdapter mlis = new java.awt.event.MouseAdapter() {
public void mousePressed(MouseEvent e) {
// 得到事件源对象(就是被点击的按钮对象)
javax.swing.JButton but = (javax.swing.JButton) e.getSource();
//得到按钮的背景颜色
c = but.getBackground();
//得到鼠标点击的是左键还是右键
int butNum = e.getButton();
if(butNum==1){
frontlabel.setBackground(c);
}else if(butNum==3){
backlabel.setBackground(c);
}
//刷新底部的左边面板
leftpanel.updateUI();
}
};
创建监听器的方法2//创建鼠标监听器对象
DrawListener dlis = new DrawListener(g,group1,centerLabel,rightLabel,frontlabel,c);
//将监听器装在画板上
DrawPanel.addMouseListener(dlis);
DrawPanel.addMouseMotionListener(dlis);
5.画板的重绘
上面的功能在经过一些修饰之后,我们的画板应该就可以执行一些简单的画图工作了。但是这时你通过试验会发现,当我们的画板不完全在窗口中或者被最小化再或者不是当前的焦点窗口时,里面所画的内容会被清空。这是因为,我们所画的内容这时还没保存到内存中,只是保存到了缓存中,当出现上述情况时,里面的内容就被擦除了。这是我们就需要一种方法来解决这种问题了,那就是重绘。在这次画板的制作中,我试验过两种重绘的方法,分别是通过建立一个泛型动态数组的方法来保存里面画过的图形的属性;还有一个是通过建立一个二维数组,来保存画布上的每一个点的方法进行重绘。这里代码过长就不进行示例了。这两种方法各有各的优缺点。首先,泛型动态数组的方法最能体现JAVA面向对象的特点,它把每一种图形都定义成了一个类;缺点是,它应该是最费事的一种重绘的方法了,需要自己编写很多个类和数据的结构。其次,是二维数组的方法。这种方法是把画布上的每一个点都保存下来成一张图片,这样,二维数组的长度就确定了。这种保存的方法只是保存了画布上每个点的颜色,这样只需要一种画直线的方法便能完成重绘,很简单。但是,如果把每个点都遍历,会非常的慢,这是就需要进行算法的优化了。