【问题标题】:Making JLabel move across JPanel使 JLabel 跨 JPanel 移动
【发布时间】:2018-02-27 03:10:34
【问题描述】:

我正在尝试让我的游戏中的一个桶自己向下浮动到屏幕上,我尝试了一些东西并查看了一些教程,但我似乎无法让它工作。也许我通过尝试在paintcomponent()中执行此操作以错误的方式处理此问题?在我的 JPanel 中,我还有一个汽车对象,它是由 KeyListeners 控制的 JPanel 的扩展。

// this is inside a JPanel class that I made that's painting out the game


OilBarrel barrel = new OilBarrel();


@Override
public void paintComponent(Graphics g) {

    super.paintComponent(g);
    road.paintComponent(g);

    this.add(car);
    this.add(barrel);

    barrel.setBounds(barrelX, barrel.getBarrelY(), 150, 180);
    int carX = car.getCarX();

    car.setBounds(carX, carY, 800, 400);
    barrel.setBounds(550, barrel.getBarrelY(), 800, 400);
    while (true) {
        barrel.move();
        repaint();
        try {
            Thread.sleep(1);

        } catch (Exception e) {
        }
    }

}



    // this is the oilbarrel class

import java.awt.Dimension;
import java.awt.Image;

 import javax.swing.ImageIcon;
import javax.swing.JLabel;


public class OilBarrel extends JLabel{

int barrelX;
int barrelY = 0;

public OilBarrel() {

    setVisible(true);
    this.setFocusable(true);
    Image img = new ImageIcon(this.getClass().getResource("/oil.png")).getImage();


    this.setIcon(new ImageIcon(img));


    }



public void move(){

    if(barrelY<1000)
     barrelY +=1;

}



public int getBarrelY() {
    return barrelY;
 }

}

【问题讨论】:

  • paintComponent() 中有一个while(true) 不是办法。您应该运行一个单独的线程来调用 move() 然后每隔一段时间重新绘制。
  • 嗯.. 总的来说,我对摇摆和线程真的很陌生。我可以为 while 条件设置一个计数器。您能否更具体一点,或者就您的线程想法举个例子?
  • 不要调用road.paintComponent(g); - 永远不需要在组件上手动调用paintpaintComponent,如果你发现自己这样做了,那么你做错了什么.另外,不要在paintComponent 方法中修改其他组件的状态。其实这种工作不要这样用组件
  • @MadProgrammer 你是什么意思我不应该“以这种方式”使用组件?你能澄清一下吗?我想我可以只添加一条道路而不是把它画出来。您还说我不应该修改其他组件的状态,但是例如可以调用paintyourself 中的方法来执行此操作吗?谢谢
  • @MarkoMarinkovic 组件需要对它们的大小和位置(特别是)进行大量的微观管理,如果处理得当,它们也是自绘的。与纯定制的绘画路线相比,他们需要更多的工作来管理

标签: java swing


【解决方案1】:

Swing 是单线程的,这意味着您在 paintComponent 方法中的 for-loop 正在阻塞事件调度线程,从而阻止它更新 UI。

详情请见Concurrency in Swing

解决该问题的最简单解决方案是使用Swing Timer,因为它允许在 EDT 上安排一个小的延迟,但这会触发它在 EDT 上的更新,从而可以安全地从内。

组件动画并不难,但它确实需要不同的工作流程,例如:

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
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 JLabel label;
        private int xDelta = 1;

        public TestPane() {
            setLayout(null);
            label = new JLabel("*");
            label.setSize(label.getPreferredSize());
            label.setLocation(0, (200 - label.getHeight()) / 2);
            add(label);
            Timer timer = new Timer(10, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    int x = label.getX();
                    x += xDelta;
                    if (x + label.getWidth() > getWidth()) {
                        x = getWidth() - label.getWidth();
                        xDelta *= -1;
                    } else if (x < 0) {
                        x = 0;
                        xDelta *= -1;
                    }
                    label.setLocation(x, label.getY());
                }
            });
            timer.start();
        }

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

    }

}

根据个人喜好,我会避免在这项工作中使用组件,而是倾向于完整的自定义绘画路线。

这有很多原因,组件是复杂的对象,需要大量关注细节。也很难对它们应用额外的效果或变换

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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 Rectangle box;
        private int xDelta = 1;

        public TestPane() {
            setLayout(null);
            box = new Rectangle(0, 75, 50, 50);
            Timer timer = new Timer(10, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    int x = box.x;
                    x += xDelta;
                    if (x + box.width > getWidth()) {
                        x = getWidth() - box.width;
                        xDelta *= -1;
                    } else if (x < 0) {
                        x = 0;
                        xDelta *= -1;
                    }
                    box.setLocation(x, box.y);
                    repaint();
                }
            });
            timer.start();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.draw(box);
            g2d.dispose();
        }

    }

}

【讨论】:

  • 我确信这对于我的问题和一个很好的答案来说是非常相关的信息。但是对于我这个级别的人来说,这似乎有点复杂。我最终确实使用了一个线程和一个 run() 方法,而不是在 Paintcomponent 中这样做。
  • @MarkoMarinkovic 有两个例子,两者基本相同,一个是移动标签,一个是执行自定义绘画。您会发现,一般而言,自定义绘制方法更有效(绘制单个组件而不是并置的多个组件)并且通常更易于管理。选择,如何永远是你的。还要了解,Swing Timer 已进行了优化,因为它不会在未完成的计时事件排队时将新的计时事件发布到 EDT,这意味着您降低了 EDT 饱和的风险,这将导致渲染速度变慢
【解决方案2】:

当线程需要更新 GUI 时使用 SwingUtilities.invokeLater。 https://docs.oracle.com/javase/7/docs/api/javax/swing/SwingUtilities.html#invokeLater(java.lang.Runnable)

Thread t = new Thread(){
                public void run(){
                    while( true ){
                        // queue on Event Dispatch Thread
                        SwingUtilities.invokeLater( new Runnable(){
                            public void run(){
                                barrel.move();
                                repaint();
                            }
                        });
                        try{
                            Thread.sleep( 100 );
                        }catch( InterruptedException ie ){}
                    }
                }
            };
            t.start();

另外,查找 Swing Timer。我认为这可能会更好。

【讨论】:

  • 在这种情况下,我会避免 Thread 不必要的复杂性。 repaint 本质上也是线程安全的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-07
相关资源
最近更新 更多