Move image in a spiral fashion in java - java

I am trying to make a simple animated intro. I have an image I am trying to move from the bottom left of the screen to the center of the screen in a clockwise spiral motion. This is the code that I am using for now. It just moves the image upward to the center:
static ImageLoader il = new ImageLoader();
private static BufferedImage logo = il.load("/logoNew.png");
private static Image power = il.gif("http://i.stack.imgur.com/KSnus.gif");
static double y = 1024.0;
public static void render(Graphics g){
if(y>(486/2)-128){
y = y-0.25;
}
if(draw){
g.drawImage(logo,(864/2)-128,(int)y,null);
g.setColor(Color.WHITE);
g.drawImage(power,10,10,null);
}
}
The if(draw) statement is activated by something else.
How do I go about moving the image. Do I just increment the x and the y differently at different points?
** EDIT **
I didn't make it clear on the motion. Its going from the bottom left to the top left to the top right to the bottom right to the bottom center (centre) to the center (centre) of the screen

Animation is the illusion of movement over time. Normally I would use something like the Timing Framework (or Trident or Universal Tween Engine) as the base of the animation, these provide better support for things like ease-in and ease-out.
The following example just makes uses of a simple javax.swing.Timer. I use this because it's safer to use with Swing, as it allows me to update the state of the UI from within the context of the Event Dispatching Thread, but doesn't block it (preventing it from updating the screen).
The following example uses a concept of a timeline and key frames. That is, at some point in time, something must happen. The timeline then provides the means for blending between those "key" points in time.
I, personally, like to work in abstract concepts, so the timeline is simply measured in a percentage from 0-1, which allows me to provide a variable time span. This allows me to adjust the speed of the animation without the need to change anything.
As you (should) be able to see, the last two legs only need to move half the distance, so they are slower than the other three legs, so, technically, they only need half the time to complete...but I'll leave it up to you to nut out the maths for that ;)
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.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test{
public static void main(String[] args) {
new Test();
}
public Test() {
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 class TestPane extends JPanel {
protected static final int PLAY_TIME = 6000;
private Timeline timeline;
private long startTime;
private Point imgPoint;
private BufferedImage img;
public TestPane() {
try {
img = ImageIO.read(new File("C:/Neko.png"));
imgPoint = new Point(0, 200 - img.getHeight());
timeline = new Timeline();
timeline.add(0f, imgPoint);
timeline.add(0.2f, new Point(0, 0));
timeline.add(0.4f, new Point(200 - img.getWidth(), 0));
timeline.add(0.6f, new Point(200 - img.getWidth(), 200 - img.getHeight()));
timeline.add(0.8f, new Point(100 - (img.getWidth() / 2), 200 - img.getHeight()));
timeline.add(1f, new Point(100 - (img.getWidth() / 2), 100 - (img.getHeight() / 2)));
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long duration = System.currentTimeMillis() - startTime;
float progress = (float) duration / (float) PLAY_TIME;
if (progress > 1f) {
startTime = System.currentTimeMillis();
progress = 0;
((Timer) (e.getSource())).stop();
}
System.out.println(progress);
imgPoint = timeline.getPointAt(progress);
repaint();
}
});
startTime = System.currentTimeMillis();
timer.start();
} catch (IOException exp) {
exp.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null && imgPoint != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(img, imgPoint.x, imgPoint.y, this);
g2d.dispose();
}
}
}
public static class Timeline {
private Map<Float, KeyFrame> mapEvents;
public Timeline() {
mapEvents = new TreeMap<>();
}
public void add(float progress, Point p) {
mapEvents.put(progress, new KeyFrame(progress, p));
}
public Point getPointAt(float progress) {
if (progress < 0) {
progress = 0;
} else if (progress > 1) {
progress = 1;
}
KeyFrame[] keyFrames = getKeyFramesBetween(progress);
float max = keyFrames[1].progress - keyFrames[0].progress;
float value = progress - keyFrames[0].progress;
float weight = value / max;
return blend(keyFrames[0].getPoint(), keyFrames[1].getPoint(), 1f - weight);
}
public KeyFrame[] getKeyFramesBetween(float progress) {
KeyFrame[] frames = new KeyFrame[2];
int startAt = 0;
Float[] keyFrames = mapEvents.keySet().toArray(new Float[mapEvents.size()]);
while (startAt < keyFrames.length && keyFrames[startAt] <= progress) {
startAt++;
}
if (startAt >= keyFrames.length) {
startAt = keyFrames.length - 1;
}
frames[0] = mapEvents.get(keyFrames[startAt - 1]);
frames[1] = mapEvents.get(keyFrames[startAt]);
return frames;
}
protected Point blend(Point start, Point end, float ratio) {
Point blend = new Point();
float ir = (float) 1.0 - ratio;
blend.x = (int) (start.x * ratio + end.x * ir);
blend.y = (int) (start.y * ratio + end.y * ir);
return blend;
}
public class KeyFrame {
private float progress;
private Point point;
public KeyFrame(float progress, Point point) {
this.progress = progress;
this.point = point;
}
public float getProgress() {
return progress;
}
public Point getPoint() {
return point;
}
}
}
}

Related

How do I make a top down view with the ability to rotate with built in java graphics?

I'm trying to make a racing game with the top down view on a static player in the middle of the screen, so instead of moving the player through the map, the map would move around the player. Since it's a racing game, I wanted it to also be somewhat similar to a car, but I've been having trouble with rotating the map around the player and having that work with translations.
I've tried keeping track of the center by adding or subtracting from it, which is what I did for the translations, but it doesn't work with the rotate method. The rotate function wouldn't rotate about the player and instead would rotate the player around some other point, and the translations would snap to a different location from the rotations. I'm sure my approach is flawed, and I have read about layers and such, but I'm not sure what I can do with them or how to use them. Also, any recommendations as to how to use java graphics in general would be greatly appreciated!
This is what I have in my main:
import javax.swing.JFrame;
import java.awt.BorderLayout;
public class game
{
public static void main(String []args)
{
JFrame frame = new JFrame();
final int FRAME_WIDTH = 1000;
final int FRAME_HEIGHT = 600;
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final Map b = new Map();
frame.add(b,BorderLayout.CENTER);
frame.setVisible(true);
b.startAnimation();
}
}
And this is the class that handles all the graphics
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Map extends JComponent implements Runnable, KeyListener
{
private int speed = 5;
private int xcenter = 500; // starts on player
private int ycenter = 300;
private double angle = 0.0;
private int[] xcords = {xcenter+10, xcenter, xcenter+20};
private int[] ycords = {ycenter-10, ycenter+20, ycenter+20};
private boolean moveNorth = false;
private boolean moveEast = false;
private boolean moveSouth = false;
private boolean moveWest = false;
public Map()
{
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
public void startAnimation()
{
Thread t = new Thread(this);
t.start();
}
public void paintComponent(Graphics g)
{
g.fillPolygon(xcords, ycords, 3);
// move screen
if(moveNorth)
{
ycenter += speed;
g.translate(xcenter, ycenter);
}
else if(moveEast)
{
angle += ((1 * Math.PI/180) % (2 * Math.PI));
((Graphics2D) g).rotate(angle, 0, 0);
}
else if(moveSouth)
{
System.out.println(xcenter + ", " + ycenter);
ycenter -= speed;
((Graphics2D) g).rotate(angle, 0, 0);
g.translate(xcenter, ycenter);
}
else if(moveWest)
{
angle -= Math.toRadians(1) % (2 * Math.PI);
((Graphics2D) g).rotate(angle, 0, 0);
}
for(int i = -10; i < 21; i++)
{
g.drawLine(i * 50, -1000, i * 50, 1000);
g.drawLine(-1000, i * 50, 1000, i * 50);
}
g.drawOval(0, 0, 35, 35);
}
public void run()
{
while (true)
{
try
{
if(moveNorth || moveEast || moveSouth || moveWest)
{
repaint();
}
Thread.sleep(10);
}
catch (InterruptedException e)
{
}
}
}
public void keyPressed(KeyEvent e)
{
if(e.getExtendedKeyCode() == 68) // d
{
moveEast = true;
}
else if(e.getExtendedKeyCode() == 87) // w
{
moveNorth = true;
}
else if(e.getExtendedKeyCode() == 65) // a
{
moveWest = true;
}
else if(e.getExtendedKeyCode() == 83) // s
{
moveSouth = true;
}
}
public void keyReleased(KeyEvent e)
{
moveNorth = false;
moveEast = false;
moveSouth = false;
moveWest = false;
}
public void keyTyped(KeyEvent e)
{
}
}
You have to keep in mind that transformations are compounding, so if you rotate the Graphics context by 45 degrees, everything painted after it will be rotated 45 degrees (around the point of rotation), if you rotate it again by 45 degrees, everything painted after it will be rotated a total of 90 degrees.
If you want to paint additional content after a transformation, then you either need to undo the transformation, or, preferably, take a snapshot of the Graphics context and dispose of it (the snapshot) when you're done.
You also need to beware of the point of rotation, Graphics2D#rotate(double) will rotate the Graphics around the point of origin (ie 0x0), which may not be desirable. You can change this by either changing the origin point (ie translate) or using Graphics2D#rotate(double, double, double), which allows you to define the point of rotation.
For example...
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.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
enum Direction {
LEFT, RIGHT;
}
protected enum InputAction {
PRESSED_LEFT, PRESSED_RIGHT, RELEASED_LEFT, RELEASED_RIGHT
}
private BufferedImage car;
private BufferedImage road;
private Set<Direction> directions = new TreeSet<>();
private double directionOfRotation = 0;
public TestPane() throws IOException {
car = ImageIO.read(getClass().getResource("/images/Car.png"));
road = ImageIO.read(getClass().getResource("/images/Road.png"));
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), InputAction.PRESSED_LEFT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), InputAction.RELEASED_LEFT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), InputAction.PRESSED_RIGHT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), InputAction.RELEASED_RIGHT);
am.put(InputAction.PRESSED_LEFT, new DirectionAction(Direction.LEFT, true));
am.put(InputAction.RELEASED_LEFT, new DirectionAction(Direction.LEFT, false));
am.put(InputAction.PRESSED_RIGHT, new DirectionAction(Direction.RIGHT, true));
am.put(InputAction.RELEASED_RIGHT, new DirectionAction(Direction.RIGHT, false));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (directions.contains(Direction.RIGHT)) {
directionOfRotation += 1;
} else if (directions.contains(Direction.LEFT)) {
directionOfRotation -= 1;
}
// No doughnuts for you :P
if (directionOfRotation > 180) {
directionOfRotation = 180;
} else if (directionOfRotation < -180) {
directionOfRotation = -180;
}
repaint();
}
});
timer.start();
}
protected void setDirectionActive(Direction direction, boolean active) {
if (active) {
directions.add(direction);
} else {
directions.remove(direction);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(213, 216);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
drawRoadSurface(g2d);
drawCar(g2d);
g2d.dispose();
}
protected void drawCar(Graphics2D g2d) {
g2d = (Graphics2D) g2d.create();
int x = (getWidth() - car.getWidth()) / 2;
int y = (getHeight() - car.getHeight()) / 2;
g2d.drawImage(car, x, y, this);
g2d.dispose();
}
protected void drawRoadSurface(Graphics2D g2d) {
g2d = (Graphics2D) g2d.create();
// This sets the point of rotation at the center of the window
int midX = getWidth() / 2;
int midY = getHeight() / 2;
g2d.rotate(Math.toRadians(directionOfRotation), midX, midY);
// We then need to offset the top/left corner so that what
// we want draw appears to be in the center of the window,
// and thus will be rotated around it's center
int x = midX - (road.getWidth() / 2);
int y = midY - (road.getHeight() / 2);
g2d.drawImage(road, x, y, this);
g2d.dispose();
}
protected class DirectionAction extends AbstractAction {
private Direction direction;
private boolean active;
public DirectionAction(Direction direction, boolean active) {
this.direction = direction;
this.active = active;
}
#Override
public void actionPerformed(ActionEvent e) {
setDirectionActive(direction, active);
}
}
}
}

The repaint method stops working for short delays

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...

How do you create multiple animated components with timer/actionevents with two different time intervals on the same panel?

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

Simple way of creating an animated JScrollPane in Java?

I currently have a JScrollPane that holds what is basically a list of items. This JScrollPane is to be displayed on an information screen.
What I'm looking for is some way of making it automatically scroll to the bottom of the list at a given interval, then back to the top. I recognise this is probably not achievable using a JScrollPane, so any suggestions for alternatives would also be great!
Normally I would use the TimingFramework or you could use something like Trident or the Unviversal Tween Engine as a bases for any animation. The main reason is, apart from doing most of the work for you, they also provide variable speed, which will make the animation look more natural.
But you can achieve the basic concept using a javax.swing.Timer.
This example will allow you to scroll to the bottom of an image and back again.
The animation will take 5 seconds (as supplied by the runningTiming variable), allowing it to be variable (the larger the image, the faster the movement, the smaller the image, the slower).
import java.awt.BorderLayout;
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.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class AutoScroller {
public static void main(String[] args) {
new AutoScroller();
}
private long startTime = -1;
private int range = 0;
private int runningTime = 5000;
private int direction = 1;
public AutoScroller() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
final JScrollPane scrollPane = new JScrollPane(new TestPane());
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(scrollPane);
// frame.pack();
frame.setSize(scrollPane.getPreferredSize().width, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startTime < 0) {
startTime = System.currentTimeMillis();
range = scrollPane.getViewport().getView().getPreferredSize().height - scrollPane.getHeight();
}
long duration = System.currentTimeMillis() - startTime;
float progress = 1f;
if (duration >= runningTime) {
startTime = -1;
direction *= -1;
// Make the progress equal the maximum range for the new direction
// This will prevent it from "bouncing"
if (direction < 0) {
progress = 1f;
} else {
progress = 0f;
}
} else {
progress = (float) duration / (float) runningTime;
if (direction < 0) {
progress = 1f - progress;
}
}
int yPos = (int) (range * progress);
scrollPane.getViewport().setViewPosition(new Point(0, yPos));
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
});
}
public class TestPane extends JPanel {
private BufferedImage image;
public TestPane() {
try {
image = ImageIO.read(new File("Path/to/your/image"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return image == null ? new Dimension(200, 200) : new Dimension(image.getWidth(), image.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (image != null) {
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - image.getWidth()) / 2;
int y = (getHeight() - image.getHeight()) / 2;
g2d.drawImage(image, x, y, this);
g2d.dispose();
}
}
}
}
Have you looked into using a timer to send scroll instructions to the JScrollPane at intervals? Just the first thing that comes to mind...

Float value for Swing timer?

Consider the following Swing timer :
timer = new Timer (ballSpeed, tc);
ballSpeed is initially at 10. tc is an Action Listener class that increments the x value of an object painted on the screen. The variable ballSpeed is kind of a misnomer because the lower the value, the faster the object moves.
Now I want the object's movement to look as smooth as possible. Therefore I will only increment the x value one by one in the ActionListener. That is the object should only move move pixel by pixel. I use x++ instead of x+=10. Therefore I will not modify the ball's speed this way.
Now since the first argument of Timer will only accept an integer, it doesn't give me a great deal of control over the object's speed. I can only use 10,9,8,etc. The object either moves too fast or too slow.
To summarize, millisecond precision is not sufficient.
Is there a way around this? Or is there an overall better way to implement object movement on the screen?
A javax.swing.Timer does not have sufficient accuracy or precision to generate the smooth graphics you are trying to achieve. Furthermore, the swing timer is affected by anything else in the swing event queue:
Second, its automatic thread sharing means that you don't have to take special steps to avoid spawning too many threads. Instead, your timer uses the same thread used to make cursors blink, tool tips appear, and so on.
If you don't want to use some media framework or use APIs that describe the motion of objects (rather than actually moving the objects), you should use the swing timer as a way to schedule the next computation, but determine the time elapsed since last computation by looking at the difference between System.nanoTime() now and the nanoTime during the last computation.
Using this approach, you will have more jaggered but more correct animation on underpowered machines.
OK, so if you really want to go for the nanosecond, at the end of this answer is a way to do it using a ScheduledThreadPool. But this is just pure madness, this may lead to tons of problems and the result is disappointing. I would really not go down that road.
With 50Hz (ie, 50 refresh per second) you should be able to achieve a decent result. The thing is that I would rather drop the assumption that you can move only pixel per pixel and link your ball speed to your move increases.
Here is an example (just drag the slider to see the result):
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.MalformedURLException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class TestAnimation2 {
private static final int NB_OF_IMAGES_PER_SECOND = 50;
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
private static final int MIN = 0;
private static final int MAX = 100;
private double speed = convert(50);
private double dx;
private double dy;
private double x = WIDTH / 2;
private double y = HEIGHT / 2;
private JFrame frame;
private CirclePanel circle;
private Runnable job;
private long lastMove = System.currentTimeMillis();
protected void initUI() throws MalformedURLException {
frame = new JFrame(TestAnimation2.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
circle = new CirclePanel();
circle.setSize(20, 20);
frame.add(circle);
frame.setSize(WIDTH, HEIGHT);
dx = speed;
dy = speed;
final JSlider slider = new JSlider(MIN, MAX);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
speed = convert(slider.getValue());
if (dx > 0) {
dx = speed;
} else {
dx = -speed;
}
if (dy > 0) {
dy = speed;
} else {
dy = -speed;
}
}
});
slider.setValue(50);
slider.setLocation(0, 0);
slider.setSize(slider.getPreferredSize());
frame.add(slider);
frame.setVisible(true);
Timer t = new Timer(1000 / NB_OF_IMAGES_PER_SECOND, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
move();
}
});
t.start();
}
protected double convert(double sliderValue) {
return sliderValue + 1;
}
protected void move() {
x += dx;
y += dy;
if (x + circle.getWidth() > frame.getContentPane().getWidth()) {
x = frame.getContentPane().getWidth() - circle.getWidth();
dx = -speed;
} else if (x < 0) {
x = 0;
dx = speed;
}
if (y + circle.getHeight() > frame.getContentPane().getHeight()) {
y = frame.getContentPane().getHeight() - circle.getHeight();
dy = -speed;
} else if (y < 0) {
y = 0;
dy = speed;
}
circle.setLocation((int) x, (int) y);
circle.repaint();
}
public static class CirclePanel extends JPanel {
public CirclePanel() {
super();
setOpaque(false);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
g.fillOval(0, 0, getWidth(), getHeight());
}
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
UnsupportedLookAndFeelException {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
new TestAnimation2().initUI();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
});
}
}
Here is an example using a scheduled thread pool (very dangerous and disappointing)
import java.awt.Color;
import java.awt.Graphics;
import java.net.MalformedURLException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class TestAnimation2 {
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
private static final int SLOWEST_RATE = 10000000;
private static final int FASTEST_RATE = 1000;
private static final int RANGE = SLOWEST_RATE - FASTEST_RATE;
private static final int MIN = 0;
private static final int MAX = 100;
private double dx;
private double dy;
private double x = WIDTH / 2;
private double y = HEIGHT / 2;
private volatile long delay = convert(50);
private JFrame frame;
private CirclePanel circle;
private Runnable job;
private long lastMove = System.currentTimeMillis();
protected void initUI() throws MalformedURLException {
frame = new JFrame(TestAnimation2.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
circle = new CirclePanel();
circle.setSize(20, 20);
frame.add(circle);
frame.setSize(WIDTH, HEIGHT);
dx = 1;
dy = 1;
final ScheduledExecutorService sheduledThreadPool = Executors.newScheduledThreadPool(1);
final JSlider slider = new JSlider(MIN, MAX);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
delay = convert(slider.getValue());
}
});
slider.setValue(50);
slider.setLocation(0, 0);
slider.setSize(slider.getPreferredSize());
frame.add(slider);
frame.setVisible(true);
job = new Runnable() {
#Override
public void run() {
move();
sheduledThreadPool.schedule(job, delay, TimeUnit.NANOSECONDS);
}
};
sheduledThreadPool.schedule(job, delay, TimeUnit.NANOSECONDS);
}
protected long convert(float sliderValue) {
return (long) (SLOWEST_RATE - sliderValue / (MAX - MIN) * RANGE);
}
protected void move() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
System.err.println("Ellapsed " + (System.currentTimeMillis() - lastMove) + " delay is " + (double) delay / 1000000 + " ms");
x += dx;
y += dy;
if (x + circle.getWidth() > frame.getContentPane().getWidth()) {
x = frame.getContentPane().getWidth() - circle.getWidth();
dx = -1;
} else if (x < 0) {
x = 0;
dx = 1;
}
if (y + circle.getHeight() > frame.getContentPane().getHeight()) {
y = frame.getContentPane().getHeight() - circle.getHeight();
dy = -1;
} else if (y < 0) {
y = 0;
dy = 1;
}
circle.setLocation((int) x, (int) y);
frame.repaint();
lastMove = System.currentTimeMillis();
}
});
}
public static class CirclePanel extends JPanel {
public CirclePanel() {
super();
setOpaque(false);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
g.fillOval(0, 0, getWidth(), getHeight());
}
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
UnsupportedLookAndFeelException {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
new TestAnimation2().initUI();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}

Categories