上期的绘图板提到了可能会出现一堆的问题,在上期的代码中也可以看出一些端倪,这次本人会讲述发现的问题及解决方案。
我们先打看之前制作的绘图板,简单地绘制几个图形,然后将程序最小化(也可以拖动到屏幕范围外),再还原,会发现我们绘制的图形会消失,同时,改变窗体大小也会导致这个情况。这是一个致命的问题,严重影响用户体验。
发现问题肯定是需要找到原因的,其原因是我们使用的JFrame与JPanel两个控件在上述操作中均调用了方法paint(),会刷新上面的组件,因此我们在绘图板上绘制的图像会被刷新。
解决方案是比较简单的,我们可以预先将我们绘制的图形存起来,通过重写paint()方法来完成。
因此,我们需要让主类继承JFrame,或者JPanel(本人是继承JPanel)。
任务完成:回收前文伏笔!
然后重绘之前画的图形
public void paint(Graphics g) {
super.paint(g);
(重绘的代码)
}
这好像说了和没说一样,我们应该从哪里获取我们绘制的图形呢?没有人帮你存,计算机也不会帮你,于是我们应该自己动手来存。
但是,我们存的图形要使用什么数据类型呢?这里我们最好创建一个新的类(可以使用字符串,但是比较硬核麻烦,不推荐)。
这里我创建了一个Shape类:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
public class Shape {
private int x1, x2, y1, y2;
private String str;
private Stroke st;
private Color c=Color.BLACK;//默认为黑色
private boolean em;
/**
* 这是Shape类的构造函数
* @param g 传入要在上面绘制的绘图板。
* @param x1 传入绘制时的起点x坐标。
* @param y1 传入绘制时的起点y坐标。
* @param x2 传入绘制时的终点x坐标。
* @param y2 传入绘制时的终点y坐标。
* @param str 传入图形
* @param c 传入颜色
* @param st 传入粗细
* @param em 传入是否空心
*/
Shape(Graphics g, int x1, int y1, int x2, int y2, String str, Color c, Stroke st, boolean em) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.str = str;
this.c = c;
this.st = st;
this.em = em;
}
/**
* 这是重绘该图形的方法,可以写在主函数的重绘方法内部。
* @param g 传入要在上面绘制的绘图板。
*/
public void rePaint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setStroke(st);
g.setColor(c);
if (str.equals("直线")) {
g.drawLine(x1, y1, x2, y2);
} else if (str.equals("矩形")) {
if (em) g.drawRect(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
else g.fillRect(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
} else if (str.equals("圆形")) {
if (em) g.drawOval(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(x1 - x2));
else g.fillOval(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(x1 - x2));
} else if (str.equals("椭圆")) {
if (em) g.drawOval(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
else g.fillOval(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
} else if (str.equals("三角形")) {
if (em) {
} else {
int[] x = { Math.min(x1, x2), Math.max(x1, x2), Math.min(x1, x2) + Math.abs(x1 - x2) / 2,
Math.min(x1, x2) };
int[] y = { Math.max(y1, y2), Math.max(y1, y2), Math.min(y1, y2), Math.max(y1, y2) };
g.fillPolygon(x, y, 4);
}
}
}
}
这里处理可以比较灵活,比如将rePaint方法写在paint()内,或者只存一些数据,剩下的数据用数组存等等;这些不一一阐述。
然后,我们创建一个数组,我们再在每次绘制图形的时候将图形的属性存在类中,再存入数组。
public void mouseReleased(MouseEvent e) {
// 在鼠标释放时绘制出相应的图形。
if (str.equals("直线")) {
x2 = e.getX();
y2 = e.getY();
g.drawLine(x1, y1, x2, y2);
// 此方法是在两点之间绘画出直线。
sp[++len] = new Shape(g, x1, y1, x2, y2, "直线", c, st, em);//存入直线
} else if (str.equals("多边形")) {
// 绘制多边形,原理是释放点和上一个点之间绘制直线。
//多边形则是存储多条直线
if (b) {
x2 = e.getX();
y2 = e.getY();
g.drawLine(x1, y1, x2, y2);
sp[++len] = new Shape(g, x1, y1, x2, y2, "直线", c, st, em);//存入直线
b = !b;// 使起点x1,y1保持不变。
} else if (e.getClickCount() == 1) {
// 在鼠标点击数为1时(单击),会将当前位置与上一个位置的点连接。
g.drawLine(x2, y2, e.getX(), e.getY());
sp[++len] = new Shape(g, x2, y2, e.getX(), e.getY(), "直线", c, st, em);//存入直线
x2 = e.getX();
y2 = e.getY();
// 更换上一个点。
} else if (e.getClickCount() == 2) {
// 双击时,将鼠标位置点与上一个点及起点相连。
g.drawLine(x2, y2, e.getX(), e.getY());
sp[++len] = new Shape(g, x2, y2, e.getX(), e.getY(), "直线", c, st, em);//存入直线
g.drawLine(x1, y1, e.getX(), e.getY());
sp[++len] = new Shape(g, x1, y1, e.getX(), e.getY(), "直线", c, st, em);//存入直线
b = !b;// 多边形绘制完成,使起点可以改变位置。
}
} else if (str.equals("矩形")) {
x2 = e.getX();
y2 = e.getY();
if (em)
g.drawRect(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
// 绘制一个空心的矩形,参数为起点x1,y1,宽度,高度。
else
g.fillRect(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
// 填充一个实心矩形,其他同上述。
sp[++len] = new Shape(g, x1, y1, x2, y2, "矩形", c, st, em);//存入矩形
} else if (str.equals("圆形")) {
x2 = e.getX();
y2 = e.getY();
if (em)
g.drawArc(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(x1 - x2), 0, 360);
// 同矩形,但是为画椭圆,为了达成圆的效果,因此宽高一致。
else
g.fillOval(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(x1 - x2));
// 填充一个实心椭圆。
sp[++len] = new Shape(g, x1, y1, x2, y2, "圆形", c, st, em);//存入圆形
} else if (str.equals("椭圆")) {
x2 = e.getX();
y2 = e.getY();
if (em)
g.drawArc(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2), 0, 180);
// 画椭圆
else
g.fillOval(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
// 填充椭圆
sp[++len] = new Shape(g, x1, y1, x2, y2, "椭圆", c, st, em);//存入椭圆
} else if (str.equals("三角形")) {
x2 = e.getX();
y2 = e.getY();
int x[] = { x1, x2, Math.min(x1, x2) + Math.abs(x1 - x2) / 2, x1 };
int y[] = { y1, y1, y2, y1 };
if (em) {
//空心三角形可以视作三条直线
for (int i = 0; i < 3; i++) {
g.drawLine(x[i], y[i], x[i + 1], y[i + 1]);
// 空心三角形可以视为三条线组成。
sp[++len] = new Shape(g, x[i], y[i], x[i + 1], y[i + 1], "直线", c, st, em);//存入直线
}
} else {
g.fillPolygon(x, y, 4);
// 这个方法会依次按照x,y数组一一对应的位置首位前4位依次连线产生的多边形进行填充。
sp[++len] = new Shape(g, x1, y1, x2, y2, "三角形", c, st, em);
}
}
}
public void mouseDragged(MouseEvent e) {
if (str != null)
if (str.equals("橡皮擦")) {
g.setColor(Color.WHITE);
g.fillOval(e.getX() - 10, e.getY() - 10, 20, 20);
// 在鼠标拖动的时候画与背景颜色一致的圆覆盖图形已达到橡皮擦的效果。
sp[++len] = new Shape(g, e.getX() - 10, e.getY() - 10, e.getX() + 10, e.getY() + 10, "圆形", Color.WHITE,
st, em);//存入圆形
} else if (str.equals("画笔")) {
g.setColor(c);
g.drawLine(x1, y1, e.getX(), e.getY());
// 不停的在鼠标移动的极微小的两点之间画线以达到画笔的效果。(也可以在鼠标位置画圆)
sp[++len] = new Shape(g, x1, y1, e.getX(), e.getY(), "直线", c, st, em);//存入直线
x1 = e.getX();
y1 = e.getY();
// 更新鼠标上一个位置。
}
}
//修改过的两个主要方法
最后,我们在paint()方法中将其绘制出来,就完成了重绘。
public void paint(Graphics g) {
//要预先获取监听器类中的形状数组,否则什么都绘制不出来
super.paint(g);///不调用的话,上面的控件也绘制不出来。
for (int i = 0; i < sp.length; i++) {
if (sp[i] == null)
break;
sp[i].rePaint(g);//重绘组件的画笔和取的画笔不一样,不能共用
}
}
详细源码,及本人编写的老版本:https://github.com/IamA1536/JFrame.git
绘图板到此可以说差不多结束了,至于其他的拓展就不在阐述了。这是Java学习的第一个项目,也是比较有意义的项目,但这不是一个结束,是一个新的开始;接下来我还会分享更多的项目,来记录不断学习的过程。