【问题标题】:Moving Graphics in JPanel在 JPanel 中移动图形
【发布时间】:2019-07-19 11:14:54
【问题描述】:

单击按钮时,我希望在 JPanel 中将椭圆形从一个位置移动到另一个位置。这是我想出的代码。但是,当我单击按钮时,这一切都立即发生,而没有明显的移动,从开始到结束的速度都很慢。椭圆形只是出现在一个新位置。

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.JPanel;
public class testtest implements ActionListener{
    JButton button;
    MyDrawPanel panel;
    int x = 0;
    int y = 0;
    public static void main(String[]args){
        testtest test = new testtest();
        test.go();
    }
    public void go(){
        JFrame frame = new JFrame("Balloon Balls");
        panel = new MyDrawPanel();
        button = new JButton("Restart");
        button.addActionListener(this);
        panel.add(button);
        frame.setSize(300, 300);
        frame.add(panel);
        frame.setVisible(true);
    }

    @Override
    public void actionPerformed (ActionEvent e){
        for(int i=0;i<130;i++){
            x++;
            y++;
            panel.repaint();
            try {
                Thread.sleep(100);
            } catch(Exception ex) { }
        }
    }
    class MyDrawPanel extends JPanel{
        @Override
        public void paintComponent(Graphics g){
            g.fillOval(x, y, 30, 30);
            g.setColor(Color.BLACK);

        }
    }
}

【问题讨论】:

    标签: java graphics


    【解决方案1】:

    Swing 是单线程的,不是线程安全的。

    ActionListener 中使用Thread.sleep(100) 会阻塞事件调度线程,从而阻止绘制任何内容。直到 actionPerformed 方法存在之后才会发生新的绘制过程。

    更多详情请见Concurrency in Swing

    Swing 也不是线程安全的,这意味着您永远不应该从 EDT 上下文之外对 UI 进行更改。

    最简单的解决方案是使用Swing Timer,这将允许建立定期定时回调,这些回调在事件调度线程中执行,但不会阻塞 EDT。

    您还遗漏了 OO 的重要概念之一,即封装。 x/y 属性实际上应该由MyDrawPanel 管理,而不是testtest

    例如...

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.Timer;
    
    public class testtest implements ActionListener {
    
        JButton button;
        MyDrawPanel panel;
    
        public static void main(String[] args) {
            testtest test = new testtest();
            test.go();
    
        }
    
        public void go() {
            JFrame frame = new JFrame("Balloon Balls");
            panel = new MyDrawPanel();
            button = new JButton("Restart");
            button.addActionListener(this);
            panel.add(button);
            frame.add(panel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
    
        }
    
        private Timer timer;
    
        public void actionPerformed(ActionEvent e) {
            if (timer != null) {
                return;
            }
            timer = new Timer(100, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent evt) {
                    if (panel.update()) {
                        timer.stop();
                        timer = null;
                    }
                }
            });
            timer.start();
        }
    
        class MyDrawPanel extends JPanel {
    
            private int xPosy = 0;
            private int yPosy = 0;
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(300, 300);
            }
    
            public boolean update() {
                xPosy++;
                yPosy++;
                repaint();
    
                return xPosy > getWidth() || yPosy > getHeight();
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);            
                g.fillOval(xPosy, yPosy, 30, 30);
                g.setColor(Color.BLACK);
            }
        }
    }
    

    【讨论】:

    • 有一个小错误(我猜是错字):if (!panel.update()) 应该是if (panel.update())
    【解决方案2】:

    paintComponent 就是这样做的,它绘制面板。最初,面板在开始 x y 处绘制椭圆形。您按下按钮,窗口被擦除,并在新的 XY 处重新绘制。

    运动是您需要教计算机的一个概念。如果我们每秒多次更新面板并缓慢移动 x y,我们会产生移动的错觉。

    制作一个每 10 毫秒刷新一次的计时器。每次刷新时,稍微增加 x 和 y 值并重新绘制面板。

    【讨论】:

      【解决方案3】:

      除了在 MadProgrammer 的 answer 中对 Swing 线程问题的解释之外,我还建议通过实现 MVC Pattern 将 gui 与其控件分开。
      这提供了更好的封装,更好的职责分离,并使得更容易使用线程进行 off-edt 处理。

      拥有一个包含视图(gui)需要的所有信息的模型:

      /*
       * The model contains the information for the view and information from the view
       * The model is independent of the user interface.
       * It notifies Listener on changes. 
       */
      class Model {
      
          private Listener listener;
          private int x = 0,  y = 0;
      
          synchronized int getX() {return x;}
      
          synchronized void setX(int x) { this.x = x; }
      
          synchronized int getY() {return y;}
      
          synchronized void setY(int y) { this.y = y; }
      
          void setListener(Listener listener){
              this.listener = listener;
          }
          //notify listener when changed 
          void notifyListener(){
              if(listener != null) {
                  listener.onChange();
              }
          }
      }
      

      在这种情况下,添加了同步以允许线程使用模型。
      监听器定义为:

      /*
      * A simple interface used to link View and Model 
      */
      interface Listener {
          void onChange();
      }
      

      视图就是这样。它实现了Listener,所以它可以监听Model的变化:

      /*
       * View is just that: a dumb as possible display 
       */
      public class View implements Listener{
      
          private final JButton button;
          private final MyDrawPanel panel;
          private final Model model;
      
          public View(Model model) {
              this.model = model;
              panel = new MyDrawPanel();
              button = new JButton("Restart");
              panel.add(button);
          }
      
          public void go(){
              JFrame frame = new JFrame("Balloon Balls");
              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.setSize(300, 300);
              frame.add(panel);
              frame.setVisible(true);
          }
      
          class MyDrawPanel extends JPanel{
              @Override
              public void paintComponent(Graphics g){
                  super.paintComponent(g); //always call super
                  g.fillOval(model.getX(), model.getY(), 30, 30);
                  g.setColor(Color.BLACK);
              }
          }
      
          @Override
          public void onChange() {
              panel.repaint();
          }
      
          void addActionListener(ActionListener listener){
              button.addActionListener(listener);
          }
      }
      

      把它们放在一起:参见下面的mvce:它添加了一个控制器来控制模型和视图。
      为了方便和简单,可以将以下代码复制粘贴到一个名为View.java 的文件中,然后运行。

      import java.awt.Color;
      import java.awt.Graphics;
      import java.awt.event.ActionEvent;
      import java.awt.event.ActionListener;
      import javax.swing.JButton;
      import javax.swing.JFrame;
      import javax.swing.JPanel;
      
      /*
       * View is just that: a dumb as possible display 
       */
      public class View implements Listener{
      
          private final JButton button;
          private final MyDrawPanel panel;
          private final Model model;
      
          public View(Model model) {
              this.model = model;
              panel = new MyDrawPanel();
              button = new JButton("Restart");
              panel.add(button);
          }
      
          public void go(){
              JFrame frame = new JFrame("Balloon Balls");
              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.setSize(300, 300);
              frame.add(panel);
              frame.setVisible(true);
          }
      
          class MyDrawPanel extends JPanel{
              @Override
              public void paintComponent(Graphics g){
                  super.paintComponent(g); //always call super
                  g.fillOval(model.getX(), model.getY(), 30, 30);
                  g.setColor(Color.BLACK);
              }
          }
      
          @Override
          public void onChange() {
              panel.repaint();
          }
      
          void addActionListener(ActionListener listener){
              button.addActionListener(listener);
          }
      
          public static void main(String[]args){
              new Controller();
          }
      }
      
      /*
       * A simple interface used to link View and Model 
       */
      interface Listener {
          void onChange();
      }
      
      /*
       * The model contains the information for the view and information from the view
       * The model is independent of the user interface.
       * It notifies Listener on changes. 
       */
      class Model {
      
          private Listener listener;
          private int x = 0,  y = 0;
      
          synchronized int getX() {return x;}
      
          synchronized void setX(int x) { this.x = x; }
      
          synchronized int getY() {return y;}
      
          synchronized void setY(int y) { this.y = y; }
      
          void setListener(Listener listener){
              this.listener = listener;
          }
          //notify listener when changed 
          void notifyListener(){
              if(listener != null) {
                  listener.onChange();
              }
          }
      }
      
      /*
       * The controller "wires" the view and model, and does the processing.
       */
      class Controller implements ActionListener{
      
          private final Model model;
          private final View view;
      
          public Controller() {
              model = new Model();
              view = new View(model);
              model.setListener(view);
              view.addActionListener(this);
              view.go();
          }
      
          @Override
          public void actionPerformed (ActionEvent e){
      
              new Thread(()->{
                  for(int i=0;i<130;i++){
                      model.setX(model.getX()+1);
                      model.setY(model.getY()+1);
                      model.notifyListener();
                      System.out.println(model.getX()+" - "+ model.getY());
                      try {
                          Thread.sleep(100);
                      } catch(Exception ex) { }
                  }
              }).start();
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2018-02-22
        • 2011-12-11
        • 1970-01-01
        • 1970-01-01
        • 2013-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多