I am trying to draw circle objects with each click and then store every circle object into an Arraylist, I don't know why my program is not working! If I removed the arraylist and the line that create a new circle object, the program will work. How would I make my program store all circuit objects into an Arraylist ?
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Random;
public class CircleObj extends JPanel {
private int rColor;
private int gColor;
private int bColor;
private int radius;
private Random rand = new Random();
private int xStart;
private int yStart;
ArrayList <Circle> xxx ;
public CircleObj () {
xxx = new ArrayList<Circle>();
addMouseListener(new MouseAdapter() {
public void mouseClicked (MouseEvent e) {
xStart = e.getX();
yStart = e.getY();
rColor = rand.nextInt(256);
gColor = rand.nextInt(256);
bColor = rand.nextInt(256);
radius = rand.nextInt(20);
repaint();
}
}); // end addMouseListener
}
public void paintComponent (Graphics g) {
super.paintComponent(g);
g.setColor(new Color(rColor, gColor, bColor));
g.fillOval(xStart, yStart, radius, radius);
xxx.add(new Circle());
}
private class Circle {
private int x;
private int y;
private int r;
private int rcol;
private int gcol;
private int bcol;
public Circle()
{
x=xStart;
y=yStart;
r=radius;
rcol= rColor;
gcol= gColor;
bcol= bColor;
}
}
}
======
import javax.swing.JFrame;
import java.awt.BorderLayout;
public class HW3 {
public static void main (String[] arg) {
JFrame frame = new JFrame("Circles");
CircleObj canvas = new CircleObj();
frame.add(canvas, BorderLayout.CENTER);
frame.setBounds(250, 98, 600, 480);
//frame.setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
} // end main
} //end HW3
Don't add the new shape inside the paintComponent method, paintComponent can be called for any number of reasons, many of which you don't control, instead, create it when the mouseClicked event is triggered...
public void mouseClicked (MouseEvent e) {
xStart = e.getX();
yStart = e.getY();
rColor = rand.nextInt(256);
gColor = rand.nextInt(256);
bColor = rand.nextInt(256);
radius = rand.nextInt(20);
xxx.add(new Circle(xStart, yStart, new Color(rColor, gColor, bColor), radius));
repaint();
}
And then in your paintComponent, loop through the ArrayList and paint the circles...
public void paintComponent (Graphics g) {
super.paintComponent(g);
for (Circle c : xxx) {
g.setColor(c.getColor());
g.fillOval(c.getX(), c.getY(), c.getRadius(), c.getRadius());
}
}
Now, you're going to have to modify you Circle class to provide getters which the CircleObj can use in order to actually paint the circles...
Alternatively, you could make use of the Shapes API provided within Java...Have a look at Working with Geometry for more details...
Related
Thanks in advance for help
I created a program that makes multiple bouncing balls When user clicks on the screen a new ball should appear and move around screen. But when i click on the screen a ball appears and doesn't moving at all. When another click happens, the ball created previously jumped to another position instantly.
this is the ball class: used to create balls
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.util.Random;
import javax.swing.JComponent;
public class Ball extends JComponent implements Runnable{
private final int DIAMETER = 25;
private final int v1 = 5;
private final int v2 = -5;
private final Random rnd = new Random();
private int posX;
private int posY;
private Color color;
private int xVelocity;
private int yVelocity;
public Ball(int posX, int posY) {
this.posX = posX;
this.posY = posY;
this.color = randomColor();
this.xVelocity = rnd.nextBoolean()?v1:v2;
this.yVelocity = rnd.nextBoolean()?v1:v2;
}
public void move() {
if (posX < 15) {
xVelocity = -xVelocity;
} else if(posX > 475) {
xVelocity = -xVelocity;
}
if (posY < 0) {
yVelocity = -yVelocity;
} else if(posY > 475) {
yVelocity = -yVelocity;
}
posX +=xVelocity;
posY +=yVelocity;
}
public void draw(Graphics2D g2) {
g2.setColor(color);
g2.fill(new Ellipse2D.Double(posX,posY,DIAMETER,DIAMETER));
}
private static Color randomColor() {
int r = (int)(Math.random()*255);
int g = (int)(Math.random()*255);
int b = (int)(Math.random()*255);
Color rColor = new Color(r,g,b);
return rColor;
}
#Override
public void run() {
while(!Thread.interrupted()) {
move();
repaint();
try {
Thread.sleep(60);
} catch (InterruptedException ex) {}
}
}
}
this is the ballcomponent class: used to create the panel & display the balls
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BallComponent extends JPanel{
private ArrayList<Ball> bList;
public BallComponent() {
bList = new ArrayList<Ball>();
this.setPreferredSize(new Dimension(500,500));
this.setBackground(Color.BLACK);
this.addMouseListener(new ClickListener());
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
super.paintComponent(g2);
for (Ball a : bList) {
a.draw(g2);
}
}
private class ClickListener extends MouseAdapter{
public void mouseClicked(MouseEvent e) {
Ball a = new Ball(e.getX(),e.getY());
bList.add(a);
repaint();
Thread gameThread = new Thread(a);
gameThread.start();
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setTitle("Bouncing Balls");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new BallComponent());
frame.pack();
frame.setVisible(true);
}
}
Since you have a bunch of threads doing random stuff. You might as well have your JPanel update on it's own.
At the end of your main method.
new Thread( ()->{
while(true){
frame.repaint();
try{
Thread.sleep(60);
} catch(Exception e){
break;
}
}
}).start();
Usually you would do this with a timer task, but since you said you couldn't use a Timer, I just wrote a thread version.
I think repaint() is safe to call from off of the EDT since it just schedules a repaint. The paintComponent method and click method will only be called on the EDT. So you shouldn't get CCME. There are a bunch of race conditions with the multiple threads, but it seems like the only problem would be balls drawn out of position.
I have a code that has a button. When a button is pressed, circles appear at random positions with random colors. There can only be 10 circles.
Now that I added random colors functionality, the problem is that after each circle is drawn, its color starts changing infinetely.
How can I make it so the colors don't change?
class Panel extends JPanel {
private JButton button;
private Ellipse2D.Double[] circles;
Integer count;
public Panel() {
setup();
}
private void setup() {
count=new Integer(0);
circles=new Ellipse2D.Double[10];
button=new JButton(count.toString());
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Random r=new Random();
//position circles with diameter 100 in a way
//that it would fit in a window's size
int highX=getWidth()-100;
int highY=getHeight()-100;
circles[count]=new
Ellipse2D.Double(r.nextInt(highX),
r.nextInt(highY), 100, 100);
count++;
button.setText(count.toString());
}
});
add(button);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
paintStuff(g);
repaint();
}
private void paintStuff(Graphics g) {
Graphics2D g2=(Graphics2D) g;
g2.setPaint(Color.RED);
if (count!=0) {
for (int i=0; i<count; i++) {
g2.draw(circles[i]);
Random r=new Random();
int red=r.nextInt(256);
int green=r.nextInt(256);
int blue=r.nextInt(256);
g2.setPaint(new Color(red, green, blue));
g2.fill(circles[i]);
}
}
}
}
public class Frame extends JFrame {
private Panel panel;
public Frame() {
panel=new Panel();
add(panel);
}
public static void main(String[] args) {
Frame frame=new Frame();
}
}
Never call repaint within a painting method as that causes a "poor-man's" animation to occur. Instead call it in your JButton's ActionListener. Also, don't randomize within the painting method, but rather do this within the ActionListener. The painting method is not under your control, and you don't want to use it to change your object's state, but rather only to display it.
Other suggestions:
Your code still needs to set the JFrame's setDefaultCloseOperation
and still needs to set the JFrame visible
You never suggest sizing in the code. Myself, I recommend overriding public Dimension getPreferredSize() of your JPanel and call pack() on the JFrame after adding the JPanel but before displaying it.
I'd rename your classes so that the names don't clash with core Java classes and cause confusion to your instructors, us, or your future self.
Don't keep re-creating a new Random object within the for loop. Rather why not simply give the class a Random field, create it once, but reuse the object repeatedly.
You will want to associate a color with your shape/Ellipse2D. For a one-to-one correspondence, consider using a Map such as a HashMap<Shape, Color>.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.Ellipse2D;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import javax.swing.*;
#SuppressWarnings("serial")
public class Panel2 extends JPanel {
// preferred size constants
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
// map to hold circles and colors
private Map<Shape, Color> shapeColorMap = new LinkedHashMap<>();
public Panel2() {
add(new JButton(new RandomColorAction()));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// create *smooth* drawings
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
paintStuff(g2);
}
private void paintStuff(Graphics2D g2) {
// iterate through our map extracting all circles and colors
// and drawing them
for (Entry<Shape, Color> entry : shapeColorMap.entrySet()) {
Shape shape = entry.getKey();
Color color = entry.getValue();
g2.setColor(color);
g2.fill(shape);
}
}
// listener for our button
private class RandomColorAction extends AbstractAction {
private static final int CIRC_WIDTH = 100;
private Random random = new Random();
private int count = 0;
public RandomColorAction() {
super("Random Circle: 0");
putValue(MNEMONIC_KEY, KeyEvent.VK_R);
}
#Override
public void actionPerformed(ActionEvent e) {
// create our random ellipses
int x = random.nextInt(getWidth() - CIRC_WIDTH);
int y = random.nextInt(getHeight() - CIRC_WIDTH);
Shape shape = new Ellipse2D.Double(x, y, CIRC_WIDTH, CIRC_WIDTH);
// create our random color using HSB for brighter colors
float hue = random.nextFloat();
float saturation = (float) (0.8 + random.nextFloat() * 0.2);
float brightness = (float) (0.8 + random.nextFloat() * 0.2);
Color color = Color.getHSBColor(hue, saturation, brightness);
shapeColorMap.put(shape, color);
// increment count, place items into map, repaint
count++;
putValue(NAME, "Random Circle: " + count);
repaint();
}
}
private static void createAndShowGui() {
Panel2 mainPanel = new Panel2();
JFrame frame = new JFrame("Panel2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
In comments, Camickr astutely points out:
A painting method should paint the current state of the component. By using the HashMap you are introducing the possibility of randomness. The order of iteration through the map can't be guaranteed. Therefore as new entries are added to the map the order each Shape is painted could change. Generally not a problem, but if two random shapes ever overlap, the result good be flip flopping which shape is painted on top of one another.
And of course, he is absolutely correct, since there is no guaranteed order for a HashMap. Fortunately the variable itself was declared to be of Map type, and so to preserve order all one needs to do is to change the actual object type from HashMap to that of LinkedHashMap, a class which per its API:
This implementation spares its clients from the unspecified, generally chaotic ordering provided by HashMap (and Hashtable), without incurring the increased cost associated with TreeMap.
So for TLDR, change this:
private Map<Shape, Color> shapeColorMap = new HashMap<>();
to this:
private Map<Shape, Color> shapeColorMap = new LinkedHashMap<>();
Edited to fix the color calculation.
A just for the fun of it version that introduces Path2D and AffineTransform with a MouseListener/MouseMotionListener to allow for dragging the circles:
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.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import javax.swing.*;
#SuppressWarnings("serial")
public class Panel2 extends JPanel {
// preferred size constants
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
// map to hold circles and colors
private Map<Shape, Color> shapeColorMap = new LinkedHashMap<>();
public Panel2() {
add(new JButton(new RandomColorAction()));
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// create *smooth* drawings
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
paintStuff(g2);
}
private void paintStuff(Graphics2D g2) {
// iterate through our map extracting all circles and colors
// and drawing them
for (Entry<Shape, Color> entry : shapeColorMap.entrySet()) {
Shape shape = entry.getKey();
Color color = entry.getValue();
g2.setColor(color);
g2.fill(shape);
}
}
private class MyMouse extends MouseAdapter {
private Entry<Shape, Color> selected = null;
private Path2D path;
private Point p = null;
#Override
public void mousePressed(MouseEvent e) {
Set<Entry<Shape, Color>> entrySet = shapeColorMap.entrySet();
// get Shape pressed
for (Entry<Shape, Color> entry : entrySet) {
if (entry.getKey().contains(e.getPoint())) {
selected = entry;
}
}
if (selected != null) {
path = new Path2D.Double(selected.getKey());
// move it to the top
entrySet.remove(selected);
shapeColorMap.put(path, selected.getValue());
p = e.getPoint();
repaint();
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (selected != null) {
moveSelected(e);
}
selected = null;
}
#Override
public void mouseDragged(MouseEvent e) {
if (selected != null) {
moveSelected(e);
}
}
private void moveSelected(MouseEvent e) {
int x = e.getX() - p.x;
int y = e.getY() - p.y;
p = e.getPoint();
AffineTransform at = AffineTransform.getTranslateInstance(x, y);
path.transform(at);
repaint();
}
}
// listener for our button
private class RandomColorAction extends AbstractAction {
private static final int CIRC_WIDTH = 100;
private Random random = new Random();
private int count = 0;
public RandomColorAction() {
super("Random Circle: 0");
putValue(MNEMONIC_KEY, KeyEvent.VK_R);
}
#Override
public void actionPerformed(ActionEvent e) {
// create our random ellipses
int x = random.nextInt(getWidth() - CIRC_WIDTH);
int y = random.nextInt(getHeight() - CIRC_WIDTH);
Shape shape = new Ellipse2D.Double(x, y, CIRC_WIDTH, CIRC_WIDTH);
// create our random color using HSB for brighter colors
float hue = random.nextFloat();
float saturation = (float) (0.8 + random.nextFloat() * 0.2);
float brightness = (float) (0.8 + random.nextFloat() * 0.2);
Color color = Color.getHSBColor(hue, saturation, brightness);
shapeColorMap.put(shape, color);
// increment count, place items into map, repaint
count++;
putValue(NAME, "Random Circle: " + count);
repaint();
}
}
private static void createAndShowGui() {
Panel2 mainPanel = new Panel2();
JFrame frame = new JFrame("Panel2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
paintStuff(Graphics g);
Is called many times, and each time it refreshes the circle color. That's the wrong place to set the color, you need to set it when you add the circle.
Create a java.awt.Color array as a global variable
private Color[] circlesColors;
Then just fill this array in the actionPerformed(...) method. This is the setupmethod with the changes
private void setup() {
count=new Integer(0);
circles=new Ellipse2D.Double[10];
circlesColors = new Color[10]; //Init the colors array to the same size of circles array
button=new JButton(count.toString());
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Random r=new Random();
int highX=getWidth()-100;
int highY=getHeight()-100;
circles[count]=new Ellipse2D.Double(r.nextInt(highX), r.nextInt(highY), 100, 100);
circlesColors[count] = new Color(r.nextInt(256), r.nextInt(256), r.nextInt(256)); //Assign random color
count++;
button.setText(count.toString());
}
});
add(button);
}
Then in your paint(...) method
private void paintStuff(Graphics g) {
Graphics2D g2=(Graphics2D) g;
g2.setPaint(Color.RED);
if (count!=0) {
for (int i=0; i<count; i++) {
g2.draw(circles[i]);
g2.setPaint(circlesColors[i]); //Get and set the color associated to the circle
g2.fill(circles[i]);
}
}
}
I'm having issues drawing some circles to my JFrame. I originally had it using the default layout and realized this was only adding the most recent circle, so I changed the layout to null, and now nothing gets drawn. I've also tried frame.setLayout(new FlowLayout()) which also doesn't draw anything. Any help would be appreciated!
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
/**
* #author Christopher Nielson
*
*/
public class Main {
private static JFrame frame;
private static Random rand;
private static Jiggler jiggler;
private static ArrayList<JComponent> circles;
private static int fps;
public static void main(String[] args) {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
frame.setBounds(100, 100, 450, 450);
rand = new Random();
circles = new ArrayList<JComponent>();
int x = frame.getWidth();
int y = frame.getHeight();
for (int i = 0; i < Integer.parseInt(args[0]); i++) {
circles.add(new Circle(rand.nextInt(frame.getWidth()), rand.nextInt(frame.getHeight()),
rand.nextInt(frame.getWidth() / 10) + 100, rand.nextInt(frame.getHeight() / 10) + 100, null));
}
circles.forEach(current -> {
frame.add(current);
});
frame.setVisible(true);
jiggler = new Jiggler(circles, new JLabel("FPS: ")); // TODO add fps
jiggler.run();
}
}
And this is one reason you'll see us recommending time and time again to avoid using null layouts like the plague.
Having said that, your main problem is a design problem, not a layout problem, and that problem being that your Circle class shouldn't extend JComponent or any component for that matter, since if you want to draw multiple circles, you should have only one component, probably a JPanel doing the drawing, and the Circles should be logical classes, classes that have a public void draw(Graphics g) method, not component classes. You would pass the List of Circles to your drawing JPanel, and it would draw the Circles in its paintComponent method by calling the draw(g) methods of each Circle in the list.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawChit extends JPanel {
private static final int PREF_W = 900;
private static final int PREF_H = 700;
private static final int MAX_SHAPES = 30;
private List<MyShape> shapes = new ArrayList<>();
public DrawChit() {
setBackground(Color.WHITE);
for (int i = 0; i < MAX_SHAPES; i++) {
double x = (PREF_W - 100) * Math.random();
double y = (PREF_H - 100) * Math.random();
double w = 100 + (Math.random() * PREF_W) / 10;
double h = 100 + (Math.random() * PREF_H) / 10;
Ellipse2D ellipse = new Ellipse2D.Double(x, y, w, h);
float hue = (float) Math.random();
double delta = 0.3;
float saturation = (float) (Math.random() * delta + (1 - delta));
float brightness = (float) (Math.random() * delta + (1 - delta));
Color color = Color.getHSBColor(hue, saturation, brightness);
shapes.add(new MyShape(ellipse, color));
}
// we'll throw a black square in the middle!
int rectW = 200;
int rectX = (PREF_W - rectW) / 2;
int rectY = (PREF_H - rectW) / 2;
shapes.add(new MyShape(new Rectangle(rectX, rectY, rectW, rectW), Color.BLACK));
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// use anti-aliasing to make graphics smooth
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// iterate through the shapes list, filling all
for (MyShape shape : shapes) {
shape.fill(g2);
}
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class MyMouse extends MouseAdapter {
private Point p0 = null;
private MyShape shape = null;
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
// iterate *backwards* so get top-most Shape
for (int i = shapes.size() - 1; i >= 0; i--) {
if (shapes.get(i).contains(e.getPoint())) {
p0 = e.getPoint();
shape = shapes.get(i);
// move selected shape to the top!
shapes.remove(shape);
shapes.add(shape);
repaint();
return;
}
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (p0 != null) {
moveShape(e.getPoint());
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (p0 != null) {
moveShape(e.getPoint());
p0 = null;
shape = null;
}
}
// translates the shape
private void moveShape(Point p1) {
int deltaX = p1.x - p0.x;
int deltaY = p1.y - p0.y;
shape.translate(deltaX, deltaY);
p0 = p1;
repaint();
}
}
private static void createAndShowGui() {
DrawChit mainPanel = new DrawChit();
JFrame frame = new JFrame("Draw Chit");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MyShape {
private Path2D path = new Path2D.Double();
private Color color;
public MyShape(Shape shape, Color color) {
path.append(shape, true);
this.color = color;
}
public boolean contains(Point p) {
return path.contains(p);
}
public void draw(Graphics2D g2) {
g2.setColor(color);
g2.draw(path);
}
public void fill(Graphics2D g2) {
g2.setColor(color);
g2.fill(path);
}
public void translate(int deltaX, int deltaY) {
path.transform(AffineTransform.getTranslateInstance(deltaX, deltaY));
}
}
So I'm trying to draw 2 circles on top of each other (kinda like a snowman) and move the snowman to the right when the user clicks on the "Start" button and stop moving the snowman when the user clicks on the "Stop" button. However, the only thing that I am able to come up with is 2 snowmen drawn next to each other that don't react to the buttons.
Here is what I've come up with:
import java.awt.Graphics2D;
public interface MoveableShape {
void draw(Graphics2D g);
void translate(int dx, int dy);
}
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
public class SnowmanShape implements MoveableShape {
private int x;
private int y;
private int width;
public SnowmanShape(int x, int y, int width){
this.x = x;
this.y = y;
this.width = width;
}
#Override
public void draw(Graphics2D g2) {
// TODO Auto-generated method stub
Ellipse2D.Double head = new Ellipse2D.Double(0, 0, 10, 10);
Ellipse2D.Double body = new Ellipse2D.Double(0, 11, 10, 10);
g2.draw(head);
g2.draw(body);
}
#Override
public void translate(int dx, int dy) {
// TODO Auto-generated method stub
x += dx;
y += dy;
}
}
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class MyPanel extends JPanel{
MoveableShape s;
public MyPanel (MoveableShape m){
s = m;
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
s.draw((Graphics2D)g);
}
}
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class AnimationTester {
private static final int DEFAULT_WIDTH = 400;
private static final int DEFAULT_HEIGHT = 200;
private static final int SNOWMAN_WIDTH = 50;
final static MoveableShape shape = new SnowmanShape(0, 0, SNOWMAN_WIDTH);
final static JPanel panel = new MyPanel(shape);
public static void main(String[] args) {
// TODO Auto-generated method stub
JFrame frame = new JFrame();
frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
JButton startButton = new JButton("Start");
JButton stopButton = new JButton("Stop");
frame.setLayout(new BorderLayout());
frame.add(panel);
frame.add(startButton, BorderLayout.NORTH);
frame.add(stopButton, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
final int DELAY = 100;
// Milliseconds between timer ticks
Timer t = new Timer(DELAY, translateSnowman());
startButton.addActionListener(startTimer(t));
stopButton.addActionListener(stopTimer(t));
}
public static ActionListener translateSnowman(){
return new ActionListener(){
public void actionPerformed(ActionEvent event){
shape.translate(1, 0);
panel.repaint();
}
};
}
public static ActionListener startTimer(final Timer t){
return new ActionListener(){
public void actionPerformed(ActionEvent event){
t.start();
}
};
}
public static ActionListener stopTimer(final Timer t){
return new ActionListener(){
public void actionPerformed(ActionEvent event){
t.stop();
}
};
}
}
Could someone please let me know where I went wrong or point me in the right direction?
EDIT: I fixed up the AnimationListener so now it doesn't draw 2 snowmans. The snowman still won't move however. I updated the code in the post as well.
Add g2.translate(x, y); to SnowmanShape#draw, before you are painting:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
class SnowmanShape implements MoveableShape {
static final Color headColor = new Color(0xFFE9C9);
static final Color bodyColor = new Color(0xEAF6FF);
static final Color outlineColor = new Color(0x252525);
int x;
int y;
int size;
Ellipse2D.Double head;
Ellipse2D.Double body;
SnowmanShape(int x, int y, int size) {
this.x = x;
this.y = y;
this.size = size;
initModel();
}
void initModel() {
head = new Ellipse2D.Double(0, 0, size, size);
body = new Ellipse2D.Double(0, head.height, size * 1.3d, size * 1.5d);
body.x -= (body.width - head.width) * (1 / 2d);
}
#Override
public void draw(Graphics2D g2) {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2.translate(x, y);
g2.setColor(headColor);
g2.fill(head);
g2.setColor(outlineColor);
g2.draw(head);
g2.setColor(bodyColor);
g2.fill(body);
g2.setColor(outlineColor);
g2.draw(body);
}
#Override
public void translate(int dx, int dy) {
x += dx;
y += dy;
}
}
I not an expert on doing things like this, but if I were you I would use the timer to move the objects and then call the paint to repaint the objects in a new position. Therefore, your ShapeIcon class would just keep track of the position of your objects. That probably wasn't all that helpful, so to point you in right direction, you can check out some code in this tutorial here.
I want a point to move towards the mouse position. Currently the point just is the mouse location. But I want it so that the bigger the distance between mouse and center of the screen, the less force get´s added to the point. So if the mouse is enough distance away from the center, the point will just stop moving further outwards because it´s force away from the center is to small. The center should always be the point which is looked at. So no repositioning.
I am really stuck here because I am pretty young and I didn´t have had vector calculation in school (I think this is what you need here). I have no idea how to do what I am trying to archive. The only thing I tried was using the distance between center and mouse (Math.hypot(width / 2 - mouseX , height / 2 - mouseY)). But it didn´t work.
This is my minimal reproducible example:
import javax.swing.*;
import java.awt.*;
public class Example extends JPanel {
private static final int size = 500;
public Example() {
this.setPreferredSize(new Dimension(size, size));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.fillOval(getWidth() / 2 - 3, getHeight() / 2 - 3, 6, 6); // center
Point point = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(point, this);
// Do calculations with the point here
g2d.fillOval(point.x - 5, point.y - 5, 10, 10);
repaint();
}
private static void createFrame() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new Example());
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(Example::createFrame);
}
}
It would look like this:
You can declare an attraction ratio value, which will strech the circle to the center of the screen, based on its value.
You need also be able to know for each cursor position how far it is from the center in percentages.
import javax.swing.*;
import java.awt.*;
public class Example extends JPanel {
private static final int SIZE = 500;
private static final int CENTER_CIRCLE_RADIUS = 3;
private static final int CIRCLE_RADIUS = 5;
private static final double ATTRACTION_RATIO = 0.75;
public Example() {
this.setPreferredSize(new Dimension(SIZE, SIZE));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
final int xCenterPoint = getWidth() / 2;
final int yCenterPoint = getHeight() / 2;
g2d.fillOval(xCenterPoint - CENTER_CIRCLE_RADIUS, yCenterPoint - CENTER_CIRCLE_RADIUS,
CENTER_CIRCLE_RADIUS * 2, CENTER_CIRCLE_RADIUS * 2); // center
Point point = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(point, this);
final double xNearToCenter = 1 - ((double) (xCenterPoint - point.x) / xCenterPoint);
final double yNearToCenter = 1 - ((double) (yCenterPoint - point.y) / yCenterPoint);
g2d.fillOval((int) (xCenterPoint * (ATTRACTION_RATIO + xNearToCenter * (1 - ATTRACTION_RATIO))),
(int) (yCenterPoint * (ATTRACTION_RATIO + yNearToCenter * (1 - ATTRACTION_RATIO))),
CIRCLE_RADIUS * 2, CIRCLE_RADIUS * 2);
repaint();
}
private static void createFrame() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Example panel = new Example();
f.add(panel);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(Example::createFrame);
}
}
I modified your code somewhat to create the following GUI.
The circle will follow your mouse around the drawing JPanel.
The first thing I did was create a Circle class. This class holds the center point, radius, and color of the circle. I use a Point2D class to hold the center point so I can move the circle in fractional pixel increments. This makes the animation smoother.
Next, I used your code to create a JFrame and a drawing JPanel. I like to separate the JFrame code from the JPanel code. It makes it easier for me to focus on one Swing component at a time.
The drawing JPanel draws the circle. Period. The controller classes are responsible for calculating the new position of the circle and calling the repaint method of the drawing JPanel.
I created a MouseMotionListener to listen for the mouse movement. This class keeps track of the mouse position.
I created a Swing Timer to animate the motion of the circle.
I used polar geometry to calculate the direction that the circle moves. The polar geometry is in the MoveListener class, the actionPerformed method. I use an arc-tangent to calculate the angle to the mouse position in radians, then take the cosine to get the X movement, and the sine to get the Y movement.
Here's the complete runnable code.
import java.awt.Color;
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.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class MouseMoveExample implements Runnable {
public static void main(String[] args) {
EventQueue.invokeLater(new MouseMoveExample());
}
private final int size = 500;
private Circle circle;
private DrawingPanel drawingPanel;
public MouseMoveExample() {
this.circle = new Circle(new Point(size / 2, size / 2), 12,
Color.BLACK);
}
#Override
public void run() {
JFrame f = new JFrame("Mouse Move Example");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.drawingPanel = new DrawingPanel();
f.add(drawingPanel);
f.pack();
f.setLocationByPlatform(true);
f.setVisible(true);
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingPanel() {
this.setBackground(Color.WHITE);
this.setPreferredSize(new Dimension(size, size));
this.addMouseMotionListener(new MoveListener());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(circle.getColor());
Point centerPoint = circle.getCenterPoint();
int radius = circle.getRadius();
int diameter = radius + radius;
g2d.fillOval(centerPoint.x - radius, centerPoint.y - radius,
diameter, diameter);
}
}
public class MoveListener extends MouseAdapter {
private MoveTimer moveTimer;
private Timer timer;
public MoveListener() {
this.moveTimer = new MoveTimer();
this.timer = new Timer(20, moveTimer);
this.timer.start();
}
#Override
public void mouseMoved(MouseEvent event) {
moveTimer.setEventPoint(event.getPoint());
}
}
public class MoveTimer implements ActionListener {
private Point eventPoint;
public void setEventPoint(Point eventPoint) {
this.eventPoint = eventPoint;
}
#Override
public void actionPerformed(ActionEvent event) {
Point centerPoint = circle.getCenterPoint();
if (eventPoint != null) {
double theta = Math.atan2(eventPoint.y - centerPoint.y,
eventPoint.x - centerPoint.x);
int increment = 1;
double x = Math.cos(theta) * increment;
double y = Math.sin(theta) * increment;
circle.incrementCenterPoint(x, y);
drawingPanel.repaint();
}
}
}
public class Circle {
private final int radius;
private final Color color;
private Point2D centerPoint;
public Circle(Point centerPoint, int radius, Color color) {
setCenterPoint(centerPoint);
this.radius = radius;
this.color = color;
}
public Point getCenterPoint() {
return new Point((int) Math.round(centerPoint.getX()),
(int) Math.round(centerPoint.getY()));
}
public void setCenterPoint(Point centerPoint) {
this.centerPoint = new Point2D.Double(
centerPoint.x, centerPoint.y);
}
public void incrementCenterPoint(double x, double y) {
centerPoint.setLocation(centerPoint.getX() + x,
centerPoint.getY() + y);
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
}
}