I have a list of JPanels that I want to display as a "slideshow" where one JPanel fades out and the next JPanel in the list fades in. This is the code I am fiddling with:
public float opacity = 0f;
private Timer fadeTimer;
private boolean out;
public void fadeIn()
{
out = false;
beginFade();
}
public void fadeOut ()
{
out = true;
beginFade();
}
private void beginFade()
{
fadeTimer =
new javax.swing.Timer(75,this);
fadeTimer.setInitialDelay(0);
fadeTimer.start();
}
public void actionPerformed(ActionEvent e)
{
if (out)
{
opacity -= .03;
if(opacity < 0)
{
opacity = 0;
fadeTimer.stop();
fadeTimer = null;
}
}
else
{
opacity += .03;
if(opacity > 1)
{
opacity = 1;
fadeTimer.stop();
fadeTimer = null;
}
}
repaint();
}
public void paintComponent(Graphics g)
{
((Graphics2D) g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
g.setColor(getBackground());
g.fillRect(0,0,getWidth(),getHeight());
}
The problem is that it fades some times, and other times it does not, and other times the transition is very laggy. What I would prefer is that there is a fraction of a second where the screen goes white, between when one JPanel fades out and the next JPanel fades in. Does anyone know how I can solve this? Thanks in advance.
So, when dealing with these types of problems, it's generally a better idea to reduce the number of Timers you have, as each timer will be posting multiple events to the Event Dispatching Queue (there own tick updates as well as repaint events). All this activity could reduce the performance of the system.
Animation is also the illusion of change over time, to this end, rather the trying to loop from a start point to an end point, you should decide how long you want the animation to run for and calculate the progress of time and update the values accordingly (this more of a "timeline" based animation cycle). This can help reduce the appearance of "lagging"
Normally I'd use the Timing Framework to accomplish this, but you could also have a look at the Trident framework or the Universal Tween Engine which also provide complex animation support for Swing.
This example is very tightly coupled to it's goal. Personally, I'd normally have a abstract concept of an "animatiable" object, which would probably just have the update(float) method, which would then be expanded to support other objects, but I'll leave that you to nut out.
Another issue is making sure that the component is fully transparent to begin with (setOpaque(false)), this allows us to fake the translucency of the component during the animation.
Normally, I'd always encourage you to override paintComponent, but there a few times when this is not adequate, this is one of them. Basically, in order to facilitate the transition from one component to the other, we need to control the alpha level of ALL the child components within the component, this is when overriding paint will be a better choice.
nb: The code is set to run at around 25fps, but the screen capture software captures at roughly 8fps
import java.awt.AlphaComposite;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class FadeTest {
public static void main(String[] args) {
new FadeTest();
}
public FadeTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
BufferedImage img1 = ImageIO.read(new File("C:\\Users\\shane\\Dropbox\\Ponies\\sillydash-small.png"));
BufferedImage img2 = ImageIO.read(new File("C:\\Users\\shane\\Dropbox\\Ponies\\SmallPony.png"));
AlphaPane pane1 = new AlphaPane();
pane1.add(new JLabel(new ImageIcon(img1)));
pane1.setAlpha(1f);
AlphaPane pane2 = new AlphaPane();
pane2.add(new JLabel(new ImageIcon(img2)));
pane2.setAlpha(0f);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 1;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.fill = GridBagConstraints.BOTH;
frame.add(pane1, gbc);
frame.add(pane2, gbc);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
MouseAdapter ma = new MouseAdapter() {
private AnimationController controller;
#Override
public void mouseClicked(MouseEvent e) {
try {
if (controller != null) {
controller.stop();
}
controller = new AnimationController(4000);
boolean fadeIn = pane1.getAlpha() < pane2.getAlpha();
controller.add(controller.new AlphaRange(pane1, fadeIn));
controller.add(controller.new AlphaRange(pane2, !fadeIn));
controller.start();
} catch (InvalidStateException ex) {
ex.printStackTrace();
}
}
};
pane1.addMouseListener(ma);
pane2.addMouseListener(ma);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class AnimationController {
private List<AlphaRange> animationRanges;
private Timer timer;
private Long startTime;
private long runTime;
public AnimationController(int runTime) {
this.runTime = runTime;
animationRanges = new ArrayList<>(25);
}
public void add(AlphaRange range) {
animationRanges.add(range);
}
public void start() throws InvalidStateException {
if (timer == null || !timer.isRunning()) {
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long duration = System.currentTimeMillis() - startTime;
float progress = (float) duration / (float) runTime;
if (progress > 1f) {
progress = 1f;
stop();
}
System.out.println(NumberFormat.getPercentInstance().format(progress));
for (AlphaRange range : animationRanges) {
range.update(progress);
}
}
});
timer.start();
} else {
throw new InvalidStateException("Animation is running");
}
}
public void stop() {
if (timer != null) {
timer.stop();
}
}
public class AlphaRange {
private float from;
private float to;
private AlphaPane alphaPane;
public AlphaRange(AlphaPane alphaPane, boolean fadeIn) {
this.from = alphaPane.getAlpha();
this.to = fadeIn ? 1f : 0f;
this.alphaPane = alphaPane;
}
public float getFrom() {
return from;
}
public float getTo() {
return to;
}
public float getValueBasedOnProgress(float progress) {
float value = 0;
float distance = to - from;
value = (distance * progress);
value += from;
return value;
}
public void update(float progress) {
float alpha = getValueBasedOnProgress(progress);
alphaPane.setAlpha(alpha);
}
}
}
public class InvalidStateException extends Exception {
public InvalidStateException(String message) {
super(message);
}
public InvalidStateException(String message, Throwable cause) {
super(message, cause);
}
}
public class AlphaPane extends JPanel {
private float alpha;
public AlphaPane() {
setOpaque(false);
}
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
super.paint(g2d);
g2d.dispose();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Fake the background
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
public void setAlpha(float value) {
if (alpha != value) {
this.alpha = Math.min(1f, Math.max(0, value));
repaint();
}
}
public float getAlpha() {
return alpha;
}
}
}
Related
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 have an application containing a jframe, this jframe then adds a jpanel which constains an image. the jpanel is displayed for a given time, then removed from the jframe and another jpanel is added.
I want to fade in and out between the images, and ive done this using a timer
private void fadeOut() {
ActionListener fadeOutAc = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
opacity += 10;
if (opacity >= 255) {
opacity = 255;
fadeOutT.stop();
}
repaint();
}
};
fadeOutT = new Timer(20, fadeOutAc);
fadeOutT.start();
}
private void fadeIn() {
ActionListener fadeInAc = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
opacity -= 10;
if (opacity <= 0) {
opacity = 0;
fadeInT.stop();
}
repaint();
}
};
fadeInT = new Timer(10, fadeInAc);
fadeInT.setInitialDelay(200);
fadeInT.start();
}
public void paint(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(picColor.getRed(), picColor.getGreen(), picColor.getBlue(), opacity));
g.fillRect(0, 0, presWin.getWidth(), presWin.getHeight());
}
i recently moved the fading in/out from the jpanel to the jframe instead. The problem is, that in the jpanel, the repaint only had to draw an image, now it has to repaint the entire jpanel each time. Is there a way to call repaint without having the paint the components, only the rectangel?
To me, it seems a bit silly to put the functionality in the JFrame when what you seem to want is a container which can fade it's content in and out. This way you can isolate the responsibility to a single container/class which can be placed or used in what ever way you want in isolation to the rest of the UI.
Basically, this example uses a FadingPane (based on a JPanel) to control the fading process, but onto which I place JLabel which holds the actual images.
Fading is controlled through the use of a AlphaComposite, meaning that this panel will actually physically fade in and out, not just change fill color ;)
There is also a FadingListener which provides additional notifications about the fading process, really only interested in fadeOutDidComplete, so you can switch the images and fade the panel back in, but you never know...
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
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.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
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 {
private JLabel label;
private FadingPane fadingPane;
private File[] pictures;
private int index;
public TestPane() {
// Just for show
setBackground(Color.RED);
fadingPane = new FadingPane(new FadeListener() {
#Override
public void fadeDidStart(FadingPane panel) {
}
#Override
public void fadeDidStop(FadingPane panel) {
}
#Override
public void fadeOutDidComplete(FadingPane panel) {
nextPicture();
fadingPane.fadeIn();
}
#Override
public void fadeInDidComplete(FadingPane panel) {
}
});
setLayout(new BorderLayout());
fadingPane.setLayout(new BorderLayout());
label = new JLabel();
fadingPane.add(label);
add(fadingPane);
JButton next = new JButton("Next");
add(next, BorderLayout.SOUTH);
next.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fadingPane.fadeOut();
}
});
pictures = new File("/Volumes/Disk02/Dropbox/MegaTokyo/thumnails").listFiles(new FileFilter() {
#Override
public boolean accept(File pathname) {
String name = pathname.getName().toLowerCase();
return name.endsWith(".jpg") || name.endsWith(".png");
}
});
nextPicture();
}
protected void nextPicture() {
index++;
if (index >= pictures.length) {
index = 0;
}
try {
BufferedImage img = ImageIO.read(pictures[index]);
label.setIcon(new ImageIcon(img));
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
public interface FadeListener {
public void fadeDidStart(FadingPane panel);
public void fadeDidStop(FadingPane panel);
public void fadeOutDidComplete(FadingPane panel);
public void fadeInDidComplete(FadingPane panel);
}
public class FadingPane extends JPanel {
private float delta;
private float alpha = 1f;
private Timer timer;
private FadeListener fadeListener;
public FadingPane(FadeListener fadeListener) {
this.fadeListener = fadeListener;
// This is important, as we may not always be opaque
// and we don't want to stuff up the painting process
setOpaque(false);
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
float alpha = getAlpha() + delta;
if (alpha < 0.001f) {
alpha = 0f;
timer.stop();
fadeListener.fadeOutDidComplete(FadingPane.this);
} else if (alpha >= 1.0f) {
alpha = 1.0f;
timer.stop();
fadeListener.fadeInDidComplete(FadingPane.this);
}
setAlpha(alpha);
}
});
}
public float getAlpha() {
return alpha;
}
public void setAlpha(float value) {
if (alpha != value) {
this.alpha = Math.min(1.0f, Math.max(0.0f, value));
repaint();
}
}
#Override
public void paint(Graphics g) {
// I don't normally recomamned overriding paint, but in this case,
// I want to affect EVERYTHING that might be added to this panel
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(getAlpha()));
super.paint(g2d);
g2d.dispose();
}
public void fadeIn() {
timer.stop();
fadeListener.fadeDidStop(FadingPane.this);
delta = 0.05f;
timer.restart();
fadeListener.fadeDidStart(FadingPane.this);
}
public void fadeOut() {
timer.stop();
fadeListener.fadeDidStop(FadingPane.this);
delta = -0.05f;
timer.restart();
fadeListener.fadeDidStart(FadingPane.this);
}
}
}
Thats totaly normal, moving your function to the JFrame and calling repaint function would actualy call repaint of your JFrame.
I think the best solution would be to pass panel as an argument to your fadeIn and fadeOut function and call its repaint methode for example fadeIn :
private void fadeIn(JPanel panelParam) {
ActionListener fadeInAc = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
opacity -= 10;
if (opacity <= 0) {
opacity = 0;
fadeInT.stop();
}
panelParam.repaint(); // here call repaint of the panel.
}
};
fadeInT = new Timer(10, fadeInAc);
fadeInT.setInitialDelay(200);
fadeInT.start();
}
With that you can apply your effect on any other panel.
Hope it helped.
The program draws a bunch of rectangles for a bar graph. I know the bar class works perfectly fine because I've got it working before adding in the graph panel class. I was drawing straight onto the frame instead of the graph panel. I assume its a problem in the way my set visible methods are called as it was pointed out to me before. I tried looking into it but I've had no luck after playing around and reading documentation.
import java.awt.Color;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.concurrent.Semaphore;
#SuppressWarnings("serial")
public class GraphPanel extends JPanel {
private ArrayList<Bar> graphBars;
private int nBars;
public GraphPanel(int nBars, JFrame mainFrame) {
this.setSize(400, 400);
this.graphBars = new ArrayList<Bar>(nBars);
this.nBars = nBars;
this.initBars(mainFrame.getWidth());
for(Bar b: this.graphBars) {
this.add(b);
}
}
private void initBars(int frameW) {
Random random = new Random();
float hue;
Color color;
int barPadding = frameW/this.nBars;
for(int i = 0; i < this.nBars; i++) {
hue = random.nextFloat();
color = Color.getHSBColor(hue, 0.9f, 1.0f);
this.graphBars.add(new Bar(i*barPadding + 30, 350, color));
}
}
public ArrayList<Bar> getBarList() {
return this.graphBars;
}
}
#SuppressWarnings("serial")
public class Bar extends JPanel implements Runnable {
int height = 0;
Color barColor;
Rectangle bar;
private final int WIDTH = 20;
Thread bartender;
private Semaphore s;
public Bar(int x, int y, Color barColor) {
this.barColor= barColor;
this.bar = new Rectangle(x, y, this.WIDTH, this.height);
this.bartender= new Thread(this);
this.s = new Semaphore(1);
}
public boolean setNewHeight(int h) {
try {
this.s.acquire();
this.height = h;
this.s.release();
return true;
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
}
#SuppressWarnings("deprecation")
public void update() {
if (this.bar.height < this.height) {
bar.reshape(this.bar.x, --this.bar.y, this.bar.width, ++this.bar.height);
} else {
bar.reshape(this.bar.x, ++this.bar.y, this.bar.width, --this.bar.height);
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(this.barColor);
g2d.fill(this.bar);
}
#SuppressWarnings("deprecation")
public void callBarTender() {
this.bartender.resume();
}
#SuppressWarnings("deprecation")
#Override
public void run() {
System.out.println("sdf");
while(true) {
if (this.bar.height < this.height) {
for(int i = this.bar.height; i<this.height; i++ ) {
try {
update();
repaint();
Thread.sleep(15);
} catch(Exception e) {
System.out.println(e);
}
}
} else if (this.height < this.bar.height) {
for(int i = this.bar.height; i>this.height; i-- ) {
try {
update();
repaint();
Thread.sleep(15);
} catch(Exception e) {
System.out.println(e);
}
}
}
this.bartender.suspend();
}
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(400, 400);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphPanel gPane = new GraphPanel(3, frame);
frame.add(gPane);
gPane.getBarList().get(0).setVisible(true);
gPane.getBarList().get(1).setVisible(true);
gPane.getBarList().get(2).setVisible(true);
gPane.setVisible(true);
frame.setVisible(true);
gPane.getBarList().get(0).setNewHeight(100);
gPane.getBarList().get(1).setNewHeight(100);
gPane.getBarList().get(2).setNewHeight(100);
gPane.getBarList().get(0).bartender.start();
gPane.getBarList().get(1).bartender.start();
gPane.getBarList().get(2).bartender.start();
}
You should override getPreferredSize of your GraphPanel to ensure that they are laid out correctly
The x/y positions you are passing to the Bar class are irrelevant, as this is causing your Rectangle to paint outside of the visible context of the Bar pane. Painting is done from within the context of the component (0x0 been the top/left corner of the component)
The use of Rectangle or the way you are using it, is actually causing issues. It's impossible to know exactly how big you component will be until it's layed or painted
There is a reason why resume and suspend are deprecated, this could cause no end of "weird" (and wonderful) issues
Take a look at Laying Out Components Within a Container for why you're bars aren't been updated correctly and why the x/y coordinates are pointless
Take a look at How to use Swing Timers for an alternative to your use of Thread
Possibly, something more like...
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 java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.LineBorder;
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();
frame.setSize(400, 400);
// frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphPanel gPane = new GraphPanel(3, frame);
frame.add(gPane);
gPane.getBarList().get(1).setFill(false);
gPane.getBarList().get(0).start();
gPane.getBarList().get(1).start();
gPane.getBarList().get(2).start();
frame.setVisible(true);
}
});
}
public class GraphPanel extends JPanel {
private ArrayList<Bar> graphBars;
private int nBars;
public GraphPanel(int nBars, JFrame mainFrame) {
this.graphBars = new ArrayList<Bar>(nBars);
this.nBars = nBars;
this.initBars(mainFrame.getWidth());
for (Bar b : this.graphBars) {
this.add(b);
}
}
private void initBars(int frameW) {
Random random = new Random();
float hue;
Color color;
for (int i = 0; i < this.nBars; i++) {
hue = random.nextFloat();
color = Color.getHSBColor(hue, 0.9f, 1.0f);
this.graphBars.add(new Bar(color));
}
}
public ArrayList<Bar> getBarList() {
return this.graphBars;
}
}
#SuppressWarnings("serial")
public class Bar extends JPanel {
private Color barColor;
private boolean fill = true;
private float fillAmount = 0;
private float delta = 0.01f;
private Timer timer;
private Rectangle bar;
public Bar(Color barColor) {
bar = new Rectangle();
setBorder(new LineBorder(Color.RED));
this.barColor = barColor;
timer = new Timer(15, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fillAmount += isFill() ? delta : -delta;
// System.out.println(fillAmount);
if (fillAmount < 0) {
fillAmount = 0;
((Timer) e.getSource()).stop();
} else if (fillAmount > 1.0f) {
fillAmount = 1f;
((Timer) e.getSource()).stop();
}
repaint();
}
});
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
public void setFill(boolean fill) {
this.fill = fill;
if (!timer.isRunning()) {
if (fill && fillAmount == 1) {
fillAmount = 0;
} else if (!fill && fillAmount == 0) {
fillAmount = 1;
}
}
}
public boolean isFill() {
return fill;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(20, 100);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(this.barColor);
int height = Math.round(getHeight() * fillAmount);
bar.setSize(getWidth(), height);
bar.setLocation(0, getHeight() - height);
g2d.fill(bar);
g2d.dispose();
}
}
}
My goal is moving clipping area 10 pixels at a time using arrow keys. I got the image on the panel and the clipping area is there too, but the thing is that the clipping area won't move. Here is my code, and I hope to learn what's wrong with it.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class clipping_area extends JFrame{
clipping_area(){
setTitle("OpenChallenge");
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500,500);
add(new panelOC());
}
class panelOC extends JPanel{
int xAxis=0;
int yAxis=0;
public void paintComponent(Graphics g){
super.paintComponent(g);
Image img=(new ImageIcon("images/image1.jpg")).getImage();
g.setClip(100+10*xAxis,100+10*yAxis,50,50);
g.drawImage(img,0,0,getWidth(),getHeight(),this);
}
panelOC(){
requestFocus();
addKeyListener(new KeyAdapter(){
public void keyPressed(KeyEvent KE){
if(KE.getKeyCode()==KeyEvent.VK_UP){
yAxis-=1;
repaint();
}
else if(KE.getKeyCode()==KeyEvent.VK_DOWN){
yAxis+=1;
repaint();
}
else if(KE.getKeyCode()==KeyEvent.VK_LEFT){
xAxis-=1;
repaint();
}
else if(KE.getKeyCode()==KeyEvent.VK_RIGHT){
xAxis+=1;
repaint();
}
}
});
}
}
public static void main(String[] args){
new clipping_area();
}
}
KeyListener is a real pain in the, well, focus area. If the component it is attached to is not focusable AND has keyboard focus, it won't trigger events, that's the way it's designed. Instead, use the Key Bindings API which has been designed to overcome this.
See How to Use Key Bindings for more details
Be wary of modifying the clip of a Graphics context, a Graphics context is shared resource, meaning that it will be past to other components. You could also, if you're not careful, size the clip in such away as to paint beyond the range of the component, causing some weird graphics glitches, personally, I stay away from it.
If you use ImageIO.read instead, you can get a reference to a BufferedImage and use getSubImage to "fake" it instead
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import static javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ClippingArea extends JFrame {
ClippingArea() {
setTitle("OpenChallenge");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new PanelOC());
pack();
setVisible(true);
}
class PanelOC extends JPanel {
int xAxis = 0;
int yAxis = 0;
private BufferedImage img;
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
int width = 50;
if (xAxis + width > img.getWidth()) {
width = img.getWidth() - xAxis;
}
int height = 50;
if (yAxis + height > img.getHeight()) {
height = img.getHeight() - yAxis;
}
if (width > 0 && height > 0) {
BufferedImage subImage = img.getSubimage(xAxis, yAxis, width, height);
g.drawImage(subImage, xAxis, yAxis, this);
}
}
}
protected void registerKeyBinding(String name, KeyStroke keyStroke, Action action) {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(keyStroke, name);
am.put(name, action);
}
public PanelOC() {
try {
img = ImageIO.read(new File("C:\\hold\\thumbnails\\_cg_836___Tilting_Windmills___by_Serena_Clearwater.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
registerKeyBinding("moveClip.up", KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), new YKeyAction(-10));
registerKeyBinding("moveClip.down", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), new YKeyAction(10));
registerKeyBinding("moveClip.left", KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), new XKeyAction(-10));
registerKeyBinding("moveClip.right", KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), new XKeyAction(10));
}
public class XKeyAction extends AbstractAction {
private int delta;
public XKeyAction(int delta) {
this.delta = delta;
}
#Override
public void actionPerformed(ActionEvent e) {
xAxis += delta;
if (yAxis > getWidth()) {
yAxis = getWidth() - 50;
} else if (yAxis < 0) {
yAxis = 0;
}
repaint();
}
}
public class YKeyAction extends AbstractAction {
private int delta;
public YKeyAction(int delta) {
this.delta = delta;
}
#Override
public void actionPerformed(ActionEvent e) {
yAxis += delta;
if (yAxis > getHeight()) {
yAxis = getHeight() - 50;
} else if (yAxis < 0) {
yAxis = 0;
}
repaint();
}
}
}
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();
}
ClippingArea ca = new ClippingArea();
}
});
}
}
Take a look at Reading/Loading an Image for more details
You should also be creating and modiying your UI from within the context of the Event Dispatching Thread, see Initial Threads for more details
instead of using a keylistener on a panel use a AWTEventListener (which will grab all events of a particular type
java.awt.Toolkit toolkit = java.awt.Toolkit.getDefaultToolkit();
toolkit.addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent ae) {
if (ae instanceof KeyEvent) {
KeyEvent KE = (KeyEvent) ae;
if (KE.getID() == KeyEvent.KEY_PRESSED) {
switch(KE.getKeyCode()) {
case KeyEvent.VK_UP:
yAxis -= 1;
break;
case KeyEvent.VK_DOWN:
yAxis += 1;
break;
case KeyEvent.VK_LEFT:
xAxis -= 1;
break;
case KeyEvent.VK_RIGHT:
xAxis += 1;
break;
}
repaint();
}
}
}
}, AWTEvent.KEY_EVENT_MASK);
And a second edit, I replaced your if statements with a switch ( its significantly cleaner to read and easier to modify later.
I can make a single JLabel fade in, but how do I make multiple labels fade in one after another? Here is my current code.
public void fadeIn(final JLabel jLabel) {
jLabelList.add(jLabel);
jLabelBackPanel.add(jLabelList.get(jLabelList.size() - 1));
new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
int c = 0;
for(int i = 0; i < 255; i++) {
Thread.sleep(1000);
jLabel.setForeground(new Color(
jLabel.getForeground().getRed(),
jLabel.getForeground().getGreen(),
jLabel.getForeground().getBlue(),
c++));
}
return null;
}
}.execute();
}
The above code will fade in about 9 labels at one time, then 9 more, etc. I can't figure out how to make one label wait until the last one is done fading in.
Now, there's probably a few ways this might be done, but this is basically what I've come up with...
First, I created a custom label, which uses a javax.swing.Timer to progress from a starting alpha value to a target alpha value (ie 0-1 to fade in).
To this label, I added a simply waitFor method, which waits until the target value has been reached. This is achieved through a simple object monitor. Very important, NEVER call this method while you're on the Event Dispatching Thread...
Next, I created a series of these labels with the text I wanted displayed and added each one to to the output.
I then started a separate Thread and iterated the list, fading each label in and using waitFor to wait for it to be displayed...
import java.awt.AlphaComposite;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class FadingLabels {
public static void main(String[] args) {
new FadingLabels();
}
public FadingLabels() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
final List<FadingLabel> labels = new ArrayList<>(25);
labels.add(new FadingLabel("A "));
labels.add(new FadingLabel("long "));
labels.add(new FadingLabel("time "));
labels.add(new FadingLabel("ago "));
labels.add(new FadingLabel("in "));
labels.add(new FadingLabel("a "));
labels.add(new FadingLabel("galaxy "));
labels.add(new FadingLabel("far, "));
labels.add(new FadingLabel("far, "));
labels.add(new FadingLabel("away"));
labels.add(new FadingLabel("..."));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
for (FadingLabel label : labels) {
frame.add(label);
}
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
new Thread(new Runnable() {
#Override
public void run() {
for (FadingLabel label : labels) {
label.fadeIn();
label.waitFor();
}
}
}).start();
}
});
}
public class FadingLabel extends JLabel {
protected static final int TIME = 1000;
protected final Object fadeLock = new Object();
private float targetAlpha;
private float alpha = 0;
private Timer timer;
private long startTime;
private float fromAlpha;
public FadingLabel() {
init();
}
public FadingLabel(String text, Icon icon, int horizontalAlignment) {
super(text, icon, horizontalAlignment);
init();
}
public FadingLabel(String text, int horizontalAlignment) {
super(text, horizontalAlignment);
init();
}
public FadingLabel(String text) {
super(text);
init();
}
public FadingLabel(Icon image, int horizontalAlignment) {
super(image, horizontalAlignment);
init();
}
public FadingLabel(Icon image) {
super(image);
init();
}
protected void init() {
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (alpha < 1f) {
long now = System.currentTimeMillis();
long diff = now - startTime;
float progress = (float) diff / (float) TIME;
float distance = targetAlpha - fromAlpha;
alpha = (float) (distance * progress);
alpha += fromAlpha;
if (alpha > 1f) {
timer.stop();
alpha = 1f;
}
} else {
alpha = 1f;
timer.stop();
}
repaint();
if (!timer.isRunning()) {
synchronized (fadeLock) {
fadeLock.notifyAll();
}
}
}
});
timer.setInitialDelay(0);
}
protected void fadeTo(float target) {
Runnable run = new Runnable() {
#Override
public void run() {
timer.stop();
fromAlpha = alpha;
targetAlpha = target;
if (targetAlpha != alpha) {
startTime = System.currentTimeMillis();
timer.start();
} else {
repaint();
}
}
};
if (EventQueue.isDispatchThread()) {
run.run();
} else {
EventQueue.invokeLater(run);
}
}
public void fadeIn() {
fadeTo(1f);
}
public void fadeOut() {
fadeTo(0f);
}
public void waitFor() {
if (EventQueue.isDispatchThread()) {
throw new IllegalStateException("Calling waitFor while within the EDT!");
}
synchronized (fadeLock) {
try {
fadeLock.wait();
} catch (InterruptedException ex) {
}
}
}
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
super.paint(g2d);
g2d.dispose();
}
}
}
The animation portion uses a fixed time progression, rather then a fixed delta. This allows the animation to be more variable depending on the over heads that might be occurred from the OS. Basically what this means is that the animation will progress over a fixed period of time each time