如何解决透明JFrame内绘制不透明填充圆形时的闪烁问题?
如何解决透明JFrame内绘制不透明填充圆形时的闪烁问题?
我之前在Linux下做Swing透明窗口绘制时也碰到过一模一样的闪烁问题,Windows下完全正常,折腾了半天终于找到靠谱的解决办法。先给你分析下原因:
Linux的X11窗口系统和Swing的透明窗口合成机制兼容性不太好——当你在透明JFrame上做局部重绘时,窗口管理器的合成操作和Swing的绘制队列不同步,再加上透明JPanel里super.paintComponent(g)会用透明色清除背景,这两步操作交替执行就会导致明显的闪烁。而Windows的窗口合成机制和Swing适配得更完善,所以没这个问题。
下面给你两个可行的解决方案,优先推荐第一个:
方案1:用离屏缓冲(BufferedImage)做绘制
这是最稳妥的办法,把所有绘制操作先放到一个内存里的缓冲图像中,再一次性画到面板上,避免多次直接操作屏幕导致的闪烁。
修改你的JPanel代码如下:
val pn = object : JPanel() { var pt: Point?=null val w=256 val h=w private lateinit var buffer: BufferedImage init { // 初始化缓冲图像,用TYPE_INT_ARGB保留透明通道 buffer = BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB) addMouseMotionListener(object : MouseMotionAdapter() { override fun mouseMoved(e: MouseEvent?) { super.mouseMoved(e) var lpt=pt if (lpt!=null) repaint(lpt.x-w/2,lpt.y-h/2,w,h) pt=e?.point lpt=pt if (lpt!=null) repaint(lpt.x-w/2,lpt.y-h/2,w,h) } }) } override fun setBounds(x: Int, y: Int, width: Int, height: Int) { super.setBounds(x, y, width, height) // 面板尺寸变化时,重新创建匹配大小的缓冲图像 buffer = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) } val rand= Random.Default override fun paintComponent(g: Graphics?) { super.paintComponent(g) if (g == null) return // 获取缓冲图像的Graphics对象,在内存里完成所有绘制 val bg = buffer.createGraphics() try { // 用透明色清空缓冲图像背景 bg.clearRect(0, 0, width, height) // 绘制固定红方块 bg.color= Color.red bg.fillRect(10,10,100,100) bg.color=Color.black bg.drawString(rand.nextInt(1000).toString(),10+50,10+50) // 绘制随鼠标移动的绿色圆 pt?.also { bg.color = Color.green bg.fillOval(it.x-w/2,it.y-h/2,w,h) bg.color=Color.black bg.drawString(rand.nextInt(1000).toString(),it.x,it.y) } } finally { // 一定要释放Graphics资源,避免内存泄漏 bg.dispose() } // 把缓冲图像一次性画到面板上 g.drawImage(buffer, 0, 0, this) } }
方案2:调整双缓冲和背景擦除逻辑
如果不想用离屏缓冲,可以试试调整Swing的绘制逻辑,但这个方法可能影响子组件(比如你加的那个JButton)的显示,谨慎使用:
- 显式启用面板的双缓冲:
pn.isDoubleBuffered = true(虽然默认是true,但在Linux下显式设置可能生效) - 重写
paintComponent时跳过super.paintComponent(g),避免背景擦除操作,但要手动绘制子组件:
override fun paintComponent(g: Graphics?) { if (g == null) return // 跳过super.paintComponent,避免透明背景擦除导致的闪烁 // super.paintComponent(g) // 直接绘制你的图形内容 g.color= Color.red g.fillRect(10,10,100,100) g.color=Color.black g.drawString(rand.nextInt(1000).toString(),10+50,10+50) pt?.also { g.color = Color.green g.fillOval(it.x-w/2,it.y-h/2,w,h) g.color=Color.black g.drawString(rand.nextInt(1000).toString(),it.x,it.y) } // 手动绘制子组件,否则按钮会不显示 paintChildren(g) }
额外小建议
- 尽量别在
paintComponent里生成随机数(虽然你是用来测试重绘区域),这个方法会被频繁调用,额外的计算会影响绘制性能 - 局部重绘的区域可以稍微放大2-4个像素,比如
repaint(lpt.x-w/2 -2, lpt.y-h/2 -2, w+4, h+4),这样可以覆盖圆形边缘的像素,避免残留痕迹
备注:内容来源于stack exchange,提问作者lineage
相关产品推荐
相关产品推荐

