首页 > 代码库 > 用Java语言编写一个简易画板
用Java语言编写一个简易画板
讲了三篇概博客的概念,今天,我们来一点实际的东西。我们来探讨一下如何用Java语言,编写一块简易的画图板。
一、需求分析
无论我们使用什么语言,去编写一个什么样的项目,我们的第一步,总是去分析这个项目需要满足怎样的需求。
那么,画板需要满足怎样的需要呢?换句话说,在画板上,我们应该赋予它什么功能呢?从我们熟悉的画板来看,我们需要实现诸如铅笔、橡皮、喷枪、刷子的功能,我们可以画出一些规则的图形,比如直线、矩形、圆。最好我们还能调整画笔的颜色和粗细。以上,我们希望的是,当我们点击一个按钮的时候,我们就能实现相对应的功能。一个简单画板的功能大概就这么多了,一些关于添加文本框、放大镜等的操作在简单画板上就不一一实现了。
二、实现画板
1、基本图形的绘制
我们先从最基本的画一些基本图形开始。在Java里有一个画笔类Graphics,而我们实现画板功能的关键就在这里。在Graphics这个画笔类里面,有几个方法,比如:画矩形、圆、直线。了解了这一点,我们的思路就来了。接下来,我们将一步一步认识实现基本图形绘制的全过程。
首先,我们需要实例化一个JFrame容器,并给它设置初始值。其次,我们需要在这个容器上,添加一些按钮。这里以直线、矩形、圆为例。这部分的实现比较简单,需要注意的是,我们可以通过实例化一个字符串类型的数组去存储按钮里的内容,然后通过遍历数组的方式去实现多个按钮的建立,以此减少代码行数。给出代码如下:
其次,因为涉及到关于鼠标点击以及按钮点击的操作,我们需要用到事件监听机制,MouseListener和ActionListener。因为它们都是接口,不能被实例化对象,所以我们需要建立一个事件处理类去实现这两个接口,并重写其中的抽象方法。具体代码如下:
当然,仅仅这样是不够的。首先,我们需要对事件源,也就是我们的按钮和画板界面通过addXXXListener的方式添加事件处理机制。其次,我们还需要对对应的方法,在其方法体中写上我们需要实现的功能。画笔类是一个抽象类,因此它是不能被实例化的,我们只能通过getGraphics这个方法从JFrame或者是JPanel里去获取画笔。注意,画笔的获取要在让界面可见之后。之后,因为我们在事件处理类里面要用到画笔,所以,我们还需要将画笔从我们的画板界面传到我们的事件处理类当中,我们可以在事件处理类里面定义一个setGraphics的方法,同时定义一个Graphics的引用去接收这个画笔。具体代码如下:
接下来就是调用画笔里的方法了。举个例子,当我们获取的按钮里的内容是Line的时候,那么,我们需要调用画笔Graphics里的drawLine方法,我们需要给它起始点和终点的x,y坐标。所以,我们在mousePressed方法里,记录下起始点的x,y坐标,在mouseReleased里记录下释放点的xy坐标,然后调用这个drawLine方法。那么,我们就可以在我们按下和释放的地方连成一条直线。代码实现如下:
这样我们就实现了三个基本图形的绘制。其他的, 在Graphics画笔类里面还有关于弧形、多边形、填充矩形等绘制方法,这里就不一一列举了。
2、画笔颜色与粗细的变化
Java里有一个Color类,它可以用作画笔颜色的设置。除了固定颜色的选取,例如Color.BLACK,我们程序员自己还可以自己去调取颜色。我们知道,每一种颜色,都可以由红、绿、蓝三基色按不同程度调配而成。在Java里有一个关于Color的构造方法,传入三个int类型、范围在0到255之间的数,分别对应红绿蓝三种颜色的程度。我们可以通过Color 颜色名=new Color(int a,int b,int c)的方式去自行定义自己想要的颜色。之后,我们可以通过.setColor的方式,将我们新建的颜色赋给我们的画笔。
而关于画笔粗细的设置方法,它并不在Graphics类里面,而是在它的子类Graphics2D里面。相较于Graphics,Graphics2D同样是一个抽象类,但是它里面新增了不少方法,比如给画笔设置粗细的方法setStroke。而在setStroke这个方法中,你所需要传递的参数是一个Stroke类型的参数,而Stroke是一个接口,所以不能够实例化,因此我们给它传递的是Stroke的一个封装类BasicStroke,它实现了Stroke这个接口。因此,我们设置粗细的方式为:(Graphics2D)g.setStroke(new BasicStroke(float width))。
如果我们需要实现这样的情况,在画板界面里有红、绿、蓝等多种颜色的按钮,当点击后画笔变成相应颜色,我们可以通过给按钮设置颜色的方式去处理这样的问题。给按钮或者窗体设置颜色的方式是.setBackground(Color c)。
3、铅笔、刷子、橡皮以及喷枪的实现
现在,我们知道了如何画一个圆,画一条直线。在实现铅笔等功能钱,我想先问个问题,如何去画一个点?我有两个想法,一个是,你可以通过画实心圆的方式来画一个点,给它半径指定为一个像素,那么所画出的实心圆就成了一个点。第二种,可以通过画直线的方式,指定起始位置和释放位置相同,那么画出的也是一个点。
接下来,我们要讲讲如何实现铅笔的功能。这里需要介绍一个新的事件监听方法,MouseMotionListener。在这个接口里,有一个鼠标拖动的监听方法。我们可以用它来实现铅笔的实现。它的工作机制是每隔一段时间,就获取一次你鼠标所在的位置。当然,这个时间非常短。事实上,我们可以观察在画图板上的直线。我们会发现每一条不规则曲线,都是由若干条直线构造而成的。那么,在这个拖动过程当中,我们可以在开始的地方记录下它的xy坐标,然后每运行一次拖动获取,就改变一次起始的xy坐标,那么,我们就实现了所谓的铅笔绘图功能。因为我们的事件处理类同时也继承了MouseListener,在拖动进行的开始,事实上我们已经进行了一次点击操作,所以,最开始的坐标我们不需要记录。注意,因为是不同的监听方法,所以一定要记得给画板界面添加MouseMotionListener的监听方法。铅笔的具体代码实现如下:
实现了铅笔,刷子其实也就那么回事,我们只需要将铅笔实现这部分的代码复制下来,并在之前添加一行给画笔加粗的代码即可,加粗了的铅笔,不就是我们的刷子吗?至于橡皮,我们也可以这么理解,只要找到与画板界面底色相类似的颜色,给画笔赋这样的颜色,不就能覆盖掉原有的部分了吗?
接下来是关于喷枪的实现。我们可以将喷枪理解成,在铅笔的基础上(去掉铅笔所绘的轨迹),对于一个点,在一定范围内,随机画若干个点。类似的变换,我们可以对一个点的xy坐标加减一定范围内不等的值,画出来的效果,就类似于喷枪效果了。生成随机数的方法在Random这个类里面,所以我们首先需要实例化一个Random类,然后通过nextInt(int a)的方式去获取一个0到a-1的随机数。这样,我们就能实现喷枪了。具体代码如下:
4、重绘
在这个实验过程当中,我们很容易发现一个问题,当我们最小化或者说调整窗体大小的时候,我们所绘制的图形消失了,然而按钮还在。这是为什么呢?事实上,每当我们进行一次最小化或者调整窗体大小的时候,我们原先的那个窗体都被关闭了,接着计算机又重新绘制了一个全新的窗体。所以,我们自行绘制的图形都没有了。那么,为什么我们的按钮还在?事实上,不仅仅是按钮,swing包内其他我们熟悉的组件,账号输入框、密码输入框等元素,也还是存在。事实上,每一个元素、每一个窗体,它都有一个重绘方法。当窗体改变大小的时候,生成新窗体后,计算机会调用这个重绘方法,所以你看到按钮等元素在改变界面后仍然在界面上。那么,如何让我们自行绘制的图形也像按钮一样,不会随着改变窗体而消失呢?
事实上,我们只需要重写一下画图界面上的重绘方法。这也就是为什么我要继承JFrame这个类的原因。因为继承了JFrame,所以,我们可以重写它继承的paint方法,在其中加上我们所绘制的内容,那么在窗体重绘的过程当中,我们所绘制的内容也就重新绘制上去了。
这只不过是重绘的第一步。因为我们绘制的内容有可能过于复杂,那么我们在主类里面如何去得知我们绘制了什么呢?我们可以定义一个shape类,然后在事件处理类里面定义一个shape类型的数组,每画一个图,那么,我们就用数组将它存储下来,然后让index++。为了简化paint方法里的内容,我们可以定义一个shape抽象类,里面定义一个抽象方法draw,然后申明不同图形,让它们都继承shape类,并且重写draw方法。这样,在paint方法里,我们就可以直接调用draw方法,而不用去写各种判断语句。这样做简化了我们的代码。
代码具体实现如下:
这里只给出shapeLine这个类的定义,关于其他类型的图案的定义就没有一一给出了,让看客自行发散。
5、进行界面的美化
假设我们把所有的按钮都如此加上,那么,我们会发现,画图界面会非常的不美观。为了增强界面的美观程度,我们可以通过引进panel的方法。我们可以将画基本图形的按钮放在一块面板内,颜色按钮放在一块面板里,而铅笔等功能又放到一块面板里。同时为了区分每个界面,我们还可以在重绘方法里,编写这样的语句g.drawLine来给界面加上边框。
完成程序,具备想要的功能只是第一步。之后我们需要做的就是去尽量让我们的界面美观,并且,一些能进行缩减的代码尽量缩减,使我们的代码看起来更加的精简干练。
三、总结
一个画板,说简单也简单,说复杂也复杂。以后我们打的程序肯定会越来越复杂,我们需要更加严谨,最好是加上一定的注释(当然我个人写代码是不喜欢写注释的,可能也是因为写的少的原因)。画板谈的比较粗糙,欢迎各位大神指正。
用Java语言编写一个简易画板