优化JFrame重绘的方法:解决移动图形时屏幕频繁闪白的问题
问题原因
Swing 直接重写JFrame的paint方法时,默认重绘流程会先清空整个窗口背景,再绘制窗口边框、组件,最后才执行你写的图形绘制逻辑,背景清空的瞬间就会出现白屏闪烁。
可行解决方案
核心思路是用自带双缓冲的JPanel作为绘图容器,替换直接在JFrame上绘图的逻辑,双缓冲会先把所有图形绘制到后台缓冲区,绘制完成后一次性替换到屏幕上,完全消除闪烁。
修改步骤
- 移除
JFrame的paint重写逻辑,新增自定义DrawPanel类继承JPanel,所有绘图逻辑放在DrawPanel的paintComponent方法中 - 将
DrawPanel添加到JFrame的内容窗格,JPanel默认开启双缓冲,可显式声明加强兼容性 - 优化冗余代码:
Figure是引用类型,从数组取出修改属性后不需要再调用set放回数组,原对象已经被修改
修改后完整代码
figurestest/Figure.java(无变动)
package figurestest; import java.awt.Graphics; public abstract class Figure { public int x, y; public int w, h; public Figure (int x, int y, int w, int h) { this.x = x; this.y = y; this.w = w; this.h = h; } public abstract void paint (Graphics g); public abstract void mov (int dx, int dy); }
figurestest/Rect.java(修正类名首字母大写,无逻辑变动,注意和文件名保持一致)
package figurestest; import java.awt.*; public class Rect extends Figure { public Rect (int x, int y, int w, int h) { super(x, y, w, h); } public void paint (Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.fillRect(this.x, this.y, this.w, this.h); g2d.drawRect(this.x,this.y, this.w,this.h); } public void mov (int dx, int dy){ this.x += dx; this.y += dy; } }
testapp.java(核心修改)
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.ArrayList; import java.util.Random; import figurestest.*; class TestApp { public static void main (String[] args) { ListFrame frame = new ListFrame(); frame.setVisible(true); } } // 自定义绘图面板 class DrawPanel extends JPanel { ArrayList<Figure> figs; public DrawPanel(ArrayList<Figure> figs) { this.figs = figs; // 显式开启双缓冲 setDoubleBuffered(true); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); // 所有图形绘制在这里执行 for (Figure fig: figs) { fig.paint(g); } } } class ListFrame extends JFrame { ArrayList<Figure> figs = new ArrayList<Figure>(); Random rand = new Random(); DrawPanel drawPanel; ListFrame () { // 初始化绘图面板并添加到窗口 drawPanel = new DrawPanel(figs); add(drawPanel, BorderLayout.CENTER); // 让面板获取焦点响应键盘事件 drawPanel.setFocusable(true); drawPanel.requestFocus(); this.addWindowListener ( new WindowAdapter() { public void windowClosing (WindowEvent e) { System.exit(0); } } ); // 键盘事件绑定到绘图面板,避免焦点丢失 drawPanel.addKeyListener ( new KeyAdapter() { public void keyPressed (KeyEvent evt) { Dimension size = drawPanel.getSize(); int x = rand.nextInt(size.width); int y = rand.nextInt(size.height); int w = 5+ rand.nextInt(50); int h = 5+ rand.nextInt(50); if (evt.getKeyChar() == 'r') { figs.add(new Rect(x,y, w,h)); } else if (evt.getKeyCode() == KeyEvent.VK_DOWN || evt.getKeyCode() == KeyEvent.VK_LEFT || evt.getKeyCode() == KeyEvent.VK_RIGHT || evt.getKeyCode() == KeyEvent.VK_UP ) { if (figs.size() > 0){ int dx = 0; int dy = 0; if(evt.getKeyCode() == KeyEvent.VK_DOWN) dy = 2; else if (evt.getKeyCode() == KeyEvent.VK_UP) dy = -2; else if (evt.getKeyCode() == KeyEvent.VK_RIGHT) dx = 2; else if (evt.getKeyCode() == KeyEvent.VK_LEFT) dx = -2; // 引用类型直接修改即可,不需要set回数组 Figure fig = figs.get(figs.size() -1); fig.mov(dx,dy); } } // 只重绘画板,不需要重绘整个窗口 drawPanel.repaint(); } } ); this.setTitle("Figures"); this.setSize(350, 350); } // 移除原JFrame的paint重写 }
额外优化建议
如果后续图形数量较多还可以进一步优化:调用repaint(Rectangle bounds)只重绘图形变化的区域,不需要重绘整个面板,进一步提升流畅度。
内容的提问来源于stack exchange,提问作者Leonardo
相关产品推荐
相关产品推荐

