在框架内的面板上显示不同半径的圆的代码

问题描述 投票:0回答:2

这里是在给定延迟率的帧内的面板上显示不同半径的圆圈的代码,但该代码显示的是最终输出而不是中间阶段,即圆圈不是一个一个出现的,而是所有圆圈都出现了立即作为最终输出。可能存在一些与按钮操作侦听器和面板线程相关的错误。该代码采用初始圆半径和迭代总数(要显示的圆总数),下一个圆的半径增加 10。

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class ControlCircle extends JFrame {
  private JButton jbtEnlarge = new JButton("Start");
  private JButton jbtShrink = new JButton("Stop");
  private CirclePanel canvas = new CirclePanel();

  private int radius = 0;
  private int iter;

  public ControlCircle() {
    JPanel panel = new JPanel();
    JPanel jp = new JPanel();
    jp.setPreferredSize(new Dimension(300, 0));
    panel.add(jbtEnlarge);
    panel.add(jbtShrink);

    this.add(jp, BorderLayout.WEST);
    this.add(canvas, BorderLayout.CENTER);
    this.add(panel, BorderLayout.SOUTH);

    final JTextField f1 = new JTextField(8),f2 = new JTextField(8);

    jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
    jp.add(new JLabel("Radius"));
    jp.add(f1);
    jp.add(new JLabel("Iteration"));
    jp.add(f2);

    f1.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        radius = Integer.parseInt(new String(f1.getText()));
      }
    });

    f2.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        iter = Integer.parseInt(new String(f2.getText()));
      }
    });

    jbtEnlarge.addActionListener(new EnlargeListener());
    jbtShrink.addActionListener(new ShrinkListener());
  }

  public static void main(String[] args) {
    JFrame frame = new ControlCircle();

    frame.setTitle("ControlCircle");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(800, 600);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }

  class EnlargeListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      canvas.enlarge();
    }
  }

  class ShrinkListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      //canvas.shrink();
    }
  }

  class CirclePanel extends JPanel {
    private int r = radius;

    public void enlarge() {
      //radius += 2;

      repaint();
    }

    public void shrink() {
      radius -= 2;

      repaint();
    }

    protected void paintComponent(Graphics g) {
      super.paintComponent(g);

      for (int i = 0; i < iter; i++) {
        g.drawOval(getWidth() / 2 - r, getHeight() / 2 - r, 2 * r, 2 * r);

        try {
          Thread.sleep(100);
        } catch (Exception exp) {
        }

        r = r + 10;
      }

      r = 0;
    }
  }
}
java multithreading swing paint event-listener
2个回答
2
投票

您遇到的问题很常见。

Swing 是一个单线程框架。这意味着所有与 UI 相关的交互都必须发生在该线程(也称为事件调度线程)的上下文中。

EDT 负责发送重绘请求等。如果代码的任何部分停止了该线程(阻塞 I/O,耗时的过程,

Thread.sleep
),EDT 将无法处理任何新事件。

请阅读Swing 中的并发以了解更多详细信息。

您现在面临两个问题...

  1. 您无法阻止 EDT
  2. 您无法从 EDT 以外的任何线程更新 UI。

幸运的是,有很多解决方案。最简单的是使用

javax.swing.Timer

此计时器在 EDT 内触发其滴答事件,但在其自己的线程内等待...

enter image description here

import com.sun.org.apache.bcel.internal.generic.LSTORE;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Droplets {

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

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

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new DropletPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
    protected static final int MAX_RADIUS = 50;
    protected static final int GROWTH_RATE = 1;

    public class DropletPane extends JPanel {

        private List<Droplet> droplets;

        public DropletPane() {
            droplets = new ArrayList<>(25);
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    droplets.add(new Droplet(e.getPoint()));
                }
            });

            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
                        droplet.grow();
                        if (droplet.getRadius() >= MAX_RADIUS) {
                            droplets.remove(droplet);
                        }
                    }
                    repaint();
                }
            });
            timer.setRepeats(true);
            timer.setCoalesce(true);
            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();
            Composite comp = g2d.getComposite();
            for (Droplet droplet : droplets) {

                float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
                g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
                Point p = droplet.getLocation();
                int radius = droplet.getRadius();
                g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
                g2d.setComposite(comp);

            }
            g2d.dispose();
        }
    }

    public class Droplet {

        private Point p;
        private int radius;

        public Droplet(Point p) {
            this.p = p;
        }

        public Point getLocation() {
            return p;
        }

        public int getRadius() {
            return radius;
        }

        public void grow() {
            radius += GROWTH_RATE;
            if (radius > MAX_RADIUS) {
                radius = MAX_RADIUS;
            }
        }
    }
}

扩展示例

当您单击“开始”按钮时,此示例将以随机间隔(每个液滴之间)创建随机数量的液滴。您可以多次按开始键,它会复合输出。

enter image description here

import static droplets.Droplets.MAX_RADIUS;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Droplets02 {

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

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

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new DropletPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
    protected static final int MAX_RADIUS = 50;
    protected static final int GROWTH_RATE = 1;

    public interface Pool {

        public void addDroplet(Droplet droplet);

        public Dimension getSize();
    }

    public class DropletPane extends JPanel implements Pool {

        private List<Droplet> droplets;
        private Timer timer;

        public DropletPane() {

            setLayout(new GridBagLayout());
            JButton button = new JButton("Start");
            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    new DropletWorker(DropletPane.this).execute();
                }
            });
            add(button);

            droplets = new ArrayList<>(25);
            timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (!droplets.isEmpty()) {
                        for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
                            droplet.grow();
                            if (droplet.getRadius() >= MAX_RADIUS) {
                                droplets.remove(droplet);
                            }
                        }
                        if (droplets.isEmpty()) {

                            ((Timer) e.getSource()).stop();

                        }
                        repaint();
                    }
                }
            });
            timer.setRepeats(true);
            timer.setCoalesce(true);

        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            Composite comp = g2d.getComposite();
            for (Droplet droplet : droplets) {

                float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
                g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
                Point p = droplet.getLocation();
                int radius = droplet.getRadius();
                g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
                g2d.setComposite(comp);

            }
            g2d.dispose();
        }

        @Override
        public void addDroplet(Droplet droplet) {
            if (!timer.isRunning()) {
                timer.start();
            }
            droplets.add(droplet);
        }
    }

    public class Droplet {

        private Point p;
        private int radius;

        public Droplet(Point p) {
            this.p = p;
        }

        public Point getLocation() {
            return p;
        }

        public int getRadius() {
            return radius;
        }

        public void grow() {
            radius += GROWTH_RATE;
            if (radius > MAX_RADIUS) {
                radius = MAX_RADIUS;
            }
        }
    }

    public class DropletWorker extends SwingWorker<Void, Droplet> {

        private Pool pool;

        public DropletWorker(Pool pool) {
            this.pool = pool;
        }

        public Pool getPool() {
            return pool;
        }

        protected int random(int minRange, int maxRange) {
            return minRange + (int) (Math.round(Math.random() * (maxRange - minRange)));
        }

        @Override
        protected Void doInBackground() throws Exception {

            int dropCount = random(1, 100);
            Pool pool = getPool();
            Dimension size = pool.getSize();
            for (int index = 0; index < dropCount; index++) {
                Thread.sleep(random(10, 1000));
                int x = random(0, size.width);
                int y = random(0, size.height);
                Droplet droplet = new Droplet(new Point(x, y));
                publish(droplet);
            }

            return null;
        }

        @Override
        protected void process(List<Droplet> chunks) {
            for (Droplet droplet : chunks) {
                getPool().addDroplet(droplet);
            }
        }
    }
}

动画基础知识

执行动画需要三件事。

  • A 开始状态
  • 目标状态
  • 增量或时间范围。

(你还需要一些方法来存储当前状态)

开始状态和目标状态是不言自明的,它们描述了您现在所处的位置以及您想要更改到的位置。

增量将是在每个“时间间隔”(或刻度)应用于当前状态的量,直到达到增量。

或者

时间范围是您想要从开始状态移动到结束状态所需的时间。

增量方法是更简单的机制,但不如时间范围方法灵活......

一旦设置了这些基本元素,您就需要某种定期触发的“tick”,它允许您计算当前状态,即从开始状态到目标状态(增量)的线性移动或随时间(时间范围)变化的进展

最终的全面返工

除了您尝试在绘制方法中阻止 EDT 并且未能遵循 Swing 的 Initial Thread 要求之外,我发现的唯一其他重要问题是您对

radius
iter
值的依赖.

基本上,除非你按下 Enter 键,否则这些永远不会被设置……而我却没有。

此示例使用您发布的代码以及第一个示例的想法...

enter image description here

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class ControlCircles extends JFrame {

    private JButton jbtEnlarge = new JButton("Start");
    private JButton jbtShrink = new JButton("Stop");
    private CirclePanel canvas = new CirclePanel();
    private JTextField f1 = new JTextField(8);
    private JTextField f2 = new JTextField(8);

    public ControlCircles() {
        JPanel panel = new JPanel();
        JPanel jp = new JPanel();
        jp.setPreferredSize(new Dimension(300, 0));
        panel.add(jbtEnlarge);
        panel.add(jbtShrink);

        this.add(jp, BorderLayout.WEST);
        this.add(canvas, BorderLayout.CENTER);
        this.add(panel, BorderLayout.SOUTH);


        jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
        jp.add(new JLabel("Radius"));
        jp.add(f1);
        jp.add(new JLabel("Iteration"));
        jp.add(f2);

        jbtEnlarge.addActionListener(new EnlargeListener());
        jbtShrink.addActionListener(new ShrinkListener());
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new ControlCircles();

                frame.setTitle("ControlCircle");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(800, 600);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }

        });
    }

    class EnlargeListener implements ActionListener {

        public void actionPerformed(ActionEvent e) {
            int radius = Integer.parseInt(f1.getText());
            int iter = Integer.parseInt(f2.getText());
            canvas.start(radius, iter);

        }

    }

    class ShrinkListener implements ActionListener {

        public void actionPerformed(ActionEvent e) {
            //canvas.shrink();
        }

    }

    class CirclePanel extends JPanel {

        private int radius;
        private int iterations;

        private int iteration;

        private List<Integer> circles;
        private Timer timer;

        public CirclePanel() {
            circles = new ArrayList<>(25);
            timer= new Timer(100, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    iteration++;
                    if (iteration < iterations) {
                        circles.add(radius);
                        radius += 10;
                    } else {
                        ((Timer)e.getSource()).stop();
                    }
                    repaint();
                }
            });
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            int width = getWidth() - 1;
            int height = getHeight()- 1;
            g.drawRect(0, 0, width, height);
            for (Integer radius : circles) {
                int x = (width - radius) / 2;
                int y = (height - radius) / 2;
                g.drawOval(x, y, radius, radius);
            }
        }

        public void start(int radius, int iter) {
            timer.stop();
            circles.clear();
            this.radius = radius;
            iterations = iter;
            iteration = 0;
            System.out.println("radius = " + radius);
            System.out.println("iterations = " + iterations);
            timer.start();
        }
    }
}

此代码基于您的问题描述,通过纠正 Swing 中动画的常见错误来工作,但您的一些代码对我来说不太有意义(即

enlarge
shrink
),所以我重点关注您的描述提供。


-1
投票

这个想法是通过按钮 Start

Stop
控制
用作画布的面板上的绘图动画,我添加了
Continue
Reset
附加控件以更好地解释这个想法。这些按钮控制动画线程的执行,从而在绘图表面上绘制圆圈。我将绘图表面分离为内部类,该内部类仅具有绘制执行的任何内容的功能。另一种想法是采用增量绘制的方式逐个绘制圆圈,直到完成绘制,因此使用了增量绘制。

我使用了上面的代码并对其进行了一些更改以支持我的想法。如果您需要更多且通常更好的示例,请查看这篇文章

代码如下,我没有对其进行足够的打磨以使其具有生产明智的外观和感觉,但仅用于演示目的。

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

public class ControlCircle extends JFrame implements Runnable {
  private JButton jbtStart = new JButton("Start");
  private JButton jbtStop = new JButton("Stop");
  private JButton jbtContinue = new JButton("Continue");
  private JButton jbtReset = new JButton("Reset");
  private CirclePanel canvas = new CirclePanel();

  private JTextField f1;
  private int radius = 0;

  private JTextField f2;
  private int iter;

  protected boolean  incrementalPainting;

  /**
   * Flag indicates that a thread is suspended
   */
  private boolean suspended = false;


  /**An instance of the class Thread.*/
  private Thread thread = null;

  public ControlCircle() {
    JPanel panel = new JPanel();
    JPanel jp = new JPanel();
    jp.setPreferredSize(new Dimension(300, 0));
    panel.add(jbtStart);
    panel.add(jbtStop);
    panel.add(jbtContinue);
    panel.add(jbtReset);

    this.add(jp, BorderLayout.WEST);
    this.add(canvas, BorderLayout.CENTER);
    this.add(panel, BorderLayout.SOUTH);

    f1 = new JTextField(8);
    f2 = new JTextField(8);

    jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
    jp.add(new JLabel("Radius"));
    jp.add(f1);
    jp.add(new JLabel("Iteration"));
    jp.add(f2);


    jbtStart.addActionListener(new StartListener());
    jbtStop.addActionListener(new StopListener());
    jbtContinue.addActionListener(new ContinueListener());
    jbtReset.addActionListener(new ResetListener());
  }

  public static void main(String[] args) {
    JFrame frame = new ControlCircle();

    frame.setTitle("ControlCircle");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(800, 600);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }

  class StartListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if (thread == null) {
        repaint();
        startThread();
      }
    }
  }

  class StopListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if (thread != null){
        mySuspend();
      }
    }
  }

  class ContinueListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      myResume();
    }
  }

  class ResetListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if (thread != null) {
        stopThread();
      }
      repaint();
    }

  }

  /**
   * my Suspend
   */
  private void mySuspend() {
    System.out.println("mySyspend()");
    suspended = true;
  }

  /**
   * my Resume
   */
  private synchronized void myResume(){
    System.out.println("myResume()");
    suspended = false;
    notifyAll();
  }

  public void run(){
    System.out.println("run() - started");

    Thread me = Thread.currentThread();
    while (thread == me) {
      radius = Integer.parseInt(f1.getText());
      iter = Integer.parseInt(f2.getText());
      for (int i = 0; i < iter; i++) {
        if (thread == null) return;
        incrementalPainting = true;
        myRepaint();
        try {
          Thread.sleep(1000);
        }
        catch(InterruptedException e){}
        radius += 10;
      }
      if(thread != null) thread = null; // exiting while
    }
    System.out.println("run() - exiting");
  }

  /**
   * start Thread
   */
  private void startThread(){
    System.out.println("startThread()");
    if(thread == null){
      thread = new Thread(this);
      thread.start();
    }
  }

  /**
   *  stop Thread
   */
  private synchronized void stopThread() {
    System.out.println("stopThread()");
    thread = null; // exiting from while
    if (suspended) {
      suspended = false;
      notify();
    }
  }

  /**
   * This is called from the run method to invoke painting.
   */
  private void myRepaint() {
    System.out.println("myRepaint()");
    incrementalPainting = true;
    repaint();
    synchronized (this) {
      while (incrementalPainting) {
        System.out.println("wait while incremental painting");
        try {
          wait();
        } catch (InterruptedException e) {
          System.out.println("interrupted");
        }
      }
    }

    suspend();
  }
  /**
   * This method should place somewhere when run() has started. Perfectly
   * when repaint() performed.
   */
  private void suspend(){
    System.out.println("suspend()");
    synchronized (this) {
      while (suspended) {
        System.out.println("wait while suspended");
        try {
          wait();
        } catch (InterruptedException e) {
          System.out.println("interrupted");
        }
      }
    }

  }

  public synchronized void myPaint(Graphics g) {
    if (g == null){
      if (incrementalPainting){
        incrementalPainting = false;
        notifyAll();
      }
      return;
    }
    if (incrementalPainting){
      myDraw(g);
      incrementalPainting = false;
      notifyAll();

    }
    else {
      myDraw(g);
    }
  }

  public void myDraw(Graphics g){
    g.drawOval(getWidth() / 2 - radius, getHeight() / 2 - radius, 2 * radius, 2 * radius);
  }

  protected final class CirclePanel extends JPanel {
    //Offscreen buffer of this canvas
    private BufferedImage backBuffer = null;

    public void paintComponent (Graphics g) {
      System.out.println("incrementalPainting="+incrementalPainting);
      // First paint background
      super.paintComponent(g);
      Dimension d = this.getSize();
      if (! incrementalPainting)
        backBuffer = (BufferedImage) this.createImage(d.width, d.height);
      Graphics2D g2 = backBuffer.createGraphics();
      if (! incrementalPainting){
        g2.setColor(Color.WHITE);
        g2.fillRect(0,0, d.width, d.height);
      }
      myPaint(g2);
      g.drawImage(backBuffer, 0, 0, this);

    }

  }

}
© www.soinside.com 2019 - 2024. All rights reserved.