ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

图形重绘的实现

图形重绘的实现 图形重绘的实现图形重绘是计算机图形学中的一个重要概念指在保持图形基本结构和语义不变的前提下对现有图形进行重新绘制、优化或转换的过程。这一技术广泛应用于图像处理、UI设计、数据可视化、游戏开发等领域。原画图工具的缺陷及改进目标在原有的画图工具中当我们把窗体最小化或改变窗体的大小时原来在画板上绘制的图形无法保留下来直接消失了。这是因为之前调用的是Graphics临时绘图绘制的图形没有进行持久化的保存当最小化或者改变窗口大小时组件触发重绘事件系统会清空画布没有保存数据时无法重新绘制图像。组件的重绘机制1.重绘触发条件所有 Swing 组件如 JFrame、JPanel均具备paint方法。当组件状态发生改变如隐藏、显示、改变大小时系统会自动调用该方法进行重绘。2.图形消失原因通过监听器如鼠标监听绘制的图形并未被组件本身记录。当组件重绘时原有的图形痕迹被擦除而监听器绘制的图形由于未保存数据无法在重绘时恢复。解决方案1.数据持久化需求为确保图形在重绘后依然存在必须将图形数据如坐标、类型保存至内存中。2.两步走策略第一步是将图形数据封装并保存第二步是在组件重绘时从内存中取出数据并重新绘制。图形重绘的实现思路1.为解决多类型的图形数据存储这里运用了面向对象编程的思路1属性封装创建Shape类封装图形名称name、起始点坐标x1, y1及结束点坐标x2, y2。通过类封装将不同类型的数据打包成一个对象便于统一管理。注单纯存储坐标无法区分图形类型如线段或矩形因此必须存储图形名称用于后续还原时的逻辑判断。2 数据存储结构由于需要存储多个Shape对象采用数组作为数据结构。数组类型定义为Shape[]以确保能存储同类型的对象引用。3数组操作定义数组下标变量index在鼠标松开Release事件发生时将新创建的Shape对象存入数组并递增下标。2.重绘功能的实现自定义面板与重写通过继承与重写机制实现图形数据的持久化重绘1继承JPanel创建MPanel类继承JPanel并重写paint方法。在该方法中首先调用super.paint(g)保留原有组件绘制功能然后增加自定义的图形绘制逻辑。2数据传递通过引用传递将主类中存储图形对象的数组传递给MPanel类确保重绘时能访问到已保存的数据。重绘图形的代码演示DrawListener类设计思路事件监听与图形数据采集作为鼠标和动作事件监听器负责捕获用户绘图操作图形数据封装在鼠标事件中创建Shapes对象封装图形类型、坐标、颜色、粗细等属性数据持久化存储使用数组存储所有绘制的图形对象实现图形数据的持久化保存实时绘制与数据记录分离在绘制图形的同时将图形数据存入数组为后续重绘做准备多图形类型支持通过name属性区分不同图形类型实现统一的处理逻辑下面通过java示例演示如何使用图形重绘库进行基本操作DrawListener类package Drawtoolsix; import java.awt.*; import java.awt.event.*; /** * author jinhong * date 2026/7/12 20:53 * description */ public class DrawListener6 implements MouseListener, ActionListener, MouseMotionListener { public Graphics gr;//保存画笔对象 public int x1, x2, x3, y1, y2, y3,x4,y4; public String name; public boolean flag true; public int strokeSize4; public Color colorColor.BLACK; //保存图形对象的数组默认是null public Shapes[]shapeArrnew Shapes[1000]; //设置数组初始下标为0 public int index0; // 动作事件处理 - 设置图形类型和样式 public void actionPerformed(ActionEvent e) { //获取按钮的内容 name e.getActionCommand(); System.out.println(点击按钮name name); if (蓝色.equals(name)) { color Color.BLUE; } if (红色.equals(name)) { color Color.RED; } if(粗线.equals(name)){ strokeSize9; } if(细线.equals(name)){ strokeSize2; } if(橡皮擦.equals(name)){ } flag true; Graphics2D g2(Graphics2D)gr; g2.setStroke(new BasicStroke(strokeSize)); } // 鼠标点击事件 - 处理三角形和多边形的特殊绘制逻辑 public void mouseClicked(MouseEvent e) { x3e.getX(); y3e.getY(); if (三角形.equals(name)) { // 三角形需要三个点点击第三点时完成绘制并保存数据 Shapes shapenew Shapes(color,strokeSize,name,x1,y1,x2,y2,x3,y3); shapeArr[index]shape; index; gr.drawLine(x1, y1, x3, y3); gr.drawLine(x2, y2, x3, y3); flag true; } if(多边形.equals(name)){ // 多边形绘制 - 每次点击添加一条边双击闭合多边形 Shapes shape new Shapes(color, strokeSize ,name, x2, y2, x3, y3); shapeArr[index] shape; index; gr.drawLine(x2, y2, x3, y3); x2 x3; y2 y3; if( e.getClickCount()2) { // 双击时闭合多边形并保存最后一条边 Shapes close new Shapes(color, strokeSize, name, x1, y1, x3, y3); shapeArr[index] close; index; gr.drawLine(x1, y1, x3, y3); flag true; } } } // 鼠标按下事件 - 记录起始点坐标 public void mousePressed(MouseEvent e) { System.out.println(按下); if (flag true) { x1 e.getX(); y1 e.getY(); } if (曲线.equals(name)){ x4e.getX(); y4e.getY(); } if(橡皮擦.equals(name)){ // 橡皮擦实现 - 使用白色线条覆盖原有图形 x4e.getX(); y4e.getY(); Graphics2D g2(Graphics2D)gr; g2.setStroke(new BasicStroke(15)); gr.setColor(Color.WHITE); x1x4; y1y4; } } // 鼠标释放事件 - 完成图形绘制并保存数据到数组 public void mouseReleased(MouseEvent e) { System.out.println(松开); if (flagtrue) { x2 e.getX(); y2 e.getY(); System.out.println(x2 x2 y2 y2); } gr.setColor(color); int w Math.abs(x2 - x1); int h Math.abs(y2 - y1); int X1 Math.min(x1, x2); int Y1 Math.min(y1, y2); // 直线绘制与数据保存 if(直线.equals(name)){ Shapes shapenew Shapes(color,strokeSize,name,x1,y1,x2,y2); //按顺序把图形对象保存到数组中 shapeArr[index]shape; index; } // 矩形绘制与数据保存 if(矩形.equals(name)){ Shapes shapenew Shapes(color,strokeSize,name,x1,y1,x2,y2); shapeArr[index]shape; index; } // 等腰三角形特殊计算与绘制 if(等腰三角形.equals(name)){ int RX 2 * x1 - x2; //计算等腰三角形第三个点的x坐标 Shapes shapenew Shapes(color,strokeSize,name,x1,y1,x2,y2,RX); gr.drawLine(x1, y1, x2, y2); gr.drawLine(x1, y1, RX, y2); gr.drawLine(x2, y2, RX, y2); shapeArr[index]shape; index; } // 椭圆绘制与数据保存 if (椭圆.equals(name)) { int o Math.abs(x2 - x1); int p Math.abs(y2 - y1); int Z1 Math.min(x1, x2); int Q1 Math.min(y1, y2); gr.drawOval(Z1, Q1, o, p); Shapes shapenew Shapes(color,strokeSize,name,x1,y1,x2,y2); shapeArr[index]shape; index; } if (三角形.equals(name)) { gr.drawLine(x1, y1, x2, y2); flag false; } if(多边形.equals(name) flagtrue){ Shapes shapenew Shapes(color,strokeSize,name,x1,y1,x2,y2); shapeArr[index]shape; index; gr.drawLine(x1, y1, x2, y2); flag false; } } // 鼠标拖拽事件 - 实现实时绘制效果 public void mouseDragged(MouseEvent e){ if(多边形.equals(name)){ return; } // 直线拖拽效果 - 先擦除旧线再绘制新线 if (直线.equals(name)) { gr.setColor(Color.WHITE); gr.drawLine(x1, y1, x4,y4); x4e.getX(); y4e.getY(); gr.setColor(color); gr.drawLine(x1,y1,x4,y4); } int w Math.abs(x2 - x1); int h Math.abs(y2 - y1); int X1 Math.min(x1, x2); int Y1 Math.min(y1, y2); // 矩形拖拽效果 - 实时更新矩形大小 if (矩形.equals(name)) { gr.setColor(Color.WHITE); gr.drawRect(X1, Y1, w, h); x2e.getX(); y2e.getY(); w Math.abs(x2 - x1); h Math.abs(y2 - y1); X1 Math.min(x1, x2); Y1 Math.min(y1, y2); gr.setColor(color); gr.drawRect(X1, Y1, w, h); } // 曲线绘制 - 连续记录点并连接成线 if(曲线.equals(name)){ x4e.getX(); y4e.getY(); Shapes shapenew Shapes(color,strokeSize,name,x1,y1,x4,y4); shapeArr[index]shape; index; gr.drawLine(x1,y1,x4,y4); x1x4; y1y4; } // 橡皮擦拖拽 - 用白色曲线覆盖 if(橡皮擦.equals(name)){ x4e.getX(); y4e.getY(); Shapes shapenew Shapes(color.WHITE,15,曲线,x1,y1,x4,y4); shapeArr[index]shape; index; gr.drawLine(x1,y1,x4,y4); x1x4; y1y4; } } public void mouseMoved(MouseEvent e){ } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } }UI类设计思路界面布局管理使用BorderLayout布局管理器将功能按钮区和绘图区分开组件初始化与配置创建JFrame窗口设置面板组件和功能按钮事件监听器绑定将DrawListener监听器绑定到按钮和绘图面板数据传递桥梁获取Graphics对象传递给监听器并将图形数组传递给MPanel面板主程序入口提供main方法作为程序启动入口UI类package Drawtoolsix; import javax.swing.*; import java.awt.*; /** * author jinhong * date 2026/7/12 20:54 * description */ public class DrawUI6 { // UI初始化方法 - 创建窗口、布局和组件 public void initUI() { JFrame jf new JFrame(); jf.setSize(800, 800); jf.setTitle(画图工具); jf.setLocationRelativeTo(null); jf.setDefaultCloseOperation(3); jf.getContentPane().setBackground(Color.WHITE); //流式布局管理器 // FlowLayout flownew FlowLayout(); //jf.setLayout(flow); //设置边界布局管理器 BorderLayout bordernew BorderLayout(); jf.setLayout(border); // 创建顶部按钮面板 JPanel northPanelnew JPanel(); northPanel.setBackground(Color.PINK); Dimension dmnew Dimension(0,60); northPanel.setPreferredSize(dm); jf.add(northPanel,BorderLayout.NORTH); DrawListener6 listenernew DrawListener6(); // 使用数组简化按钮创建和事件绑定 String[]arr{直线,矩形,等腰三角形,椭圆,三角形,多边形,曲线,橡皮擦,粗线,细线,蓝色,红色}; for (int i0;iarr.length;i){ JButton jbunew JButton(arr[i]); northPanel.add(jbu); jbu.addActionListener(listener); } // 创建自定义绘图面板 MPanel drawPanelnew MPanel(); //更改jpanel为MPanel drawPanel.setBackground(Color.WHITE); jf.add(drawPanel,BorderLayout.CENTER); jf.setVisible(true); // 获取Graphics对象并绑定事件监听器 Graphics g drawPanel.getGraphics(); drawPanel.addMouseListener(listener); drawPanel.addMouseMotionListener(listener); // 传递画笔对象和图形数组建立数据桥梁 listener.gr g; //把shapeArr数组从DrawListener类传递到MPanel类中 drawPanel.shapeArr listener.shapeArr; } public static void main(String[]args){ DrawUI6 uinew DrawUI6(); ui.initUI(); } }Shapes类设计思路图形数据封装封装所有图形绘制所需的属性坐标、类型、颜色、粗细等统一数据模型为不同类型的图形提供统一的数据存储结构图形还原方法提供drawShape方法根据保存的数据重新绘制图形多态性设计通过switch语句根据图形类型调用不同的绘制逻辑状态保存与恢复保存绘制时的画笔状态颜色、粗细确保重绘时效果一致Shapes类package Drawtoolsix; import java.awt.*; import java.awt.event.MouseEvent; /** * author jinhong * date 2026/8/7 22:01 * description */ //根据图形对象设计对应的封装类Shapes public class Shapes { // 图形属性封装 - 存储所有绘制所需的数据 public int x1,y1,x2,y2,x3,y3,X1,Y1,w,h,RX; public String name; public Graphics g; public boolean flag true; public Color color; public int strokeSize; // 图形还原方法 - 根据保存的数据重新绘制图形 public void drawShape(Graphics g){ Graphics2D g2(Graphics2D)g; //还原画笔颜色 g2.setColor(color); //还原画笔粗细 g2.setStroke(new BasicStroke(strokeSize)); // 多态性设计 - 根据图形类型调用不同的绘制逻辑 switch (name) { case 直线: g.drawLine(x1, y1, x2, y2); break; case 矩形: g.drawRect(X1, Y1, w, h); break; case 等腰三角形: g.drawLine(x1, y1, x2, y2); g.drawLine(x1, y1, RX, y2); g.drawLine(x2, y2, RX, y2); break; case 椭圆: g.drawOval(X1, Y1, w, h); break; case 三角形: // 三角形需要绘制三条边 g.drawLine(x1, y1, x3, y3); g.drawLine(x2, y2, x3, y3); g.drawLine(x1, y1, x2, y2); break; case多边形: // 思路多边形只保存单条边重绘时由多条边组成 g.drawLine(x1, y1, x2, y2); break; case 曲线: // 曲线需要恢复画笔状态 g.setColor(this.color); ((Graphics2D) g).setStroke(new BasicStroke(this.strokeSize)); g.drawLine(x1, y1, x2, y2); break; } } }MPanel类设计思路自定义绘图面板继承JPanel并重写paint方法实现自定义绘制逻辑图形数据传递接收并存储从DrawListener传递过来的图形数组重绘机制实现在paint方法中遍历图形数组调用每个图形的drawShape方法空值安全处理在遍历数组时进行非空判断避免NullPointerException组件功能保留调用super.paint(g)保留JPanel原有的组件绘制功能MPanel类package Drawtoolsix; import javax.swing.*; import java.awt.*; /** * author jinhong * date 2026/8/7 22:02 * description */ public class MPanel extends JPanel { // 数据传递 - 保存从DrawListener传递过来的图形数组 public Shapes []shapeArr; // 重写paint方法 - 实现自定义重绘逻辑 public void paint(Graphics g){ //使用类的继承保留绘制组件的功能 super.paint(g); System.out.println(paint); // 遍历图形数组并重绘所有保存的图形 for(int i0;ishapeArr.length;i){ Shapes shapeshapeArr[i]; // 空值安全处理 - 避免NullPointerException if(shapenull) break; // 调用每个图形的drawShape方法进行重绘 shape.drawShape(g); } } }问题与反思在进行图形重绘时需要注意以下关键点1.NullPointerException报错在遍历数组绘制图形时必须进行非空判断if (shape ! null)避免因数组未初始化或空元素导致的NullPointerException。2.遍历绘制逻辑通过循环遍历数组取出每个Shape对象并根据其name属性使用switch语句调用相应的绘制方法还原图形。3.编写多边形绘制图形时长期困惑鼠标点击传入全局变量x2,y2,x3,y3时为什么在Shapes绘图模块时只用g.drawLine(x1,y1,x2,y2)能够正确画出图形而不需要像绘制三角形那样将剩下两条线画出来。问题根源误以为变量名称存在绑定关系难以理解参数的传递。混淆三层作用域的同名变量错误认为只有变量名称一致才能够对应赋值。DrawListener全局变量 x1,y1,x2,y2,x3,y3 鼠标点击产生的坐标Shapes构造方法形参 x1,y1,x2,y2 仅方法内有效Shapes类成员变量 this.x1,this.y1,this.x2,this.y2 图形持久存储坐标正确代码javanew Shapes(color, strokeSize ,name, x2, y2, x3, y3);实参按顺序传递给构造器形参全局 x2 → 形参 x1全局 y2 → 形参 y1全局 x3 → 形参 x2全局 y3 → 形参 y2构造器执行赋值this.x1 x1; this.y1 y1; this.x2 x2; this.y2 y2;最终外部传入的 x2,y2,x3,y3 保存在对象内部 this.x1,this.y1,this.x2,this.y2 。绘图代码 g.drawLine(x1,y1,x2,y2) 读取对象内部坐标完成绘制。总结该项目基于Java Swing实现交互式绘图功能通过自定义Shapes类封装各图形参数将图形存入数组并实现重绘持久化。开发难点在于理解方法的传参规则即实参依靠顺序与形参匹配不受变量名的影响。多边形、曲线及橡皮擦的绘制采用逐段生成线段的方法将顺时鼠标坐标保存到对象内部避免全局变量覆盖而造成线条错乱。本次的实践结合了之前所学的诸多知识例如数组、类的继承。深刻认识到区分变量作用域的重要性。后续编写图形程序时需要在必要的地方做好操作注释理清代码思维链路减少逻辑的误解。
返回列表