[Java学习日志]图形界面开发练习——绘图板的实现

Java具有十分丰富的绘图功能,通过调用对象方法,可以实现众多诸如绘制直线,矩形,圆形等功能。

通过实现一个仿画图软件的画图板,可以让我们更好的了解Java中的众多绘图机制,并能够加深对事件概念的认识和使用


在Java中,我们通过使用Graphics和Graphics2D类创建的画笔画布对象,来实现一些基本绘图功能。


首先创建一个继承JFrame的画板类,创建一个窗体对象作为画板,我们便在其上来实现绘画功能。上面说过,我们是通过Graphics类对象来实现绘图功能的,可以通过JFframe窗体对象的getGraphics方法获取该窗体的画笔画布对象,然后便可实现在窗体上的绘图工作。

其次,想像一下,如果要在画板上绘制一条直线,那么应该在窗体上点击鼠标,拖动鼠标,释放鼠标,然后绘制出一条直线,这便要求用到事件机制,可以创建一个监听类继承MouseAdapter类,来实现鼠标上的各种事件监听,MouseAdapter类里包含了所有关于鼠标事件的实现。然后将前面获取的Graphics对象传给监听类,当触发鼠标事件时,便用该getGraphics对象来绘制图案。


简单图形绘制:

1.画直线

绘制一条直线,可以调用Graphics类的drawLine()方法来实现,该方法的API文档介绍如下:

public abstract voiddrawLine(int x1,int y1,int x2,int y2)

在此图形上下文的坐标系中,使用当前颜色在点(x1, y1)(x2, y2) 之间画一条线。
参数:
x1 - 第一个点的x 坐标。
y1 - 第一个点的y 坐标。
x2 - 第二个点的x 坐标。
y2 - 第二个点的y 坐标。

我们可以在鼠标按下时获取第一个点坐标,鼠标释放时获取第二个点坐标,然后通过此方法来画一条直线。

2.画矩形

矩形的绘制方法,通过调用Graphics类的drawRect()实现,其具体介绍如下:

public void drawRect(int x,int y,int width,int height)
绘制指定矩形的边框。矩形的左边缘和右边缘分别位于 xx + width。上边缘和下边缘分别位于 yy + height。使用图形上下文的当前颜色绘制该矩形。
参数:
x - 要绘制矩形的 x 坐标。
y - 要绘制矩形的 y 坐标。
width - 要绘制矩形的宽度。
height - 要绘制矩形的高度。

同样的,可通过按下鼠标获取第一个点坐标,释放鼠标获取第二个点坐标,然后第二个点坐标与第一个点坐标差得到要绘制矩形的长和宽,再调用该方法实现绘制。

3.画圆

圆的绘制使用的是Graphics类的drawOval()方法,该方法介绍如下:

public abstract void drawOval(int x,int y,int width,int height)

绘制椭圆的边框。得到一个圆或椭圆,它刚好能放入由xywidthheight 参数指定的矩形中。椭圆覆盖区域的宽度为width + 1 像素,高度为height + 1 像素。

参数:

x - 要绘制椭圆的左上角的x 坐标。

y - 要绘制椭圆的左上角的y 坐标。

width - 要绘制椭圆的宽度。

height - 要绘制椭圆的高度。

可以看到该方法是用来画椭圆的,那么怎么画圆呢?显然只要width = height就可以了。

4.画图片

画图片的方法如下:

public abstract boolean drawImage(Image img,int x,int y,ImageObserver observer)

绘制指定图像中当前可用的图像。图像的左上角位于该图形上下文坐标空间的 (xy)。图像中的透明像素不影响该处已存在的像素。
参数:
img - 要绘制的指定图像。如果 img 为 null,则此方法不执行任何操作。
x - x 坐标。
y - y 坐标。
observer - 转换了更多图像时要通知的对象。
返回:
如果图像像素仍在更改,则返回 false;否则返回 true

此方法在任何情况下都立刻返回,甚至在图像尚未完整加载,并且还没有针对当前输出设备完成抖动和转换的情况下也是如此。

如果图像已经完整加载,并且其像素不再发生更改,则 drawImage 返回true。否则,drawImage 返回false,并且随着更多的图像可用或者到了绘制动画另一帧的时候,加载图像的进程将通知指定的图像观察者。

5.其它

下面列出一些其它图形的绘制方法,更多相关内容可参见Java API文档;

drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)
          绘制一个覆盖指定矩形的圆弧或椭圆弧边框。

drawPolygon(int[] xPoints, int[] yPoints, int nPoints)

          绘制一个由 xy 坐标数组定义的闭合多边形。

drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)

          用此图形上下文的当前颜色绘制圆角矩形的边框。

drawString(String str, int x, int y)

          使用此图形上下文的当前字体和颜色绘制由指定 string 给定的文本。

fill3DRect(int x, int y, int width, int height, boolean raised)

          绘制一个用当前颜色填充的 3-D 高亮显示矩形。

fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)

          填充覆盖指定矩形的圆弧或椭圆弧。

fillOval(int x, int y, int width, int height)

          使用当前颜色填充外接指定矩形框的椭圆。

fillPolygon(int[] xPoints, int[] yPoints, int nPoints)

          填充由 xy 坐标数组定义的闭合多边形。

fillRect(int x, int y, int width, int height)

          填充指定的矩形。

fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)

          用当前颜色填充指定的圆角矩形。


特殊图形绘制

1.任意多边形

Graphics类方法中没有任意多边形的绘制方法,我们可以通过使用绘制直线的方法,来实现任意多边形的绘制方法。
想象一下,任意多边形的绘制,其实就是多根直线闭合连接,可以使用鼠标点击事件获取坐标,再多次调用drawLine()方法绘制直线,使其组成一个任意多边形,并且要当最后一次点击与第一次的坐标相距较近时自动闭合连接。下面是我实现任意多边形的部分代码:
if(ButtonName.equals("任意多边形")){
    				dr.drawLine(x1, y1, x2, y2);
}

if(ButtonName.equals("任意多边形")){
    			if( ( (ClickX - x1) * (ClickX - x1) + (ClickY - y1) * (ClickY - y1) ) < 900){
    				dr.drawLine(tempX, tempY, x1, y1); 
    				
    				//重绘部分
    				shapeArray[index++] = rshape;
        			if(index == 1000)
        				index = 0;
    				
    				initmreck();
    			}
    			else if( flag2 == true ){
    				dr.drawLine(tempX, tempY, ClickX, ClickY);
    				tempX = ClickX;
    				tempY = ClickY;
    			}
    			else{
    				dr.drawLine(x2, y2, ClickX, ClickY);
    				tempX = ClickX;
    				tempY = ClickY;
    				flag2 = true;
    			}

2.分形的绘制

首先,什么是分形呢?以下是来自百度百科的copy:
分形,具有以非整数维形式充填空间的形态特征。通常被定义为“一个粗糙或零碎的 几何形状 ,可以分成数个部分,且每一部分都(至少近似地)是整体缩小后的形状”,即具有 自相似 的性质。

乍一看,一脸懵逼的感觉。可以不在意这些概念,绘制分形图形,即在绘图时可以根据一些分形公式计算出响应坐标值,再进行绘制,可以绘制出如下面的图形:

[Java学习日志]图形界面开发练习——绘图板的实现_第1张图片

这图形的绘制公式为:


即用该公式计算出相应坐标值,在调用drawLine()方法在每个坐标处绘制一个点,组成起来即可,下面是我绘制该图形的部分代码:

if(ButtonName.equals("分形1")){
        		int movx = x1;
        		int movy = y1;
        		
        		//重绘部分
        		Shape shape = new Shape();
        		shape.setS(ButtonName, x1, y1,x2,y2,bColor);
        		
        		shapeArray[index++] = shape;
    			if(index == 10000)
    				index = 0;
        		
        		int bx = x2 - x1;
        		int by = y2 - y1;
        		
        		double fx=0,fy=0,rfx=0,rfy=0;
        		for(int i = 0; i < 100000; i++){
    	    		fx = Math.sin((-2) * rfy) - Math.cos(((-2) * rfx));
    	    		fy = Math.sin((-1.2) * rfx) - Math.cos(2 * rfy);
    	    		rfx = fx;
    	    		rfy = fy;
    	    		if(i == 20000)
    	    			dr.setColor(Color.red);
    	    		if(i == 50000)
    	    			dr.setColor(Color.blue);
    	    		if(i == 80000)
    	    			dr.setColor(Color.green);
    	    		dr.drawLine((int)(rfx * bx) + movx, (int)(rfy * bx) + movy, (int)(rfx * bx) + movx, (int)(rfy * bx) + movy);
        		}
    		}

3.画笔功能的实现

用过画图软件的同学都知道,有一个铅笔的画图功能
[Java学习日志]图形界面开发练习——绘图板的实现_第2张图片
那它是怎么实现的呢?首先,我们看下铅笔画下的图形放大时的情况,相信很多同学以前也注意到了:
[Java学习日志]图形界面开发练习——绘图板的实现_第3张图片
可以看到,该图形是由众多直线组成的!所以铅笔功能实际上就是在鼠标移动过程中不断调用drawLine()方法来绘制直线,那么我们要怎样实现呢?这便要用到鼠标的拖曳移动事件,该事件在按下鼠标移动时触发,我们可以使用mouseDragged()监听器,来记录下移动时的每个坐标,然后再使用drawLine方法来实现,下面是我实现的部分代码:
//鼠标拖动监听
    public void mouseDragged(MouseEvent e){
    		    	
		    	int x2 = e.getX();
		    	int y2 = e.getY();
	    	
	    	if(ButtonName.equals("铅笔")){
	    		dr.drawLine(x1, y1, x2, y2);	    		
		    	
		    	//重绘部分
    			Shape shape = new Shape();
    			shape.setS(ButtonName,x1,y1,x2,y2,bColor);
    			
    			shapeArray[index++] = shape;
    			if(index == 1000)
    				index = 0;
    			
    			x1 = x2;
		    	y1 = y2;
    			
	    	}
}
其中x1,y1为按下鼠标时获取的坐标值

4.立体图形的实现

下面看下立体图形怎么实现。首先,日常生活中我们看到物体,为什么会觉得这个物体是立体的,是实实在在存在的呢?显然是因为物体表面的光影,让我们产生了这种感觉,下面看一个立体球:
[Java学习日志]图形界面开发练习——绘图板的实现_第4张图片
可以看出,这不过就是一个圆中增加了一些色泽,所以成为了立体的样子,那么怎么增添这部分光泽呢?下面是我的部分实现代码:
private Color changeColor(Color bColor){
    		
    	int red = bColor.getRed();
		int green = bColor.getGreen();
		int blue = bColor.getBlue();
		
		red += 4;
		green += 4;
		blue += 4;
		if(red >= 255) red = 255;
		if(green >= 255) green = 255;
		if(blue >= 255) blue = 255;
		
		return new Color(red, green, blue);
    }

if(ButtonName.equals("立体圆")){
    			
    			dr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    			
    			dr.setColor(bColor);
    			
    			int r = (x2 - x1)/2;
    			dr.fillOval(x1, y1, r * 2, r * 2);
    			
    			
    			for(int i = r; i > 0; i--){
    				dr.setColor(changeColor(dr.getColor()));
    				
    				if(i % 2 == 0){
    					x1 = x1 + 1;
    					y1 = y1 + 1;
    				}
    				dr.fillOval(x1, y1, i * 2, i * 2);
    			}
    		}

首先我定义了一个changeColor()方法用来不断改变颜色,然后进行多次绘制圆形,每一次绘制都使圆的大小和位置发生改变,使其偏于第一个圆的中心偏左一些,并且每一次绘画都改变圆的颜色,不断趋于白色,最后便实现了上面的效果。

重绘机制

从上面的代码可以发现,代码中都有一部分重绘部分,这部分内容是什么呢?
当我们实现了直线,矩形等绘制方法之后,可以发现,当我们把窗体最小化,再复原,或者将窗体最大化的时候,前面绘制过的图形全部没有了。这是怎么回事呢?
首先要了解一下关于图形界面的pain()机制。我们平时所看到的图形界面,其实是当我们启动程序时,系统讲界面绘制出来给我们看的,就如最小化一样,当我们点击最小化按钮后,系统讲打开的图形界面擦除,然后当我们复原的时候,系统通过方法,来在屏幕上将界面再次绘制出来。因为我们编写窗口界面时,系统将窗口的布局,图形等信息都存储起来,当我们最小化,复原窗口时,系统便能再次将窗体绘制出来。但我们在窗体上绘制的图形并没有存储起来,所以当窗体再次绘制出来时,我们的图形便丢失了,所以,重绘机制,便是将我们的画的图形存储起来,当窗体再次绘制时,我们的所画的图形也一并再次绘画出来。

我们通过重写JFrame的pain()方法来实现重绘机制。
可定义一个Shape类,来存储相关的图形信息,再把图形存储在一个Shape数组中,重绘时便可以将每个图形再次画出来。
下面是我关于直线的重绘机制的相关代码:
//设置重绘存储图形的数组
	private Shape [] shapeArray = new Shape[10000];
public void paint(Graphics g){
		super.paint(g);//使基类的paint方法可用
		
		for(int i = 0; i < shapeArray.length; i++){
			if(null != shapeArray[i]){
				Shape shape = shapeArray[i];
				if(shape.Type.equals("直线")){
					
					//设置线条粗细
		    		((Graphics2D) g).setStroke(shape.s);
					g.setColor(shape.Color);
					g.drawLine(shape.x1, shape.y1, shape.x2, shape.y2);
					
				}
}

其中使基类的pain()方法可用,即是系统用于将我们的窗体绘制出来的方法。

上面便是我在进行Java图形界面开发练习,制作绘图板时的一下学习过程,在这里和大家分享下。

新手入Java,本文旨在记录下我的Java学习历程,并将自己学到的一些东西分享给大家。相关概念与理解或许略有偏差,希望大家能给我指出。















你可能感兴趣的:(Java)