I need to simulate a running fan with 3 buttons(start, reverse,stop) and a scroll bar to control the speed.
I wrote a code but and their are no errors but its not working.
At first the class extended Jframe and the window that has the buttons and the arcs of the fan appeared but when it extended Japplet it didn't appear.
But it didn't work both ways.
package Ass3_10203038;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Graphics;
import javax.swing.JButton;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Adjustable;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Ass3_10203038 extends JApplet implements Runnable {
private static
Lock lock = new ReentrantLock();
Graphics fan;
JButton start = new JButton("Start");
JButton stop = new JButton("Stop");
JButton reverse = new JButton("Reverse");
JScrollBar speed = new JScrollBar();
Thread timer = new Thread();
int thr=50;
int strtpt=0;
JFrame frame= new JFrame();
#Override
public void run() {
repaint();
while (true) {
try {
Thread.sleep(thr);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Ass3_10203038() {
final ArcsPanel arcs = new ArcsPanel();
JPanel p = new JPanel();
p.setSize(500, 500);
p.add(arcs);
// p.setSize(5000, 5000);
p.add(start);
p.add(stop);
p.add(reverse);
p.add(speed);
p.setLayout(new GridLayout());
frame.add(p);
add(frame);
frame.setTitle("Fan");
start.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
timer.start();
for(int x=strtpt;x<362;x++)
{if(x==361)
x=0;
else
arcs.initializex(x);
arcs.paintComponent(fan);
strtpt=x;
}
}
});
stop.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
lock.lock();
timer.start();
}
});
reverse.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
timer.start();
for(int x=strtpt;x>-1;x--)
{if(x==0)
x=360;
else
arcs.initializex(x);
arcs.paintComponent(fan);
strtpt=x;
}
}});
speed.addAdjustmentListener(new AdjustmentListener(){
public void adjustmentValueChanged(AdjustmentEvent ae){
try {
switch (thr){
case AdjustmentEvent.UNIT_INCREMENT:
Thread.sleep( thr+=2);
break;
case AdjustmentEvent.UNIT_DECREMENT:
Thread.sleep(thr-=2);
break;
}
int value = ae.getValue();
} catch (InterruptedException ex) {
Logger.getLogger(Ass3_10203038.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
/**
* Main method
*/
public static void main(String[] args) {
Ass3_10203038 window = new Ass3_10203038();
window.setSize(500, 500);
window.setLocation(50, 50); // Center the frame
// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
// The class for drawing arcs on a panel
class ArcsPanel extends JPanel {
// Draw four blades of a fan
public int initializex(int x){
return x;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int xCenter = getWidth() / 2;
int yCenter = getHeight() / 2;
int radius = (int) (Math.min(getWidth(), getHeight()) * 0.4);
int x = xCenter - radius;
int y = yCenter - radius;
{
g.fillArc(x, y, 2 * radius, 2 * radius, 0+initializex(x), 30);
g.fillArc(x, y, 2 * radius, 2 * radius, 90+initializex(x), 30);
g.fillArc(x, y, 2 * radius, 2 * radius, 180+initializex(x), 30);
g.fillArc(x, y, 2 * radius, 2 * radius, 270+initializex(x), 30);
}
}
}
There are quite a few problems with this code, and I won't try to address all of them. However, I'll list the ones I notice and describe what is going wrong.
First off, your code crashes with an exception:
java.lang.IllegalArgumentException: adding a window to a container
This happens in the Ass3_10203038 constructor, where you try to add a JFrame to the new Ass3_10203038 instance (which is a JApplet, and thereby a container). Since the JFrame only holds a JPanel, you can get around this by directly adding the JPanel to the Ass3_10203038 instance. This will at least display your UI when running as Applet.
Next, clicking a button will generate a NullPointerException. This happens because you call paintComponent directly, passing in fan as the Graphics parameter - but you never initialize fan to anything in your code, so it is always null. As a solution, you should not call paintComponent directly, because you don't have a Graphics object that can be used to draw on the screen. The Swing system has this object, though, so we can ask it to repaint instead of calling paintComponent directly:
arcs.repaint();
Now the program goes into an infinite loop on the press of a button though. What happens is that this loop never ends:
for (int x = strtpt; x < 362; x++)
{
if (x == 361)
x = 0;
else
arcs.initializex(x);
arcs.repaint();
strtpt = x;
}
It always keeps looping because x never reaches 362. Given that you want the fan to spin continuously that makes a kind of sense, but since you are running this loop on the UI thread, it will freeze up the entire window. Apart from that, the fan would rotate really quickly, since there is no timing in this loop - it would go as fast as the computer can make it go, which is pretty fast indeed.
Apparently you tried to solve this problem by involving a Thread called timer:
timer.start();
However, since timer is just a blank Thread object this line does absolutely nothing (at least, nothing of any importance to your program). In order to run code in a thread, you have to either extend Thread and override the run method, or pass a Runnable to the constructor of Thread. However, I don't think raw Threads will get us anywhere here, since they still don't really solve the timing problem. I suggest you look into javax.swing.Timer.
Even with this out of the way though, your fan blades still don't move. This is because your paintComponent method never actually sees any angle as input. It looks like you try to pass in an angle by calling initializex(x) in the loop shown above and in the paintComponent method, but initializex does not do anything except return its parameter - whatever you pass in, you get back out immediately. So in the paintComponent method, initializex(x) simply returns the value of x. This means that instead of you code:
g.fillArc(x, y, 2 * radius, 2 * radius, 0 + initializex(x), 30);
You could just as well have written
g.fillArc(x, y, 2 * radius, 2 * radius, 0 + x, 30);
with exactly the same effect.
I hope this explains a few of the things that went wrong.
I fixed the code, but thank you so much
i Just needed to organize the logic in my head
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package assign3_10203038;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.image.ImageObserver;
import java.text.AttributedCharacterIterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
public class Assign3_10203038 extends JFrame {
private JButton start = new JButton("Start");
private JButton stop = new JButton("Stop");
private JButton reverse = new JButton("Reverse");
private JScrollBar speed = new JScrollBar();
private Thread timer;
private final ArcsPanel arcs;
public Assign3_10203038() {
arcs = new ArcsPanel();
JPanel p = new JPanel();
p.setSize(5000, 5000);
p.add(arcs);
p.add(start);
p.add(stop);
p.add(reverse);
p.add(speed);
p.setLayout(new GridLayout());
add(p);
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
timer = new Thread(arcs);
timer.start();
}
});
speed.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent ae) {
System.out.println(ae.getValue());
arcs.speed(ae.getValue());
}
});
reverse.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
arcs.reverse();
}
});
stop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
timer.stop();
}
});
}
public static void main(String[] args) {
Assign3_10203038 window = new Assign3_10203038();
// (new Thread(window)).start();
window.setSize(500, 500);
window.setLocation(50, 50); // Center the frame
// window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
class ArcsPanel extends JPanel implements Runnable {
// Draw four blades of a fan
int thr = 1000;
int i = -20;
public void run() {
while (true) {
starting_x = (starting_x + i) % 360;
repaint();
try {
Thread.sleep(thr);
} catch (InterruptedException ex) {
Logger.getLogger(Assign3_10203038.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public void reverse() {
i = -i;
}
public void speed(int p) {
thr = 1000 - (p += p) * 5;
}
int starting_x;
public void initializex(int x_val) {
starting_x = x_val;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int xCenter = getWidth() / 2;
int yCenter = getHeight() / 2;
int radius = (int) (Math.min(getWidth(), getHeight()) * 0.4);
int x = xCenter - radius;
int y = yCenter - radius;
{
g.fillArc(x, y, 2 * radius, 2 * radius, 0 + starting_x, 30);
g.fillArc(x, y, 2 * radius, 2 * radius, 90 + starting_x, 30);
g.fillArc(x, y, 2 * radius, 2 * radius, 180 + starting_x, 30);
g.fillArc(x, y, 2 * radius, 2 * radius, 270 + starting_x, 30);
}
}
}
Related
class GraphicsExampleComponent extends JComponent
{
//#Override
public void paintComponent(Graphics g)
{
//make the first call to your recursive routine
drawSquare1(g, 0, 0, 80);
}
public void drawSquare1(Graphics g, int x, int y, int size)
{
//draw a rectangle
g.drawRect(x, y, size, size);
g.fillRect(x, y, size, size);
//reset the parameters
x = x + size + 10;
y = y + (size/4);
size = size/4;
//determine if you should call it again.
if (size<4 || x>600)
drawSquare1(g, x, y, size);
}
My assignment is to create disappearing squares that get 25% smaller as they move to the right. When I run the code, it just creates the one square and stops. Can anyone help me understand what I am doing wrong.
disappearing squares that get 25% smaller as they move to the right.
Ok, let's step back for a second and break this down a bit.
You need to know...
The amount of space to be covered
The amount of space already covered (by the square)
The original size of the square
The size of the square should be when it reaches the other side (25% smaller)
When you have all this, you can calculate the size of the square at any point along its journey.
To determine the amount of space, you can use the component's width, via getWidth().
To determine the space already covered, you could start by having a look at box's current x position
// Assuming that box is a instance of Rectangle
double progress = (double)box.x / (double)getWidth();
We could argue that we should look at the middle of the box, or the trailing edge, but both of those are easy to implement.
Next, we need know the range of change (from start to end size), we can then use that to calculate the delta to be applied to the box...
double range = startSize - endSize;
double value = (range * progress);
box.width = (int)startSize - (int)value;
box.height = (int)startSize - (int)value;
Soooo, this will provide with the means to determine the size of the component based on it's current location (horizontally) through the component.
Next, you need some way to update the box's position and update the UI.
One of the better solutions is to use a Swing Timer. This will allow you to perform a repeating action (with a specified delay between updates) which won't block the UI and will generate updates within the Event Dispatching Queue, which is important because Swing is not Thread safe.
Have a look at How to Use Swing Timers for more details.
And finally, all we need, is to update the component with current state, via it's paintComponent method ... easy :P
Runnable example
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
protected static double startSize = 50;
protected static double endSize = startSize * 0.25;
private Rectangle box;
private Timer timer;
public TestPane() {
box = new Rectangle(0, 100 - 25, 50, 50);
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (box.x + box.width >= getWidth()) {
box.x = getWidth() - box.width;
box.width = (int)endSize;
box.height = (int)endSize;
timer.stop();
repaint();
}
box.x += 1;
double progress = (double)box.x / (double)getWidth();
double range = startSize - endSize;
double value = (range * progress);
box.width = (int)startSize - (int)value;
box.height = (int)startSize - (int)value;
repaint();
}
});
}
#Override
public void addNotify() {
super.addNotify();
box.x = 0;
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
timer.stop();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.fill(box);
g2d.dispose();
}
}
}
The condition if (size < 4 || x>600) is never true because when drawSquare1 is invoked for the first time size=80 and x=0.
Changing it to say if (size > 4 && x<600) will paint 3 squares on the screen without any noticeable animation.
To animate it we'll need to add some delay between paintings and remove previously painted squares.
To do so we use a swing Timer. We use the timer to repeatedly invoke drawSquare1.
drawSquare1 should modify the parameters controlling the painting, and call repaint.
import java.awt.*;
import javax.swing.*;
public class Main {
Main() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
GraphicsExampleComponent board = new GraphicsExampleComponent();
frame.add(board);
frame.pack();
frame.setVisible(true);
board.animate();
}
public static void main(String[] args) {
new Main();
}
}
class GraphicsExampleComponent extends JComponent{
private final static int W = 200, H = 100, MIN_SIZE = 4, STEP = 10, DELAY = 2000;
private int x= 0, y = 0, size = 80;
private Timer timer;
void animate(){
timer = new Timer(DELAY, e->drawSquare());
timer.start();
}
public void drawSquare(){
//check stop criteria
if (size < MIN_SIZE || x >= getWidth()) {
timer.stop();
return;
}
//reset the parameters
x = x + size + STEP;
y = y + size/4;
size = size/4;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//draw a rectangle
g.fillRect(x, y, size, size);
g.dispose();;
}
#Override
public Dimension preferredSize() {
return new Dimension(W, H);
}
}
(Test in online here)
Thank you everyone for the help. I unfortunately could not use a timer due to us not learning about it in class and we are not allowed to use methods/codes that we haven't gone over. To fix this I had to fix my base case by making it size >= 4 && x < 600.
I want to have a circle which works like a second-hand of a clock but I have a text field instead of a clock handle.for example when the second-hand of the clock is 30 the text field is in the lowest place of the circle and its text is 30.But I cant divide the circle into 60 parts.please help me to edit my codes.
I have to mention that I'm using Timer class.
these are my codes:
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
ClockWiseRotation window = new ClockWiseRotation();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public ClockWiseRotation() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
second = Integer.toString(Integer.parseInt(handle.getText()) + 1);
for (int i = 33; i <= 60; i++) {
int angle = i * (360 / 60);
handle.setBounds((int)(202 + 100 * Math.cos(angle)),
(int)(119 + 100 * Math.sin(angle)), 23, 26);
}
handle.setText(second);
}
});
timer.start();
frame = new JFrame();
frame.setBounds(100, 100, 450, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
handle = new JTextField();
handle.setText("0");
handle.setBounds(202, 22, 23, 26);
frame.getContentPane().add(handle);
handle.setColumns(10);
JComboBox centerPoint = new JComboBox();
centerPoint.setBounds(202, 119, 17, 20);
frame.getContentPane().add(centerPoint);
}
}
Swing is a single threaded environment, blocking the EDT for any reason (like a long running loop of Thread.sleep) will prevent the UI from been updated. Swing is also not thread and updates to the UI should never be made from outside the context of the EDT
With that in mind, on every tick of the Timer, you looping 60 some 30 times trying to move the handle, but the only position the handle will appear in is the last position it's in when the actionPerformed method exits.
See Concurrency in Swing for more details.
What you should be doing is simply placing the handle at the current location based on your requirements/needs at the time of the call
I've also been looking at you calculation...
handle.setBounds((int)(202 + 100 * Math.cos(angle)),
(int)(119 + 100 * Math.sin(angle)), 23, 26);
From what I remember, to find the point on a circle, based on an angle, you need to use...
x = xOffset + Math.cos(radians) * radius;
y = yOffset - Math.sin(radians) * radius;
So, two things to point out there, but the most important is Math.cos and Math.sin expect the value to be in radians, not degrees
You should also avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify.
You should also avoid using components in this manner, instead, I'd consider using a custom painting processing....
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Calendar;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestClock {
public static void main(String[] args) {
new TestClock();
}
public TestClock() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new ClockPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected class ClockPane extends JPanel {
public ClockPane() {
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(false);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected Point getPointTo(float angle) {
int x = Math.round(getWidth() / 2);
int y = Math.round(getHeight() / 2);
double rads = Math.toRadians(angle);
// This is an arbitrary amount, you will need to correct for this
// I'm working of a width of 200 pixels, so that makes the radius
// 100...
int radius = 100;
// Calculate the outter point of the line
int xPosy = Math.round((float) (x + Math.cos(rads) * radius));
int yPosy = Math.round((float) (y - Math.sin(rads) * radius));
return new Point(xPosy, yPosy);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
Calendar cal = Calendar.getInstance();
int seconds = cal.get(Calendar.SECOND);
float angle = -(360f * (seconds / 60f));
angle += 90; // Correct for 0 being out to the right instead of up
Point p = getPointTo(angle);
int x = getWidth() / 2;
int y = getHeight() / 2;
g2d.drawLine(x, y, p.x, p.y);
FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(seconds);
g2d.drawString(text, getWidth() - fm.stringWidth(text), getHeight() - fm.getHeight() + fm.getAscent());
g2d.dispose();
}
}
}
Have a look at Painting in AWT and Swing, Performing Custom Painting and 2D Graphics for more details
Thanks your guidance was really helpful and i have solved it like this(by removing the for loop):
public void actionPerformed(ActionEvent arg0) {
int intSecond = Integer.parseInt(handle.getText()) + 1;
if (intSecond <= 60) {
second = Integer.toString(intSecond);
handle.setBounds(
(int) (Math.cos(intSecond * Math.PI / 30 - Math.PI
/ 2) * 100 + 202),
(int) (Math.sin(intSecond * Math.PI / 30 - Math.PI
/ 2) * 100 + 119), 17, 20);
handle.setText(second);
} else {
timer.stop();
}
}
});
I'm trying to create a simple panel where a 2-dimensional ball is bouncing up and down. I can't get it to work because for some reason I can't call the repaint method more than once a second. The design is basically that there is an object that can be given "an impulse" with the method move(). Everytime the evaluatePosition method is called, the current position will be calculated through the time that has passed, the velocity and the acceleration. The code for the panel is:
public class Display extends JPanel {
private MovableObject object = new MovableObject(new Ellipse2D.Double(5,5,50,50));
private static final int DELAY = 1000;
public Display(){
object.move(50,50);
Timer timer = new Timer(DELAY, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
object.evaluatePosition();
repaint();
}
});
timer.start();
}
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawOval((int)object.getPosition().getX(), (int)object.getPosition.getY()
(int)object.getShape().getWidth(), object.getShape().getHeight());
}
This code works for DELAY=1000 but not for DELAY=100 or DELAY=10 and so on. I read some example code here on SO but they all seem to me like what I already did. So why is my code not working?
EDIT (2016-01-30):
Since it really seems to be a performance issue, here's the code for the MovableObject (I just thought it would be irrelevant and you will probably see why):
public class MovableObject {
// I would really like to use Shape instead of Ellipse2D so that
// objects of any shape can be created
private Ellipse2D.Double shape;
private Point position;
// Vector is my own class. I want to have some easy vector addition and
// multiplication and magnitude methods
private Vector velocity = new Vector(0, 0);
private Vector acceleration = new Vector(0, 0);
private Date lastEvaluation = new Date();
public MovableObject(Ellipse2D.Double objectShape){
shape = objectShape;
}
public void evaluatePosition(){
Date currentTime = new Date();
long deltaTInS = (currentTime.getTime()-lastEvaluation.getTime())/1000;
// s = s_0 + v*t + 0.5*a*t^2
position = new Point((int)position.getX()+ (int)(velocity.getX()*deltaTInS) + (int)(0.5*acceleration.getX()*deltaTInS*deltaTInS),
(int)position.getY()+ (int)(velocity.getY()*deltaTInS) + (int)(0.5*acceleration.getY()*deltaTInS*deltaTInS));
lastEvaluation = currentTime;
}
}
public void move(Vector vector){
velocity = velocity.add(vector);
evaluatePosition();
}
public Point getPosition(){
return position;
}
public Ellipse2D.Double getShape(){
return shape;
}
My move method does not change position but velocity. Please notice that I just changed the shape Object from Shape to Ellipse2D for testing if my code has a performance issue because of the additional code. So if you think this is more complex than it needs to be: I actually want to add some complexity so that the MovableObject can have the shape of any subclass of shape. I've seen a lot of code that seemed more complex to me and rendered fast. So I'd like to know what's wrong with this (besides the fact that it's a bit too complex for just rendering an ellipse).
Okay, so this is a simple example, based on the out-of-context code snippet you left which doesn't seem to have any problems. It has variable speed controlled by a simple slider...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
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.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Display extends JPanel {
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) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Display());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private MovableObject object = new MovableObject(new Ellipse2D.Double(5, 5, 50, 50));
private int delay = 40;
private Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
object.evaluatePosition(getSize());
repaint();
}
});
private JSlider slider = new JSlider(5, 1000);
public Display() {
object.move(50, 50);
slider = new JSlider(5, 1000);
slider.setSnapToTicks(true);
slider.setMajorTickSpacing(10);
slider.setMinorTickSpacing(5);
setLayout(new BorderLayout());
add(slider, BorderLayout.SOUTH);
// This is simply designed to put an artificate delay between the
// change listener and the time the update takes place, the intention
// is to stop it from pausing the "main" timer...
Timer delay = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timer != null) {
timer.stop();
}
timer.setDelay(slider.getValue());
timer.start();
}
});
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
delay.restart();
repaint();
}
});
slider.setValue(40);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.draw(object.getTranslatedShape());
FontMetrics fm = g2.getFontMetrics();
String text = Integer.toString(slider.getValue());
g2.drawString(text, 0, fm.getAscent());
g2.dispose();
}
public class MovableObject {
private Shape shape;
private Point location;
private int xDelta, yDelta;
public MovableObject(Shape shape) {
this.shape = shape;
location = shape.getBounds().getLocation();
Random rnd = new Random();
xDelta = rnd.nextInt(8);
yDelta = rnd.nextInt(8);
if (rnd.nextBoolean()) {
xDelta *= -1;
}
if (rnd.nextBoolean()) {
yDelta *= -1;
}
}
public void move(int x, int y) {
location.setLocation(x, y);
}
public void evaluatePosition(Dimension bounds) {
int x = location.x + xDelta;
int y = location.y + yDelta;
if (x < 0) {
x = 0;
xDelta *= -1;
} else if (x + shape.getBounds().width > bounds.width) {
x = bounds.width - shape.getBounds().width;
xDelta *= -1;
}
if (y < 0) {
y = 0;
yDelta *= -1;
} else if (y + shape.getBounds().height > bounds.height) {
y = bounds.height - shape.getBounds().height;
yDelta *= -1;
}
location.setLocation(x, y);
}
public Shape getTranslatedShape() {
PathIterator pi = shape.getPathIterator(AffineTransform.getTranslateInstance(location.x, location.y));
GeneralPath path = new GeneralPath();
path.append(pi, true);
return path;
}
}
}
You could also have a look at
Swing animation running extremely slow
Rotating multiple images causing flickering. Java Graphics2D
Java Bouncing Ball
for some more examples...
I need help with a simple animation assignment. It goes as follows.
I have two stop lights on a JPanel and the object is for the two of them to have different time intervals i.e there lights cycle at different times.
Everything works fine if I only have one light at a time. I am relatively new to this but I believe I know the problem.
In the code under this text, I use this several times. I believe my issue occurs in the public void cycle() method in which it just says this.repaint(); I have a feeling that the panel is being repainted at the two different time periods and it gives me a somewhat random light changing instead of a nice cycle.
Is there a way I can have these two components on the same JPanel with a more specific repaint method (maybe a bounding box around the individual light fixtures) or would creating separate panels be a better option (and a little help if that is the case because I understand the basic layouts but have never used them before).
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class DrawingPanel extends JPanel implements Lighter
{
// instance variables
private final int INTERVAL1 = 2000;
private final int INTERVAL2 = 5000;
private TrafficLight _light1, _light2;
private LightTimer _timer1,_timer2;
/**
* Constructor for objects of class DrawingPanel
*/
public DrawingPanel()
{
// initialise instance variables
super();
this.setBackground(Color.CYAN);
_light1 = new TrafficLight(50,50);
_light2 = new TrafficLight(200,50);
_timer1 = new LightTimer(INTERVAL1,this);
_timer2 = new LightTimer(INTERVAL2,this);
_timer1.start();
_timer2.start();
}
public void cycle(){
_light1.cycle();
_light2.cycle();
this.repaint();
}
public void paintComponent(Graphics pen)
{
super.paintComponent(pen);
Graphics2D aBetterPen = (Graphics2D)pen;
_light1.fill(aBetterPen);
_light2.fill(aBetterPen);
}
}
Running two timers in the fashion is achievable. Personally, I would write a "signal" class that controls a single light with it's own timing and painting routines, but that's not what you've asked.
What you need to do is maintain some kind of state variable for each signal and update them separately.
You would then need to modify the paint code to detect these states and take appropriate actions...for example
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestBlink {
public static void main(String[] args) {
new TestBlink();
}
public TestBlink() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Timer blink1;
private Timer blink2;
private boolean light1 = false;
private boolean light2 = false;
public TestPane() {
blink1 = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
light1 = !light1;
repaint();
}
});
blink2 = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
light2 = !light2;
repaint();
}
});
blink1.start();
blink2.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int radius = 20;
int x = (getWidth() - (radius * 2)) / 2;
int y = (getHeight() - (radius * 2)) / 2;
Ellipse2D signal1 = new Ellipse2D.Float(x, y, radius, radius);
Ellipse2D signal2 = new Ellipse2D.Float(x + radius, y, radius, radius);
g2d.setColor(Color.RED);
g2d.draw(signal1);
if (light1) {
g2d.fill(signal1);
}
g2d.setColor(Color.GREEN);
g2d.draw(signal2);
if (light2) {
g2d.fill(signal2);
}
g2d.dispose();
}
}
}
Updated
A better solution (as I stated earlier) would be to contain all the logic for a single sequence in a single class. This isolates the painting and allows you to change the individual nature each sequence.
For example...
This is a simple example which uses a fixed rate of change, so each light gets the same about of time...
public class TraficLight01 extends JPanel {
public static final int RADIUS = 20;
private Timer timer;
private int state = 0;
public TraficLight01() {
timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
state++;
if (state > 2) {
state = 0;
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(RADIUS, (RADIUS + 1) * 3);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int radius = 20;
Ellipse2D light = new Ellipse2D.Float(0, 0, RADIUS, RADIUS);
int x = (getWidth() - radius) / 2;
int y = ((getHeight()- (radius * 3)) / 2) + (radius * 2);
Color color[] = new Color[]{Color.RED, Color.YELLOW, Color.GREEN};
for (int index = 0; index < color.length; index++) {
g2d.translate(x, y);
g2d.setColor(color[index]);
g2d.draw(light);
if (state == index) {
g2d.fill(light);
}
g2d.translate(-x, -y);
y -= radius + 1;
}
g2d.dispose();
}
}
Or you provide a variable interval for each light...
public static class TraficLight02 extends JPanel {
public static final int RADIUS = 20;
private Timer timer;
private int state = 0;
// Green, Yellow, Red
private int[] intervals = new int[]{3000, 500, 3000};
public TraficLight02() {
timer = new Timer(intervals[0], new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
timer.stop();
state++;
if (state > 2) {
state = 0;
}
timer.setInitialDelay(intervals[state]);
repaint();
timer.restart();
}
});
timer.start();
timer.setRepeats(false);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(RADIUS, (RADIUS + 1) * 3);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int radius = 20;
Ellipse2D light = new Ellipse2D.Float(0, 0, RADIUS, RADIUS);
int x = (getWidth() - radius) / 2;
int y = ((getHeight()- (radius * 3)) / 2) + (radius * 2);
Color color[] = new Color[]{Color.GREEN, Color.YELLOW, Color.RED};
for (int index = 0; index < color.length; index++) {
g2d.translate(x, y);
g2d.setColor(color[index]);
g2d.draw(light);
if (state == index) {
g2d.fill(light);
}
g2d.translate(-x, -y);
y -= radius + 1;
}
g2d.dispose();
}
}
It wouldn't take much to change these two concepts to seed them with variable intervals via a setter method.
Equally, you could use three Timers, each time one fires, you would simply start the next one. This way you could define a chain of timers, each one firing at different intervals after the completion of it's parent...
As a Note, your comment
it gives me a somewhat random light changing instead of a nice cycle.
What are you expecting it to look like?
With the Time intervals you have set it may appear somewhat random but it is actually working ie your intervals would work like this (well my assumptions of what the Interval variable are for)
Time(s) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Light1 ON OFF ON OFF ON OFF ON OFF
Light2 ON OFF ON
I have JToggleButton and this method capturing events:
tgl_playMouseClicked(java.awt.event.MouseEvent evt) {
new Thread() {
public void run() {
int i = 0;
String outputName = null;
while ((i <= 99)) {
ImageIcon imgThisImg = new ImageIcon("images/" + outputName + i + ".png");
lbl_image.setIcon(imgThisImg);
i++;
}
tgl_play.setSelected(!tgl_play.isSelected());
}
}.start();
}
I try to simulate video by reading and showing single images in imageIcon.
When I fist time click on JToggleButton, all is ok. Video is running. But when I press again, nothing happens. The event is captured as prints are displayed in output, but no refresh on ImageIcon.
I use thread there in order to be able to set some delay between frames.
What's wrong? Help me please
I think that one of your best shot is to use javax.swing.Timer to pace your "video". This will ensure that you are doing everything properly with Swing EDT.
(If the millisecond is not sufficient, then I would take a look at: java.util.concurrent.Executors.newScheduledThreadPool(int) and java.util.concurrent.ScheduledThreadPoolExecutor.scheduleAtFixedRate(Runnable, long, long, TimeUnit) and add Runnable's that immediately call all their code in SwingUtilities.invokeLater())
Here I made a small demo example with a list of images displaying a growing and shrinking circle (the images are created on the fly with some JPanel but this is just for the demo).
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UnsupportedLookAndFeelException;
public class TestAnimation {
private static final int NB_OF_IMAGES = 50;
private static final int NB_OF_IMAGES_PER_SECOND = 25;
private static final int WIDTH = 300;
private static final int HEIGHT = 300;
protected void initUI() {
final JFrame frame = new JFrame(TestAnimation.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
// the label on which I will set images
final JLabel label = new JLabel();
// By adding to the frame, it is set as the central component of the
// BorderLayout of the JFrame. Eventually, the label will have the size of the content pane
frame.add(label);
frame.setSize(WIDTH, HEIGHT);
// Creating a list of images (just for demo purposes)
final List<Image> images = new ArrayList<Image>(NB_OF_IMAGES);
for (int i = 0; i < NB_OF_IMAGES; i++) {
CirclePanel circle = new CirclePanel(WIDTH / 2, WIDTH / 2, 2 * WIDTH * (NB_OF_IMAGES / 2 - Math.abs(i - NB_OF_IMAGES / 2))
/ NB_OF_IMAGES);
circle.setSize(WIDTH, HEIGHT);
BufferedImage image = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()
.createCompatibleImage(WIDTH, HEIGHT, BufferedImage.TRANSLUCENT);
circle.print(image.getGraphics());
images.add(image);
}
// Here is the timer logic
Timer t = new Timer(1000 / NB_OF_IMAGES_PER_SECOND, new ActionListener() {
private int i = 0;
#Override
public void actionPerformed(ActionEvent e) {
if (i == images.size()) {
i = 0;
}
label.setIcon(new ImageIcon(images.get(i++)));
}
});
frame.setVisible(true);
t.start();
}
// Simple class that draws a red circle centered on x,y and given radius
public static class CirclePanel extends JPanel {
private int x;
private int y;
private int radius;
public CirclePanel(int x, int y, int radius) {
super();
this.x = x;
this.y = y;
this.radius = radius;
setOpaque(false);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
g.drawArc(x - radius / 2, y - radius / 2, radius, radius, 0, 360);
}
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
UnsupportedLookAndFeelException {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestAnimation().initUI();
}
});
}
}