Java Swing:通过鼠标点击JPanel制作一个不断增长的圆圈



我是Java的初学者,这次我试图通过查找代码示例并对其进行编辑来了解更多信息,例如从这个网站。我有一个 JFrame,每次点击它(或者更准确地说是其中的 JPanel)时,都会画一个圆圈,它会像水波纹一样向外增长/扩展。每个圆都以一定的半径开始,当达到更大的半径时将被移除或重新绘制(我从 10 到 200 选择了半径"r")。我有两个程序,几乎可以工作,但缺少一些东西。如果我是对的,我可能也知道他们的问题是什么,但我无法弄清楚如何解决它们:

一个生成增长的圆,但无论它们何时生成,它们都具有相同的大小,因为我找不到如何将半径分配给单个圆。代码改编自这里:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
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 javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Ripples extends JPanel implements ActionListener{
public int r = 10;
private ArrayList<Point> p;

public Ripples() {
p = new ArrayList<>();
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
p.add(new Point(e.getX(), e.getY()));
}
});
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.CYAN);
for (Point pt : p) {
g2.drawOval(pt.x-r, pt.y-r, 2*r, 2*r);
}
}


@Override
public void actionPerformed(ActionEvent evt) {
if(r<200){
r++;
} else {
r = 10;
}
repaint();
}

public static void Gui() {
JFrame f = new JFrame();
Ripples p = new Ripples();
p.setBackground(Color.WHITE);
f.setContentPane(p);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(500,300);
f.setVisible(true);
Timer t = new Timer(20,p);
t.start();
}
public static void main(String[] args) {
Gui();
}
}

另一个程序(我忘记了我是从哪里得到的)具有不同半径的圆,具体取决于它们的生成时间,但是圆圈"闪烁",因为 - 据我了解 - 它们都是同时处理的,因为代码不包含一个数组来单独存储和更新每个圆,就像上面的一样:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
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 javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Ripples extends JPanel { 
int x,y;
int r = 10;

public Ripples(int x, int y) {
this.x = x;
this.y = y; 
Timer t = new Timer(20, new ActionListener() { 
@Override
public void actionPerformed(ActionEvent e) { 
if (r<200) { 
r++;
} else {
r=10;
}
revalidate();
repaint();
}
}); 
t.start(); 
}
@Override
public void paintComponent(Graphics g) { 
super.paintComponent(g);
g.setColor(Color.CYAN);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.drawOval(x-r,y-r,2*r,2*r);
}

public static void Gui() {
JFrame f = new JFrame("Water Ripples");
JPanel p0 = new JPanel();
p0.setBackground(Color.WHITE);
f.add(p0);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setBounds(100,100,600,500);
f.setVisible(true);
f.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) { 
Ripples rG = new Ripples(e.getX(), e.getY());
rG.setBackground(Color.WHITE);
f.add(rG); 
}
});
}
public static void main(String[] args) {
Gui();
}

}

那么我怎样才能解决这个问题,让圈子彼此独立呢?我更喜欢上层代码的解决方案/改进/提示,因为我认为它的结构比第二个更好。另外,我很抱歉没有将代码拆分为更多类,并且可能没有坚持命名约定。感谢您的帮助,非常感谢!

我在Ripples代码中添加了一个Circle类。 这允许ActionListener独立处理每个圆圈。

我通过调用SwingUtilitiesinvokeLater方法启动了 GUI。 此方法可确保在事件调度线程上创建和执行 Swing 组件。

这是代码。

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
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.SwingUtilities;
import javax.swing.Timer;
public class Ripples extends JPanel implements ActionListener {
private List<Circle> circles;
public Ripples() {
circles = new ArrayList<>();
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent event) {
circles.add(new Circle(event.getPoint()));
}
});
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);

Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.CYAN);
g2.setStroke(new BasicStroke(3f));

for (Circle circle : circles) {
Point p = circle.getCenter();
int radius = circle.getRadius();
g2.drawOval(p.x - radius, p.y - radius,
2 * radius, 2 * radius);
}
}
@Override
public void actionPerformed(ActionEvent evt) {
for (Circle circle : circles) {
circle.incrementRadius();
}
repaint();
}
public static void createGUI() {
JFrame f = new JFrame("Ripples");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Ripples p = new Ripples();
p.setBackground(Color.WHITE);
p.setPreferredSize(new Dimension(500, 500));
f.setContentPane(p);
f.pack();
f.setLocationByPlatform(true);
f.setVisible(true);
Timer t = new Timer(20, p);
t.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
createGUI();
}
});
}
public class Circle {
private int radius;
private final Point center;
public Circle(Point center) {
this.center = center;
this.radius = 10;
}
public void incrementRadius() {
radius += 1;
radius = (radius > 200) ? 10 : radius;
}
public int getRadius() {
return radius;
}
public Point getCenter() {
return center;
}
}
}

编辑添加:

我重新设计了Ripples类代码以分离关注点。 我创建了一个用于保存绘图面板的DrawingPanel类,创建了一个用于保存MouseAdapter代码的RipplesListener类,创建了一个用于保存运行圆动画的RunnableAnimation类,创建了一个用于保存Circle实例ListRipplesModel类,最后是Circle类。

我本可以为动画使用 SwingTimer,但我更熟悉创建和运行自己的动画线程。

是的,此代码比原始示例更复杂。 这里使用的编码风格可以带入更大、更复杂的 Swing GUI 开发中。

这是修订后的代码。 我希望这是一个更好的例子。

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Ripples implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Ripples());
}
private Animation animation;
private DrawingPanel drawingPanel;
private RipplesModel model;
public Ripples() {
model = new RipplesModel();
}
@Override
public void run() {
JFrame frame = new JFrame("Ripples");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent event) {
stopAnimation();
frame.dispose();
System.exit(0);
}
});
drawingPanel = new DrawingPanel(model);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation(this, model);
new Thread(animation).start();
}
public void repaint() {
drawingPanel.repaint();
}
private void stopAnimation() {
if (animation != null) {
animation.setRunning(false);
}
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private RipplesModel model;
public DrawingPanel(RipplesModel model) {
this.model = model;
setBackground(Color.WHITE);
setPreferredSize(new Dimension(500, 500));
addMouseListener(new RipplesListener(model));
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(3f));
List<Circle> circles = model.getCircles();
for (Circle circle : circles) {
Point p = circle.getCenter();
int radius = circle.getRadius();
g2.setColor(circle.getColor());
g2.drawOval(p.x - radius, p.y - radius,
2 * radius, 2 * radius);
}
}
}
public class RipplesListener extends MouseAdapter {
private RipplesModel model;
public RipplesListener(RipplesModel model) {
this.model = model;
}
@Override
public void mousePressed(MouseEvent event) {
model.addCircle(new Circle(event.getPoint(),
createColor()));
}
private Color createColor() {
Random random = new Random();
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
return new Color(r, g, b);
}
}
public class Animation implements Runnable {
private volatile boolean running;
private Ripples frame;
private RipplesModel model;
public Animation(Ripples frame, RipplesModel model) {
this.frame = frame;
this.model = model;
this.running = true;
}
@Override
public void run() {
while (running) {
sleep(20L);
incrementRadius();
}
}
private void incrementRadius() {
List<Circle> circles = model.getCircles();
for (Circle circle : circles) {
circle.incrementRadius();
}
repaint();
}
private void sleep(long delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
frame.repaint();
}
});
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
}
public class RipplesModel {
private List<Circle> circles;
public RipplesModel() {
this.circles = new ArrayList<>();
}
public void addCircle(Circle circle) {
this.circles.add(circle);
}
public List<Circle> getCircles() {
return circles;
}
}
public class Circle {
private int radius;
private final Color color;
private final Point center;
public Circle(Point center, Color color) {
this.center = center;
this.color = color;
this.radius = 10;
}
public void incrementRadius() {
radius = (++radius > 200) ? 10 : radius;
}
public Color getColor() {
return color;
}
public int getRadius() {
return radius;
}
public Point getCenter() {
return center;
}
}
}

我更喜欢上层代码的解决方案/改进/提示

第二个代码更好,因为它使用:

  1. 一个自定义类,用于包含有关要绘制的对象的信息
  2. 一个数组列表,用于包含要绘制的对象
  3. 动画的计时器。

因为我认为它的结构比第二个更好。

不是一个好理由。使用提供所需功能的代码。

自己重构代码。这是学习经历的一部分。

第二个代码的问题:

  1. 它不编译。为什么要发布无法编译的代码?这意味着你甚至还没有测试过它。
  2. 初始半径是在创建Position对象时指定的。
  3. 当计时器触发时,您需要循环访问 ArrayList 以更新每个Position对象的半径。
  4. Position对象的半径在绘制代码中使用。

作为附加更改,也许将Position类称为Ripple。然后,您可以为纹波币的Color添加另一个自定义属性。然后,当您将Ripple添加到数组列表时,您将随机生成一种颜色。然后在绘制方法中使用Ripple类的 Color 属性。这就是你如何使物体和绘画更加灵活和动态。

最新更新