如何在 Java 中刷新 Window class 的图形

How to refresh graphics of a Window class in Java

我正在尝试绘制一个 vlcj(java VLC 库的绑定)面板,以便我可以播放视频并在其上绘制。我遇到了一些问题。这是完整的基本代码:

代码清单 1:AppOverlay.java

package app;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;

import com.sun.jna.platform.WindowUtils;

@SuppressWarnings("serial")
public class AppOverlay extends Window implements Runnable {

    private final boolean isRunning;
    private final int fps;
    private BufferedImage graphics;
    private BufferedImage img;
    private int x, y;
    private boolean ltr;

    public AppOverlay(Window owner) {
        super(owner, WindowUtils.getAlphaCompatibleGraphicsConfiguration());
        setBackground(new Color(0,0,0,0));

        graphics    = new BufferedImage(1280,800, BufferedImage.TYPE_INT_ARGB);
        isRunning   = true;
        img         = null;
        ltr         = true;
        fps         = 60;
        x           = 0;
        y           = 0;
    }

    @Override
    public void run(){
        while(isRunning){

            try{
                Thread.sleep(1000/fps);
            } catch(InterruptedException e){
                e.printStackTrace();
            }

            if(ltr) {
                if(x < 1280) x++;
                else ltr = false;
            } else {
                if(x < 0) ltr = true;
                else x--;
            }

            repaint();
        }
    }

    public void createAndShowGUI() {
        setVisible(true);

        Thread thread = new Thread(this);
        thread.start();

        String path = "Drive:\path\to\image.png";
        try {
            img = ImageIO.read(new java.io.FileInputStream(path));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D)g;
        Graphics2D gfx = graphics.createGraphics();
        gfx.setColor(new Color(255,255,255,0));
        gfx.clearRect(0, 0, 1280, 800);
        if(img != null) gfx.drawImage(img, x, y, null);
        gfx.dispose();
        g2d.drawImage(graphics, 0, 0, null);
    }
}

代码清单 2:AppPlayer.java

package app;

import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;

@SuppressWarnings("serial")
public class AppPlayer extends EmbeddedMediaPlayerComponent {

}

代码清单 3:AppFrame.java

package app;

import java.awt.Dimension;

import javax.swing.JFrame;

@SuppressWarnings("serial")
public class AppFrame extends JFrame {

    private AppPlayer appPlayer;
    private AppOverlay overlay;

    public AppFrame(){
        super();
    }

    public void createAndShowGUI() {

        appPlayer = new AppPlayer();
        appPlayer.setPreferredSize(new Dimension(1280,800));
        getContentPane().add(appPlayer);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("App");
        setVisible(true);
        pack();

        overlay = new AppOverlay(this);
        appPlayer.mediaPlayer().overlay().set(overlay);
        appPlayer.mediaPlayer().overlay().enable(true);
        overlay.createAndShowGUI();
    }
}

代码清单 4:Main.java

package main;

import javax.swing.SwingUtilities;

import app.AppFrame;

public class Main {

    public static void main(String[] args) {
        final AppFrame app = new AppFrame();
        SwingUtilities.invokeLater( new Runnable() {
            @Override
            public void run() {
                app.createAndShowGUI();
            }
        });
    }

}

有了它和 vlcj-4 库,您应该能够自己测试我的代码。我的问题是覆盖(扩展 Window class 的 AppOverlay class)不显示或刷新动画,除非我删除 select window(我单击另一个 window 或桌面或 OS 工具栏),以便 window(应用程序)处于非活动状态,然后 select window(应用程序) 再次。它只会加载一帧,仅此而已。我必须再次 deselect 并重新 select window 才能加载另一帧(这只是 Overlay 的情况,即如果我在 AppPlayer class 视频将正常播放。

我想要的是能够在叠加层上绘制一些动画图形。我知道 JPanel class 有 paintComponent() 方法,但是 Window class 没有那个方法(只有 paint() 和 repaint() 方法可用) .

我应该怎么做才能解决这个问题?

编辑:

我尝试添加一个 JPanel 来进行绘制,而不是直接在 AppOverlay 上绘制

代码清单 5:AppPanel.java

package app;

import java.awt.Color;
import java.awt.Graphics;

import javax.swing.JPanel;

public class AppPanel extends JPanel implements Runnable {
    private int x, y;
    private boolean ltr;

    public AppPanel() {
        x   = 0;
        y   = 0;
        ltr = true;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(new Color(0,0,0,0));
        g.clearRect(0, 0, 1280, 800);
        g.setColor(Color.RED);
        g.fillRect(x, y, 100, 100);
    }

    @Override
    public void run() {
        while(true){

            try{
                Thread.sleep(1000/60);
            } catch(InterruptedException e){
                e.printStackTrace();
            }
            if(ltr) {
                if(x < 1280) x++;
                else ltr = false;
            } else {
                if(x < 0) ltr = true;
                else x--;
            }

            repaint();
        }
    }
}

然后将其添加到 AppOverlay。

代码清单 6:AppOverlay.java 部分修改

public class AppOverlay extends Window implements Runnable {
    //previous field declaration above ...
    AppPanel panel;
    AppPlayer player = null;

    public AppOverlay(Window owner) {
        //previous constructor instructions above...

        panel = new AppPanel();
        add(panel);
    }

    public void createAndShowGUI(AppPlayer player) {
        setVisible(true);

        /*
        Thread thread = new Thread(this);
        thread.start();

        String path = "Drive:\path\to\image.png";
        try {
            img = ImageIO.read(new java.io.FileInputStream(path));
        } catch (IOException e) {
            e.printStackTrace();
        }
        */

        Thread panelThread = new Thread(panel);
        panelThread.start();
    }
}

执行此操作将显示 JPanel 的图形并根据需要为它们设置动画。

如果您知道使 JPanel 背景透明(以便我们可以看穿它)同时仍让它显示其图形的方法。那肯定能解决问题。

您已经完成了大部分工作。每次通过调用 app.repaint();

绘制框架时只需重新绘制框架

您可以使用 JComponent 中的以下方法:( http://download.oracle.com/javase/6/docs/api/javax/swing/JComponent.html )

void    repaint(long tm, int x, int y, int width, int height)
 //**Adds the specified region to the dirty region list if the component is showing.*//
void    repaint(Rectangle r)
 /**Adds the specified region to the dirty region list if the component is showing.*//
You can call those before redraw()

我试了一下你的例子,想出了一些可行的方法,但我不认为它是一个好的解决方案。

主要问题似乎是没有办法告诉覆盖刷新(或者我只是没有找到它)。只是 repainting 叠加层不会在屏幕上更新它,所以我使用的解决方法是隐藏并再次显示它。

对于更新间隔的时间我使用了javax.swing.Timer。 (在真实版本中,您可能希望通过 MediaPlayerEventListener 启动和停止计时器)。

作为副作用,调用 repaint 方法并调整 x 坐标以在屏幕上移动图像。

在下面的简化示例中(使用您的 main 到 运行 它),我移动了一个带有 x 坐标的红色矩形而不是一些未知图像。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.Timer;

import com.sun.jna.platform.WindowUtils;

import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.player.embedded.OverlayApi;

public class AppFrame extends JFrame {

    private static final long serialVersionUID = -1569823648323129877L;

    public class Overlay extends Window {

        private static final long serialVersionUID = 8337750467830040964L;

        private int x, y;
        private boolean ltr = true;

        public Overlay(Window owner) throws HeadlessException {
            super(owner, WindowUtils.getAlphaCompatibleGraphicsConfiguration());
            setBackground(new Color(0,0,0,0));
        }

        @Override
        public void paint(Graphics g) {

            super.paint(g);

            if (ltr) {
                if (x < 1180)
                    x += 1;
                else
                    ltr = false;
            } else {
                if (x < 0)
                    ltr = true;
                else
                    x -= 1;
            }

            g.setColor(Color.RED);
            g.fillRect(x, y, 100, 100);

            String s = Integer.toString(x);
            g.setColor(Color.WHITE);
            g.drawChars(s.toCharArray(), 0, s.length(), x+10, y+50);
        }
    }

    private EmbeddedMediaPlayerComponent appPlayer;

    public void createAndShowGUI() {

        appPlayer = new EmbeddedMediaPlayerComponent();
        appPlayer.setPreferredSize(new Dimension(1280, 800));
        getContentPane().add(appPlayer);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("App");
        setVisible(true);
        pack();

        Overlay overlay = new Overlay(this);

        OverlayApi api = appPlayer.mediaPlayer().overlay();
        api.set(overlay);
        api.enable(true);

        //appPlayer.mediaPlayer().media().play(" ... ");

        Timer timer = new Timer(0, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                api.enable(false);
                api.enable(true);
            }
        });
        timer.setRepeats(true);
        timer.setDelay(200);
        timer.start();
    }
}

如果这是您的选择,使用 animated gif 可能会容易得多。至少它是独立工作的(不需要定时器)。


更新:

如您所见,使用 JPanel 似乎效果更好。 只需使用 setOpaque(false) 使其透明即可。

这里是调整后的例子。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.player.embedded.OverlayApi;

public class AppFrame2 extends JFrame {

    private static final long serialVersionUID = -1569823648323129877L;

    public class OverlayPanel extends JPanel {

        private static final long serialVersionUID = 8070414617530302145L;

        private int x, y;
        private boolean ltr = true;

        public OverlayPanel() {
            this.setOpaque(false);
        }

        @Override
        public void paint(Graphics g) {

            super.paint(g);

            if (ltr) {
                if (x < 1180)
                    x += 1;
                else
                    ltr = false;
            } else {
                if (x < 0)
                    ltr = true;
                else
                    x -= 1;
            }

            g.setColor(Color.RED);
            g.fillRect(x, y, 100, 100);

            String s = Integer.toString(x);
            g.setColor(Color.WHITE);
            g.drawChars(s.toCharArray(), 0, s.length(), x+10, y+50);
        }
    }

    public class Overlay extends Window {

        private static final long serialVersionUID = 8337750467830040964L;

        OverlayPanel panel;

        public Overlay(Window owner) throws HeadlessException {
            super(owner);
            setBackground(new Color(0,0,0,0));

            panel = new OverlayPanel();
            this.add(panel);
        }
    }

    private EmbeddedMediaPlayerComponent appPlayer;

    public void createAndShowGUI() {

        appPlayer = new EmbeddedMediaPlayerComponent();
        appPlayer.setPreferredSize(new Dimension(1280, 800));
        getContentPane().add(appPlayer);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("App");
        setVisible(true);
        pack();

        Overlay overlay = new Overlay(this);

        OverlayApi api = appPlayer.mediaPlayer().overlay();
        api.set(overlay);
        api.enable(true);

        //appPlayer.mediaPlayer().media().play(" ... ");

        Timer timer = new Timer(0, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                overlay.panel.repaint();
            }
        });
        timer.setRepeats(true);
        timer.setDelay(17);
        timer.start();
    }
}