JButton 在 Animated Timed paintComponent 之上

JButton on top of Animated Timed paintComponent

我有一个 BVH 播放器,它有 9 个视图,可以在计时器上工作以使其动画化,我正在尝试将控件放在每个面板的顶部以单独控制变换操作。

这些按钮在我的 paintComponent 后面呈现,但有时可以点击。我还有一个 MouseListener 来调整骨架的变换,有时它可以工作。我可以post听众,但我认为这不重要

我试过 graphics.dispose 和 graphics2d.dispose 的各种组合,我也试过改变 super.paintComponent(g) 的位置,但结果很奇怪,但没什么用。

下面是显示问题的视频。如果你仔细观察,当你将鼠标悬停在它们应该出现的位置时,你会看到按钮弹出。您还会看到,当我 g.dispose() 时,两个地方的按钮变得陌生,但只点击一个地方(这不是它们最明显的地方)。 https://youtu.be/CyFpUlbFI1U

这是我的源代码:

public SkeletonPlayer(int camera, double scale, int rotLeftRight, int rotUpDown, double translateLeftRight, double translateUpDown) {
    this.camera = camera;
    this.scale = scale;
    this.rotLeftRight = rotLeftRight;
    this.rotUpDown = rotUpDown;
    this.translateLeftRight = translateLeftRight;
    this.translateUpDown = translateUpDown;
    panel = new JPanel() {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            g2d.translate(getWidth() / 2, getHeight() / 2);

            AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
            at.rotate(Math.toRadians(90), Math.toRadians(90), Math.toRadians(90));

            for (int n = 0; n < MainFrame.skeleton[camera].getNodes().size(); n++) {
                Node node = MainFrame.skeleton[camera].getNodes().get(n);
                int x1 = (int) (scale * node.getPosition().getX());
                int y1 = (int) (-scale * node.getPosition().getY());
                g2d.setColor(Color.RED);
                g2d.fillOval((int) (x1 - 2), (int) (y1 - 2), 4, 4);

                g2d.setColor(Color.BLACK);
                //g2d.drawString(node.getName(), x1 + 10, y1);

                for (Node child : node.getChildrens()) {
                    int x2 = (int) (scale * child.getPosition().getX());
                    int y2 = (int) (-scale * child.getPosition().getY());
                    g2d.drawLine(x1, y1, x2, y2);
                }
            }

            int x1 = (int) (scale * groundPlane.rootNode.getPosition().getX());
            int y1 = (int) (-scale * groundPlane.rootNode.getPosition().getY());
            g2d.setColor(Color.BLACK);
            g2d.fillOval((int) (x1 - 2), (int) (y1 - 2), 4, 4);

            for (int n = 1; n < groundPlane.nodes.size(); n++) {
                Node node = groundPlane.nodes.get(n);

                int x2 = (int) (scale * node.getPosition().getX());
                int y2 = (int) (-scale * node.getPosition().getY());
                g2d.setColor(Color.BLACK);
                g2d.fillOval((int) (x2 - 2), (int) (y2 - 2), 4, 4);

                g2d.setColor(Color.GREEN);

                g2d.drawLine(x1, y1, x2, y2);
                //System.out.println("line:" + x1 + "," + y1 + " " + x2 + "," + y2);
            }
        }
    };
    panel.addMouseListener(this);
    panel.addMouseMotionListener(this);
    panel.addMouseWheelListener(this);
    MigLayout layout = new MigLayout(
            "insets 10, gap 10, wrap", // Layout Constraints
            "[fill,grow][fill,grow][fill,grow]", // Column constraints with default align
            "[fill,grow][fill,grow][fill,grow]");
    panel.setLayout(layout);
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
}

这是计时器:

new Timer().scheduleAtFixedRate(new TimerTask() {

        @Override
        public void run() {
            update();
        }
    }, 0, (long) Math.round(motion.getFrameTime() * 1000));

private static void update() {
    for (int i = 0; i < NUM_SKELETON_PLAYERS; i++) {
        MainFrame.skeleton[i].setPose((int) frameIndex, i);
        skeletonPlayers[i].groundPlane.setPose(i);
        skeletonPlayers[i].panel.repaint();
    }
    //skeleton.setPose(null);
    frameIndex += 1;
    if (frameIndex > motion.getFrameSize() - 1) {
        frameIndex = 0;
    }
}

If you watch closely you'll see the buttons popping up when you hover over where they are supposed to be.

这意味着您遇到了 ZOorder 问题。 ZOrder 是组件在 z 轴上堆叠在一起时绘制的顺序。

Swing 以组件添加到面板的相反顺序绘制组件,因此最后添加的组件先绘制。所以听起来你有多个面板堆叠在一起,你添加的最后一个面板包含按钮,但它首先被绘制,所以所有其他面板都在它上面绘制。

当鼠标移到按钮上时,按钮需要重新绘制自身以显示按钮的滚动效果,因此按钮暂时绘制在顶部,直到下一次重新绘制正确的 ZOrder 时父面板重新绘制成立。

我对你的程序的结构了解不够,但我想问你为什么有一堆面板一个接一个地堆叠。您只需要一个带有自定义绘画的面板,然后将按钮放在这个面板上。

您的主要问题就在这里...

Graphics2D g2d = (Graphics2D) g;
g2d.translate(getWidth() / 2, getHeight() / 2);

AffineTransform at = AffineTransform.getTranslateInstance(0, 0);

传递给你的组件的Graphics上下文是一个共享资源,它被传递给在绘制周期中绘制的所有组件,这意味着当你改变原点和旋转等东西时它会影响所有在它之后绘制的组件。

相反,您应该在应用更改之前创建 Graphics 状态的副本,这会使原始状态保持不变

Graphics2D g2d = (Graphics2D) g.create();
g2d.translate(getWidth() / 2, getHeight() / 2);

AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
//...
g2d.dispose();   

你的第二个错误是...

new Timer().scheduleAtFixedRate(new TimerTask() {
    @Override
    public void run() {
        update();
    }
}, 0, (long) Math.round(motion.getFrameTime() * 1000));

Swing 不是线程安全的,更新 UI 或 UI 所依赖的状态可能会产生意外且难以诊断的结果。

在这种情况下,您有两种选择,根据需要使用 Swing TimerSwingWorker

查看 Concurrency in Swing 了解更多详情

另一个问题是 at.rotate(Math.toRadians(90), Math.toRadians(90), Math.toRadians(90));。这根本就没有意义。

documentation 状态

 public void rotate​(double theta,
                   double anchorx,
                   double anchory) 

Concatenates this transform with a transform that rotates coordinates around an anchor point. This operation is equivalent to translating the coordinates so that the anchor point is at the origin (S1), then rotating them about the new origin (S2), and finally translating so that the intermediate origin is restored to the coordinates of the original anchor point (S3). This operation is equivalent to the following sequence of calls:

 translate(anchorx, anchory);      // S3: final translation
 rotate(theta);                    // S2: rotate around anchor
 translate(-anchorx, -anchory);    // S1: translate anchor to origin   Rotating by a positive angle theta rotates points on the

positive X axis toward the positive Y axis. Note also the discussion of Handling 90-Degree Rotations above.
Parameters:
theta - the angle of rotation measured in radians
anchorx - the X coordinate of the rotation anchor point
anchory - the Y coordinate of the rotation anchor point

这就是说,第二个和第三个参数实际上是发生旋转的点。在你的情况下 at.rotate(Math.toRadians(90), 0, 0); 可能是你真正想要的。

使用Graphics#create

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private double angle;

        public TestPane() {
            setLayout(new GridBagLayout());
            for (int index = 0; index < 5; index++) {
                add(new JButton("Here"));
            }

            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    angle += 0.01;
                    repaint();
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            //Graphics2D g2d = (Graphics2D) g;
            g2d.translate(getWidth() / 2, getHeight() / 2);

            AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
            at.rotate(Math.toRadians(90), 0, 0);
            g2d.drawRect(-50, -50, 100, 100);
            g2d.setColor(Color.RED);
            g2d.rotate(angle, 0, 0);
            g2d.drawRect(-100, -100, 200, 200);
            g2d.dispose();
        }

    }

}

不使用Graphics#create

哦,哦,我的按钮在哪里 - 根据您对 Graphics 上下文所做的更改,可能围绕容器的中心点旋转了 90 度,所以现在它们可能不在屏幕上

我已经转移到 JLayeredPane 以获得我正在寻找的效果。我可以点击顶层并拖动底层。

    LayerUI backgroundUI = new WallpaperLayerUI();
    jlayer = new JLayer<JPanel>(panel, backgroundUI);

    LayerUI controlsUI = new LayerUI();
    JPanel controlPanel = new JPanel();
    MigLayout layout = new MigLayout(
            "debug, insets 10, gap 10, wrap", // Layout Constraints
            "[fill,grow][fill,grow][fill,grow]", // Column constraints with default align
            "[fill,grow][fill,grow][fill,grow]");
    controlPanel.setLayout(layout);
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));

    jlayer2 = new JLayer<JPanel>(controlPanel, controlsUI);

    panel.addMouseListener(this);
    panel.addMouseMotionListener(this);
    panel.addMouseWheelListener(this);

    finalPanel = new JLayeredPane();
    finalPanel.setPreferredSize(new Dimension(300, 310));
    //finalPanel.setLayout(null);
    jlayer.setBounds(0, 0, (808-40)/3, (608-40)/3);
    jlayer2.setBounds(0, 0, (808-80)/3, (608-80)/3);
    controlPanel.setBackground(new Color(0,0,0,0));
    finalPanel.add(jlayer, new Integer(0));
    finalPanel.add(jlayer2 ,new Integer(1));