0

这是用于在具有给定延迟率的帧内的面板上显示具有不同半径的圆圈的代码,但代码显示的是最终输出而不是中间阶段,即圆圈没有一个一个出现,但所有的圆圈都来了一次作为最终输出。可能有一些与按钮操作侦听器和面板线程相关的错误。代码采用初始圆半径和迭代总数(要显示的圆的总数),每个下一个圆的半径增加 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;
    }
  }
}
4

2 回答 2

2

你遇到的问题很普遍。

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

除其他外,EDT 负责发送重绘请求。如果您的代码的任何部分停止此线程(阻塞 I/O、耗时的进程Thread.sleep),EDT 将无法处理任何新事件。

阅读Swing 中的并发以获取更多详细信息。

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

  1. 你不能阻止 EDT
  2. 您不能从除 EDT 之外的任何线程更新 UI。

幸运的是,有许多解决方案。最简单的是使用javax.swing.Timer.

这个计时器在 EDT 中触发它的滴答事件,但在它自己的线程中等待......

在此处输入图像描述

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;
            }
        }
    }
}

扩展示例

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

在此处输入图像描述

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 开始状态
  • 目标状态
  • 增量或时间范围。

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

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

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

或者

时间范围是您希望从开始状态移动到结束状态的时间量。

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

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

最终的、完整的工作返工

除了您试图在绘制方法中阻止 EDT 并且未能遵循 Swing 的初始线程要求之外,我发现的唯一另一个重要的问题是您对radiusanditer值的依赖。

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

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

在此处输入图像描述

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 中动画的常见错误来根据您的问题描述工作,但您的某些代码对我来说不太有意义(即enlargeshrink),因此我专注于您提供的描述。

于 2013-05-31T23:46:05.940 回答
-1

这个想法是用按钮来控制用作画布的面板上的绘图动画StartStop我添加了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);

    }

  }

}
于 2013-06-01T14:13:14.330 回答