一,为什么要重绘?
在上一篇博客中,我们实现了画板的功能,但是我们发现如果将画板窗体从屏幕中移除或者改变画板窗体大小时,会发现原来在画板上画的图形消失了,这就降低了用户体验,那么出现这种现象的原因是什么呢?
二,为什么会出现图形消失的原因?
首先我们要明白的是当我们将画板窗体移除或者改变画板窗体大小的时候都是画板的Frame框架在不断调用paint()方法。我们写的代码中有给Frame框架增加组件的部分,而这些组件包括框架就是通过paint方法将这些组件“画”到了我们的电脑屏幕上才让我们看到了这些组件。那么当我们将画图板最小化、将画图板拖到边缘或者改变画图板大小的时候,都是画图板的可视化部分在改变的时候。既然画图板可视部分要改变了,就必须通过重新“画”一个新的面板上去才能实现状态的改变,这就是paint方法调用的原因。而paint方法是一个java早已经定义好的一个方法,java设计者并不知道我们用paint方法是来画什么图形的,设计者在设计之初只定义了用paint方法把这些组件画出来了,却并没有画我们自己创造的这些图形的这个部分。因此当程序自动调用paint方法时,就没有实现我们之前画的图形的可视化过程,只实现了框架组件的可视化过程,这就是为什么会出现已画图形消失的原因。
三,如何重写paint()方法达到重绘效果
1,画图+保存的准备工作
我们绘制一个图形需要一些属性和方法,以直线为例我们需要知道它的起点坐标、终点坐标、颜色,最重要的还有名字——直线,然后知道这些属性之后就可以调用相应的方法绘制出直线。所以我们以直线为例,所有只需要起点坐标x1、y1,终点坐标x2、y2,类型type,颜色color这六个属性就能调用不同方法绘制的图形都用Shape类来保存。
package com.yzd1209.DrawRepaint;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.Random;
public class Shape{
//将所有与图形相关的信息保存再Repaint类中 包括属性和方法
int x1,y1,x2,y2;
Color color;
String type;
Graphics g;
public Shape() {
//无参构造方法 方便子类重写
}
public Shape(int x1,int y1,int x2,int y2,String type,Color color) {
//有参构造方法
// xDown yDown xUp yUp Shape中x1与DrawListener中坐标的关系???
this.x1=x1;//?????
this.y1=y1;
this.x2=x2;
this.y2=y2;
this.type=type;
this.color=color;
}
public void show(Graphics g) {
// TODO Auto-generated method stub
g.setColor(color);//获取画笔颜色 这样颜色也能重绘
if("直线".equals(type)) {
g.drawLine(x1,y1,x2,y2);
}else if("矩形".equals(type)) {
if(x1<x2) {
g.drawRect(x1,y1, x2-x1,y2-y1);
}else {
g.drawRect(x1-(x1-x2),y1-(y1-y2), Math.abs(x2-x1), Math.abs(y2-y1));
}
}else if("立体图".equals(type)) {
for(int i=0;i<100;i++) {
g.setColor(new Color(i*2, 100+i, i*2+50));
g.fillOval(x1+i/3, y1+i/3, 100-i, 100-i);
}
}else if("分形图".equals(type)) {
double a=-1.4,b=1.6,c=1.0,d=0.7;
double x=0.0,y=0.0;
for(int i=0;i<1000;i++) {
//System.out.println(i);
double temx = Math.sin(a*y)+c*Math.cos(a*x);
double temy = Math.sin(b*x)+d*Math.cos(b*y);
x=temx;
y=temy;
int drawx = (int)(temx*50+x1);
int drawy = (int)(temy*50+y1);
//g.setColor(new Color(0,0,i/100));
g.drawLine(drawx, drawy, drawx, drawy);
}
}else if("概率画图".equals(type)) {
double a,b,c,d,e1,f;
double x=0.0,y=0.0;
Random random = new Random();
for(int i=0;i<1000;i++) {
int k=random.nextInt(5);
if(k==0) {
a=0.1950;b=-0.4880;c=0.3440;d=0.4430;e1=0.4431;f=0.2452;
double temx=a*x+b*y+e1;
double temy=c*x+d*y+f;
int drawx = (int)(temx*500+x1);
int drawy = (int)(temy*500+y1);
g.drawLine(drawx, -drawy+800, drawx, -drawy+800);
x=temx;
y=temy;
//System.out.println(drawx+" "+drawy);
}else if(k==1) {
a=0.4620;b=0.4140;c=-0.2520;d=0.3610;e1=0.2511;f=0.5692;
double temx=a*x+b*y+e1;
double temy=c*x+d*y+f;
int drawx = (int)(temx*500+x1);
int drawy = (int)(temy*500+y1);
g.drawLine(drawx, -drawy+800, drawx, -drawy+800);
//System.out.println(drawx+" "+drawy);
x=temx;
y=temy;
}else if(k==2) {
a=-0.6370;b=0.0000;c=0.0000;d=0.5010;e1=0.8562;f=0.2512;
double temx=a*x+b*y+e1;
double temy=c*x+d*y+f;
int drawx = (int)(temx*500+x1);
int drawy = (int)(temy*500+y1);
g.drawLine(drawx, -drawy+800, drawx, -drawy+800);
//System.out.println(drawx+" "+drawy);
x=temx;
y=temy;
}else if(k==3) {
a=-0.0350;b=0.0700;c=-0.4690;d=0.0220;e1=0.4884;f=0.5069;
double temx=a*x+b*y+e1;
double temy=c*x+d*y+f;
int drawx = (int)(temx*500+x1);
int drawy = (int)(temy*500+y1);
g.drawLine(drawx, -drawy+800, drawx, -drawy+800);
//System.out.println(drawx+" "+drawy);
x=temx;
y=temy;
}else if(k==4) {
a=-0.0580;b=-0.0700;c=0.4530;d=-0.1110;e1=0.5976;f=0.0969;
double temx=a*x+b*y+e1;
double temy=c*x+d*y+f;
int drawx = (int)(temx*500+x1);
int drawy = (int)(temy*500+y1);
g.drawLine(drawx, -drawy+800, drawx, -drawy+800);
//System.out.println(drawx+" "+drawy);
x=temx;
y=temy;
}
}
}else if("曲线".equals(type)) {
g.drawLine(x1, y1, x2, y2);
}else if("三点分形".equals(type)) {
g.drawLine(x1, y1, x2, y2);
}
}
}
这样所有有关图形的信息都在Shape类里了,再调用Shape对象中的show方法就可以实现图像的可视化。
2,将图像保存到Shape对象中
由于每个图像对象的具体信息都不一样,所以要对每个图像对象都创建一个Shape对象。
以画直线为例,我们在DrawListener类中定义一个数组用来保存图形
int count=0;//记录图像个数
Shape[] data=new Shape[100000];//创建一个保存图像的数组
然后定义一个方法NewShape(),该方法创建了一个Shape类型的对象s,将外界传入的属性参数赋给了对象的属性,将对象s保存到了我们之前定义的Shape类型的数组data中。
public void NewShape(int x1,int y1,int x2,int y2,String type,Color color) {
//保存图像有关信息
Shape s = new Shape();//存储的是四个点???分别对应哪四个点
s.x1=x1;
s.y1=y1;
s.x2=x2;
s.y2=y2;
s.type=Bname;
s.color=color;
data[count]=s;
count++;
}
然后我们在画直线的时候,当绘图完成后就调用这个方法,传入我们需要保存的参数保存图形。
if(Bname.equals("直线")) {
System.out.println(Bname);
g.drawLine(xDown, yDown, xUp, yUp);
NewShape(xDown,yDown,xUp,yUp,"直线",color);
}
3,保存对象之后输出
之前我们已经提到了java设计者设计paint()方法时,只实现的组件(如按钮之类)的可视化,并没有实现所化图像的可视化,于是我们要做的就是重写paint()方法,让按钮可视化的同时图形也可视化。
方法的重写是建立在继承的关系上的——extends,我们创建一个DrawFrame类继承Frame类来重写paint()方法。
package com.yzd1209.DrawRepaint;
import java.awt.Graphics;
import javax.swing.JFrame;
public class DrawFrame extends JFrame{
Shape [] data;//图像对象的数组 包含6个类型的数据x1 y1 x2 y2 type color
DrawListener dlistener=null;//声明一个DrawListener类型的对象 默认初始化为null ???
public void paint(Graphics g) {
//重写paint方法
super.paint(g);
//调用父类的paint方法,绘制组件 自动绘制按钮等组件
//画图的paint方法正在重写(因为当窗体移出桌面时不会自动调用,要实现和组件同时出现就要在调用paint方法时一起自动调用)
data=dlistener.data;//将dlistener.data(即定义的100000大小的数组)传给data ????
for(int i=0;i<data.length;i++) {
//遍历数组
if(data[i]!=null) {
data[i].show(g);//调用每种图形对应的方法画图,这样就能在展示按钮的同时展示图像
}
else {
break;
}
}
System.out.println("paint");
}
}
在重写的过程中,我们仍然要调用父类Frame的paint()方法,使用关键字super,并且该行代码要写在重写方法的第一行,因为我们仍然需要实现按钮组件的可视化。
super.paint(g);
然后定义一个shape类型的数组data,声明一个DrawListener类型的对象dlisterner,由于dlisterner是一个DrawListener类型的对象,在DrawListener类中我们定义了一个data数组用来保存图形信息,将dlisterner保存到的图形相关信息data传给data,同时由于data数组中并不是每个元素都有数据,因此需要遍历数组,将有数据的元素显示出来,这样data就能调用Shape类中的show()方法再一次绘制图形——即图形的重绘。
4,Board类修改JFrame
由于DrawFrame类继承了JFrame类,所以在创建窗体对象的时候,创建的窗体的对象应该为DrawFrame类。
DrawFrame jf = new DrawFrame();
至此,我们就完成了直线、矩形、分形图、立体图、概率画图、三点分形的重绘。
四,不同图形的重绘
之前我们提到了,不同图形需要保存的数据不一样,但是可以创建一个新的类来继承shape类,重写其中的构造方法,来达到保存不同类型数据的功能。
我们之前在shape类中定义了一个空的构造方法,空的构造方法的作用就是方便子类重写。
public Shape() {
//无参构造方法 方便子类重写
}
以递归直线为例,我们定义了一个RecurShape类,继承shape类,在其中重写了RecurShape()方法。
public class RecurShape extends Shape{
int n;//递归直线递归次数 与比shape类多需要保存的数据
public RecurShape(int x1,int y1,int x2,int y2,String type,Color color,int n) {
super(x1,y1,x2,y2,type,color);
this.n=n;
}
然后同样定义一个show方法画图
public void show(Graphics g) {
if (type.equals("递归直线")) {
g.setColor(color);//设置画笔颜色 使颜色也能重绘
MyDrawLine(x1,y1,x2,y2,g,6);
}
}
这里我们将画递归直线的具体方法从DrawListener类中拿出放在了RecurShape类中。
public void MyDrawLine(int x1,int y1,int x2,int y2,Graphics g,int n) {
//自定义方法递归画直线 n为递归结束条件
if(n==0) {
//递归终止条件
return;//终止处理
}
g.drawLine(x1, y1, x2, y2);//初始线
int xL1=x1;int yL1=y1+40;int xL2=x1+(x2-x1)/3;int yL2=y1+40;//计算左三等分线坐标
MyDrawLine(xL1,yL1,xL2,yL2,g,n-1);//递归调用,每调用一次n-1
//System.out.println(n);
int xR1=x2-(x2-x1)/3;int yR1=y2+40;int xR2=x2;int yR2=y2+40;//计算右三等分线坐标
MyDrawLine(xR1,yR1,xR2,yR2,g,n-1);//递归调用,每调用一次n-1
//System.out.println(n);
}
在DrawListener类中画递归直线的部分同样创建一个RecurShape类型的对象shapeRecur,这个对象可以调用RecurShape类型中的show方法(show方法再调用画递归直线的具体方法MyDrawLine)实现图形的绘制,绘制完成之后将数据保存。
else if(Bname.equals("递归直线")) {
//MyDrawLine(6,xDown,yDown,xDown+600,yDown);//三等分画线,6为递归结束条件
RecurShape shapeRecur = new RecurShape(xDown,yDown,xUp+600,yUp,Bname,color,6);
shapeRecur.show(g);
//shapeRecur.MyDrawLine(xDown,yDown,xDown+600,yDown,g,6);
data[count]=shapeRecur;
count++;
}
最后同样将保存到的数据展示出来(上文中同一段代码)。
public void paint(Graphics g) {
//重写paint方法
super.paint(g);
//调用父类的paint方法,绘制组件 自动绘制按钮等组件
//画图的paint方法正在重写(因为当窗体移出桌面时不会自动调用,要实现和组件同时出现就要在调用paint方法时一起自动调用)
data=dlistener.data;//将dlistener.data(即定义的100000大小的数组)传给data ????
for(int i=0;i<data.length;i++) {
//遍历数组
if(data[i]!=null) {
data[i].show(g);//调用每种图形对应的方法画图,这样就能在展示按钮的同时展示图像
}
else {
break;
}
}
System.out.println("paint");
}
其他图形的重绘方法类似,就不一一列举了,完整代码参考如下:
https://blog.csdn.net/weixin_43722843/article/details/111052694