You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决透明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)的显示,谨慎使用:

  1. 显式启用面板的双缓冲:pn.isDoubleBuffered = true(虽然默认是true,但在Linux下显式设置可能生效)
  2. 重写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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 10:03:04