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

标签 java multithreading swing paint event-listener

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

最佳答案

您遇到的问题非常常见。

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

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

通读一下Concurrency in 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);
            }
        }
    }
}

动画基础知识

执行动画需要三件事。

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

(您还需要某种方法来存储当前状态)

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

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

或者

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

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

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

最终的、全面的返工

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

基本上,除非您按下 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 中动画的常见错误来工作,但您的一些代码对我来说不太有意义(即 enlargeshr​​ink ),所以我重点关注您提供的描述。

关于java - 用于在框架内的面板上显示不同半径的圆的代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16857380/

相关文章:

java - 将非静态列表转换为静态列表

java - 如何从 ResultSet 类型的对象中获取一个 int 值?

java - 模拟 - 创建新文件(Java)

multithreading - Perl 线程 : Array of hash issue

c# - 使用构造函数启动一个新线程

java - 无法在 Java 中将字符串转换为整数

java - JAX-RS 客户端线程是否安全

java - 使类型行(x 行)中的标签在 jfreechart 中垂直

java - 使用 JFrame 创建颜色网格?

java - 具有 AbstractTableModel 的 JTable 不会在单击时进行编辑