Animate image list by group Java Swing - java

Currently my code animates an image from the list from initial size to final size and continues with the next one until it completes animating all images and all have their final size.
package ui;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.Timer;
import utils.SudokuGen;
// TODO: Auto-generated Javadoc
/**
* The Class ButtonPanel.
*/
public class ButtonPanel extends JPanel implements ActionListener{
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The buttons. */
private JButton[] buttons;
private int[] imgsize;
private int index,count;
private Timer timer;
private ImageButton images;
private static final int COUNT = 10;
private static final int INITSIZE = 32;
private static final int COLUMNS = 1;
private static final int ROWS = 9;
/**
* Instantiates a new button panel.
*
* #param puzzle The Sudoku matrix object
* #param images The images object
* #param sPanel The Sudoku JPanel
*/
public ButtonPanel(SudokuGen puzzle, ImageButton images, SudokuPanel sPanel){
//Create an array of JButton with 9 elements
this.buttons = new JButton[puzzle.getMyboard().getNewnumbers().size()];
this.images = images;
this.imgsize = new int[COUNT];
for (int i=0;i<COUNT;i++){
imgsize[i] = INITSIZE;
}
//Sets the layout to a 9*1 GridLayout with padding 5
this.setLayout(new GridLayout(ROWS,COLUMNS,5,5));
//Load an image, create the MouseListener and add it at index i
for (int i=1;i<this.images.getImagelist().size();i++){
ImageIcon image = new ImageIcon(this.images.getImagelist()
.get(i).getImage().getScaledInstance(32, 32, Image.SCALE_SMOOTH));
buttons[i] = createMouseListener(image, i, puzzle, sPanel);
buttons[i].setPreferredSize(new Dimension(100, 100));
this.add(buttons[i]);
}
index = 1; //first button
count = 0; //times to resize the button
timer = new Timer(5,this);
timer.start();
}
public void set(int X){
this.imgsize[index] = X;
}
/**
* Creates the mouse listener.
*
* #param image The image at index i
* #param i The index for the button i
* #param puzzle The Sudoku matrix object
* #param sPanel The Sudoku JPanel
* #return the JButton to add at index i
*/
private JButton createMouseListener(ImageIcon image, int i, SudokuGen puzzle, SudokuPanel sPanel){
JButton button = new JButton();
button.setIcon(image);
button.setActionCommand(Integer.toString(puzzle.getMyboard().getNewnumbers().get(i)));
button.addActionListener(sPanel.new ButtonActionListener());
return button;
}
#Override
public void actionPerformed(ActionEvent e) {
count++;
set(this.imgsize[index]+1); //increase the size of the image
if (this.imgsize[index] < 64){
ImageIcon image = new ImageIcon(this.images.getImagelist()
.get(index).getImage().getScaledInstance(this.imgsize[index], this.imgsize[index], Image.SCALE_SMOOTH));
buttons[index].setIcon(image);
if (count > 24){ //reached final size
count = 0; //start counting again
index++; //move to the next image
}
};
if (index == 10) timer.stop();
repaint();
}
}
I'm not able to provide an SSCCE given the amount of resources needed for it to work so apologizes for that.
My goal is to start animating the next image when the first animation finished half its process. So when the second image reaches its half animation the first one completes its full animation. Kind of a wave effect.
If there something else I could improve, I'm glad to hear suggestions.
Update: Updated method used to achieve the effect:
public void actionPerformed(ActionEvent e) {
count++;
this.imgsize[index]++;
ImageIcon image = new ImageIcon(this.images.getImagelist()
.get(index).getImage().getScaledInstance(this.imgsize[index], this.imgsize[index], Image.SCALE_SMOOTH));
buttons[index].setIcon(image);
if (count > 24){
count = 0;
index++;
}
if (count > 12 && index < 9)
{
this.imgsize[index+1]++;
image = new ImageIcon(this.images.getImagelist()
.get(index+1).getImage().getScaledInstance(this.imgsize[index+1], this.imgsize[index+1], Image.SCALE_SMOOTH));
buttons[index+1].setIcon(image);
}
if (index == 10) timer.stop();
repaint();
}

This is a somewhat overly simplified idea...
Basically, it sets up a normalised timeline (0-1) where events can occur along it, starting and ending at specified points. During these "periods" some "action" can occur.
In your case, these actions are image scaling actions, the scale of which is a proportion of the amount of time that a given event has been running, which is a proportion of the overall time line.
This all sounds wonderfully confusing, but the benefit of which is, you can change the duration of the timeline and the rest of the code will scale automatically
For example, you could take...
timeline = new Timeline(events, 10000);
which generates a timeline to run over 10 seconds and change it to
timeline = new Timeline(events, 5000);
which will generate a timeline to run over 5 seconds and you've not had to change a single line of code to make it work.
I've also spend some time working out how to automatically generate timeline events from a series of images, so you can supply n number of images, and it will generate the required events so that they overlap each other accordingly
Now, you're probably thinking that this is all overly complicated, but the point is, it's all variable, the duration over which the images are scaled, the number of images, it's all variable and you don't need to do much to add/remove images or change the duration
Cavert- I've used getScaledInstance for this example, it's neither fast nor does it generate a quality image
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
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 {
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);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private Timeline timeline;
private Map<Object, Image> mapImages = new HashMap<>(5);
private List<Object> imageOrder = new ArrayList<>(5);
public TestPane() throws IOException {
BufferedImage[] images = new BufferedImage[]{
ImageIO.read(new File("...")),
ImageIO.read(new File("...")),
ImageIO.read(new File("...")),
ImageIO.read(new File("..."))
};
List<TimelineEvent> events = generateEvents(images, new ImageScaledObserver() {
#Override
public void imageScaled(Object id, Image image) {
if (!imageOrder.contains(id)) {
imageOrder.add(id);
}
mapImages.put(id, image);
repaint();
}
});
timeline = new Timeline(events, 10000);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timeline.update()) {
((Timer) e.getSource()).stop();
}
}
});
timer.setInitialDelay(5000);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Object id : imageOrder) {
Image image = mapImages.get(id);
int x = (getWidth() - image.getWidth(this)) / 2;
int y = (getHeight() - image.getHeight(this)) / 2;
g2d.drawImage(image, x, y, this);
}
g2d.dispose();
}
}
protected List<TimelineEvent> generateEvents(BufferedImage[] images, ImageScaledObserver observer) {
double length = 1.0 / (double) (images.length);
double overlap = length * 0.5;
List<TimelineEvent> events = new ArrayList<>(images.length);
double startAt = 0.0;
for (BufferedImage image : images) {
double endAt = Math.min(startAt + length + (overlap / 2.0), 1.0);
events.add(new ScaleImageTimelineEvent(image, observer, startAt, endAt));
startAt = Math.min(endAt - (overlap / 2.0), 1.0);
}
return events;
}
public interface TimelineEvent {
public double[] range();
public boolean isWithin(double timelineProgression);
public void performAction(double timelineProgression);
}
public abstract class AbstractTimelineEvent implements TimelineEvent {
private double from, to;
public AbstractTimelineEvent(double from, double to) {
this.from = from;
this.to = to;
}
#Override
public double[] range() {
return new double[]{from, to};
}
#Override
public boolean isWithin(double timelineProgression) {
boolean within = timelineProgression >= from && timelineProgression <= to;
return within;
}
protected double localisedProgression(double timelineProgression) {
double max = from - to;
double value = timelineProgression - to;
double weight = value / max;
return 1d - weight;
}
}
public interface ImageScaledObserver {
public void imageScaled(Object id, Image img);
}
public class ScaleImageTimelineEvent extends AbstractTimelineEvent {
private BufferedImage original;
private ImageScaledObserver observer;
private UUID id;
public ScaleImageTimelineEvent(BufferedImage image, ImageScaledObserver observer, double from, double to) {
super(from, to);
this.original = image;
this.observer = observer;
this.id = UUID.randomUUID();
}
#Override
public void performAction(double timelineProgression) {
double progress = localisedProgression(timelineProgression);
Image image = null;
if (progress < 1.0) {
int width = (int) (original.getWidth() * progress);
if (width > 0) {
image = original.getScaledInstance((int) (original.getWidth() * progress), -1, Image.SCALE_FAST);
}
} else {
image = original;
}
if (image != null) {
observer.imageScaled(id, image);
}
}
}
public static class Timeline {
private List<TimelineEvent> events;
private Long startTime;
private long duration;
public Timeline(List<TimelineEvent> events, long duration) {
this.events = events;
this.duration = duration;
}
public List<TimelineEvent> getEvents() {
return events;
}
public Long getStartTime() {
return startTime;
}
public long getDuration() {
return duration;
}
public void start() {
if (startTime == null) {
startTime = System.nanoTime();
}
}
public void stop() {
startTime = null;
}
public boolean update() {
if (startTime == null) {
start();
}
boolean completed = false;
long currentTime = System.nanoTime();
long diff = currentTime - getStartTime();
long nanoDuration = TimeUnit.NANOSECONDS.convert(getDuration(), TimeUnit.MILLISECONDS);
double progress = diff / (double) nanoDuration;
if (progress > 1.0d) {
progress = 1.0d;
completed = true;
stop();
}
for (TimelineEvent evt : getEvents()) {
if (evt.isWithin(progress)) {
evt.performAction(progress);
}
}
return completed;
}
}
}
I'd also suggest having a look at some of the animation frameworks which are available, which provide additional functionality, like easement. Maybe do a search for "Timing Framework" as a suggestion
The example uses a UUID to generate a unique identifier for the imageScaled event. I wouldn't be hard to make ScaleImageTimelineEvent take an identifier of your own which is linked back to a specific image, allowing you to determine which image was scaled and perform more accurate updates

Related

The Constructor Window() is not visible

iam just starting programming in Java. Have issue with classes. As i try to use Windows in my project it keep shows this errors: Multiple markers at this line
- The constructor Window() is not visible
- The constructor Window(GameContainer) is
undefined
i tried make my class public but it didnt help. Can anyone helps me? Error is showing on Window lines in main class. Thanks anyone for helpss :)
main class:
package g_core_engine;
import java.awt.Window;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
public class GameContainer implements Runnable{
private Thread thread;
private Window window;
private boolean running = false;
private final double UPDATE_CAP = 1.0 / 60.0;
private int width = 320, height = 240;
private float Scale = 4;
private String title = "g_core_engine v1.0";
public GameContainer()
{
}
//Method that starts the actual computing of the game
public void start()
{
window = new Window();
thread = new Thread(this);
thread.run();
}
public void stop()
{
}
//Method that contains the update and render loops
public void run()
{
running = true;
boolean render = false;
enter code heredouble firstTime = 0;
double lastTime = System.nanoTime() / 1000000000.0;
double passedTime = 0;
double unprocessedTime = 0;
double frameTime = 0;
int frames = 0;
int fps = 0;
while(running)
{
render = false;
firstTime = System.nanoTime() / 1000000000.0;
passedTime = firstTime - lastTime;
lastTime = firstTime;
unprocessedTime += passedTime;
frameTime += passedTime;
while(unprocessedTime >= UPDATE_CAP)
{
unprocessedTime -= UPDATE_CAP;
render = true;
//TODO: Update Game
if(frameTime >= 1.0)
{
frameTime = 0;
fps = frames;
frames = 0;
System.out.println("FPS: "+fps);
}
}
if(render)
{
//TODO: Render Game
window.update();
frames++;
}
else
{
try
{
thread.sleep(1);
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
}
dispose();
}
public void dispose()
{
}
//Main method that runs on program start
public static void main(String[] args)
{
new GameContainer().start();
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getScale() {
// TODO Auto-generated method stub
return 0;
}
}
second class:
package g_core_engine;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
public class Window
{
private JFrame frame;
private BufferedImage image;
private Canvas canvas;
private BufferStrategy bs;
private Graphics g;
public Window (GameContainer gc)
{
image = new BufferedImage(gc.getWidth(), gc.getHeight(), BufferedImage.TYPE_INT_RGB);
canvas = new Canvas();
Dimension s = new Dimension((int)(gc.getWidth() * gc.getScale()), (int)(gc.getHeight() * gc.getScale()));
canvas.setPreferredSize(s);
canvas.setMaximumSize(s);
canvas.setMinimumSize(s);
frame = new JFrame(gc.getTitle());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(canvas, BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
canvas.createBufferStrategy(2);
bs = canvas.getBufferStrategy();
g = bs.getDrawGraphics();
}
public void update()
{
g.drawImage(image, 0, 0, canvas.getWidth(), canvas.getHeight(), null);
bs.show();
}
}
Looks like you imported a Window class:
import java.awt.Window;
So when you're trying to make an instance of your "window" its actually trying to make a java.awt.Window. Be careful with class names. Change the name of your Window class (and then change that Window.java file of yours to the new name you pick / as well as anywhere else you were trying to use your Window) and it should work fine.

How to get my Buffered Image class to display in my GUI?

I have a program that does an animation using timers switching images. When the program is on its last image I use a class to create a buffered image of that image with text over it. When the last image of the animation is displayed I want to change the image displayed to the buffered image. I can't get it to work. The code as is plays as if the bolded section isnt there. If I delete the line above it, it displays the image with text over it and nothing else. What edits should I make to my code to fix this?
The Class that does the animation
**import java.awt.event.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.image.*;
import java.io.*;
import java.io.File;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.swing.*;
import javax.swing.*;
import javax.imageio.ImageIO;
/**
* Write a description of class Reveal here.
*
* #author (your name)
* #version (a version number or a date)
*/
public class Reveal extends JPanel
{
private JPanel panel = new JPanel(); //a panel to house the label
private JLabel label = new JLabel(); //a label to house the image
private String[] image = {"Jack in the Box 1.png","Jack in the Box 2.png","Jack in the Box 3.png","Jack in the Box 4.png","Jack in the Box 5.png","Jack in the Box 6.png","Jack in the Box 7.png"}; //an array to hold the frames of the animation
private ImageIcon[] icon = new ImageIcon[7]; //an array of icons to be the images
private JFrame f;
private TextOverlay TO;
private Timer timer;
private Timer timer2;
int x = 0;
int y = 4;
int counter = 0;
/**
* Constructor for objects of class Reveal
*/
public Reveal(String name, int number)
{
TO = new TextOverlay("Jack in the Box 7.png", name, number);
for (int h = 0; h < 7; h++){
icon[h] = new ImageIcon(image[h]);
icon[h].getImage();
}
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
//Sets the size of the window
f.setSize(800,850);
panel = new JPanel();
label = new JLabel();
label.setIcon( icon[x] );
panel.add(label);
setVisible(true);
f.add(panel);
display(name, number);
**f.add(TO);**
}
public void display(String name, int number){
timer = new Timer(150, new ActionListener(){
public void actionPerformed(ActionEvent e) {
if (counter > 27){
timer.stop();
timer2.start(); //starts the second half of the animation
}else{
if (x != 3){
x++;
}else{
x = 0;
}
label.setIcon( icon[x] );
counter++;
} //ends if-else
} //ends action method
}); //ends timer
timer2 = new Timer(250, new ActionListener(){
public void actionPerformed(ActionEvent e){
if (y > 6) {
timer2.stop();
}else{
label.setIcon( icon[y] );
y++;
} //ends if-else
} //ends action method
}); //ends timer2
timer.start();
}
}
**
The class that puts text over an image
import java.io.*;
import java.awt.*;
import javax.swing.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
/**
* #see https://stackoverflow.com/questions/2658663
*/
public class TextOverlay extends JPanel {
private BufferedImage image;
private String name;
private String fileX;
private int number;
public TextOverlay(String f, String s, int n) {
name = s;
number = n;
fileX = f;
try {
image = ImageIO.read(new File(fileX));
} catch (IOException e) {
e.printStackTrace();
}
image = process(image, name, number);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
private BufferedImage process(BufferedImage old, String name, int number) {
int w = old.getWidth();
int h = old.getHeight();
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(old, 0, 0, w, h, this);
g2d.setPaint(Color.black);
g2d.setFont(new Font("Franklin Gothic Demi Cond", Font.PLAIN, 30));
String s1 = name;
String s2 = Integer.toString(number);;
FontMetrics fm = g2d.getFontMetrics();
g2d.drawString(s1, 40, 90);
g2d.drawString(s2, 40, 140);
g2d.dispose();
return img;
}
}
So, you seem to have a misunderstanding of how Swing works, you might find How to Use Swing Timers and Concurrency in Swing of some assistance.
Basically, when you start a Timer, it doesn't block at this point until the timer ends (and even if it did, your wouldn't work the way you wanted it to). Instead, a new thread is created and after the specified period a request is placed on Event Dispatching Thread to execute the supplied Runnable.
This means that when you do something like...
f.add(panel);
display(name, number);
f.add(TO);
You are actually adding the TO component onto of the JLabel (because the frame is using a BorderLayout and the CENTRE position is the default position.
Instead, in your second timer completes, you need to remove the label and add the TO component...
timer2 = new Timer(250, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (y > 6) {
timer2.stop();
Container parent = label.getParent();
parent.remove(label);
parent.add(TO);
parent.revalidate();
} else {
label.setIcon(icon[y]);
y++;
} //ends if-else
} //ends action method
}); //ends timer2
Runnable Example...
import java.awt.event.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.border.LineBorder;
public class Reveal 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();
}
new Reveal("Test", 5);
}
});
}
private JPanel panel = new JPanel(); //a panel to house the label
private JLabel label = new JLabel(); //a label to house the image
private ImageIcon[] icon = new ImageIcon[7]; //an array of icons to be the images
private JFrame f;
private TextOverlay TO;
private Timer timer;
private Timer timer2;
int x = 0;
int y = 4;
int counter = 0;
/**
* Constructor for objects of class Reveal
*/
public Reveal(String name, int number) {
TO = new TextOverlay("Jack in the Box 7.png", name, number);
for (int h = 0; h < 7; h++) {
icon[h] = new ImageIcon(makeImage(h));
icon[h].getImage();
}
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
//Sets the size of the window
f.setSize(800, 850);
panel = new JPanel(new GridBagLayout());
label = new JLabel();
label.setIcon(icon[x]);
label.setBorder(new LineBorder(Color.RED));
panel.add(label);
f.add(panel);
display(name, number);
// f.add(TO);
setVisible(true);
}
public void display(String name, int number) {
timer = new Timer(150, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (counter > 27) {
timer.stop();
timer2.start(); //starts the second half of the animation
} else {
if (x != 3) {
x++;
} else {
x = 0;
}
label.setIcon(icon[x]);
counter++;
} //ends if-else
} //ends action method
}); //ends timer
timer2 = new Timer(250, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (y > 6) {
timer2.stop();
Container parent = label.getParent();
parent.remove(label);
parent.add(TO);
parent.revalidate();
} else {
label.setIcon(icon[y]);
y++;
} //ends if-else
} //ends action method
}); //ends timer2
timer.start();
}
protected BufferedImage makeImage(int h) {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(h);
int x = (100 - fm.stringWidth(text)) / 2;
int y = ((100 - fm.getHeight()) / 2) + fm.getAscent();
g2d.setColor(Color.BLUE);
g2d.fillRect(0, 0, 100, 100);
g2d.setColor(Color.BLACK);
g2d.drawString(text, x, y);
g2d.dispose();
return img;
}
public class TextOverlay extends JPanel {
private BufferedImage image;
private String name;
private String fileX;
private int number;
public TextOverlay(String f, String s, int n) {
name = s;
number = n;
fileX = f;
image = makeImage(n);
image = process(image, name, number);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
private BufferedImage process(BufferedImage old, String name, int number) {
int w = old.getWidth();
int h = old.getHeight();
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(old, 0, 0, w, h, this);
g2d.setPaint(Color.black);
g2d.setFont(new Font("Franklin Gothic Demi Cond", Font.PLAIN, 30));
String s1 = name;
String s2 = Integer.toString(number);;
FontMetrics fm = g2d.getFontMetrics();
g2d.drawString(s1, 40, 90);
g2d.drawString(s2, 40, 140);
g2d.dispose();
return img;
}
}
}
A "slightly" different approach...
Animation is actually a really complex subject which is not easy to implement well.
This is why, when faced with problems like these, I prefer to look at libraries which have already been implemented to help solve them. I'd recommend having a look at:
The Timing Framework
Trident
universal-tween-engine
as some starting points.
While I prefer to use libraries, sometimes it's not possible or the libraries don't fit my overall needs ... that and I like to dabble ... it's kind of a hobby.
Based on what I can understand from your code, you're trying to start out with a fast animation and then slow it down till you get to the last frame. In animation theory, this is commonly known as easement, more specifically, "slow/ease out".
The following borrows from a bunch of snippets I've been playing with (to devise a more reusable library) that will basically (randomly) display the images over a period of 4 seconds, with the animation slowing down and finally, presenting the "lucky" number
nb The gif animation is actually really slow, you'll need to run it to see the difference
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.swing.*;
public class Reveal extends JPanel {
public static void main(String[] args) {
new Reveal();
}
public Reveal() {
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 IntAnimatable animatable;
private List<ImageIcon> icons = new ArrayList<>(25);
private JLabel label = new JLabel();
public TestPane() {
setLayout(new GridBagLayout());
IntRange range = new IntRange(0, 111);
animatable = new IntAnimatable(range, Duration.ofSeconds(4), Easement.SLOWOUT, new AnimatableListener<Integer>() {
#Override
public void animationChanged(Animatable<Integer> animator) {
int value = animator.getValue();
int index = value % 7;
ImageIcon icon = icons.get(index);
if (label.getIcon() != icon) {
label.setIcon(icon);
}
}
}, new AnimatableLifeCycleAdapter<Integer>() {
#Override
public void animationCompleted(Animatable<Integer> animator) {
BufferedImage img = makeImage(3);
writeTextOverImage("Lucky number", img);
ImageIcon luckNumber = new ImageIcon(img);
label.setIcon(luckNumber);
}
});
for (int index = 0; index < 7; index++) {
icons.add(new ImageIcon(makeImage(index)));
}
Collections.shuffle(icons);
add(label);
Animator.INSTANCE.add(animatable);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
protected void writeTextOverImage(String text, BufferedImage img) {
Graphics2D g2d = img.createGraphics();
Font font = g2d.getFont();
font = font.deriveFont(Font.BOLD, font.getSize2D() + 2);
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
int width = img.getWidth();
int height = img.getWidth();
int x = (width - fm.stringWidth(text)) / 2;
int y = fm.getAscent();
g2d.setColor(Color.YELLOW);
g2d.drawString(text, x, y);
g2d.dispose();
}
protected BufferedImage makeImage(int h) {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(h);
int x = (100 - fm.stringWidth(text)) / 2;
int y = ((100 - fm.getHeight()) / 2) + fm.getAscent();
g2d.setColor(Color.BLUE);
g2d.fillRect(0, 0, 100, 100);
g2d.setColor(Color.WHITE);
g2d.drawString(text, x, y);
g2d.dispose();
return img;
}
/**** Range ****/
/*
A lot of animation is done from one point to another, this just
provides a self contained concept of a range which can be used to
calculate the value based on the current progression over time
*/
public abstract class Range<T> {
private T from;
private T to;
public Range(T from, T to) {
this.from = from;
this.to = to;
}
public T getFrom() {
return from;
}
public T getTo() {
return to;
}
#Override
public String toString() {
return "From " + getFrom() + " to " + getTo();
}
public abstract T valueAt(double progress);
}
public class IntRange extends Range<Integer> {
public IntRange(Integer from, Integer to) {
super(from, to);
}
public Integer getDistance() {
return getTo() - getFrom();
}
#Override
public Integer valueAt(double progress) {
int distance = getDistance();
int value = (int) Math.round((double) distance * progress);
value += getFrom();
return value;
}
}
/**** Animatable ****/
/*
The core concept of something that is animatable. This basic wraps up the
logic for calculating the progression of the animation over a period of time
and then use that to calculate the value of the range and then the observers
are notified so they can do stuff
*/
public class IntAnimatable extends AbstractAnimatableRange<Integer> {
public IntAnimatable(IntRange animationRange, Duration duration, Easement easement, AnimatableListener<Integer> listener, AnimatableLifeCycleListener<Integer> lifeCycleListener) {
super(animationRange, duration, easement, listener, lifeCycleListener);
}
}
public interface AnimatableListener<T> {
public void animationChanged(Animatable<T> animator);
}
public interface AnimatableLifeCycleListener<T> {
public void animationStopped(Animatable<T> animator);
public void animationCompleted(Animatable<T> animator);
public void animationStarted(Animatable<T> animator);
public void animationPaused(Animatable<T> animator);
}
public interface Animatable<T> {
public T getValue();
public void tick();
public Duration getDuration();
public Easement getEasement();
// Wondering if these should be part of a secondary interface
// Provide a "self managed" unit of work
public void start();
public void stop();
public void pause();
}
public class AnimatableLifeCycleAdapter<T> implements AnimatableLifeCycleListener<T> {
#Override
public void animationStopped(Animatable<T> animator) {
}
#Override
public void animationCompleted(Animatable<T> animator) {
}
#Override
public void animationStarted(Animatable<T> animator) {
}
#Override
public void animationPaused(Animatable<T> animator) {
}
}
public abstract class AbstractAnimatable<T> implements Animatable<T> {
private LocalDateTime startTime;
private Duration duration = Duration.ofSeconds(5);
private AnimatableListener<T> animatableListener;
private AnimatableLifeCycleListener<T> lifeCycleListener;
private Easement easement;
private double rawOffset;
public AbstractAnimatable(Duration duration, AnimatableListener<T> listener) {
this.animatableListener = listener;
this.duration = duration;
}
public AbstractAnimatable(Duration duration, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
this(duration, listener);
this.lifeCycleListener = lifeCycleListener;
}
public AbstractAnimatable(Duration duration, Easement easement, AnimatableListener<T> listener) {
this(duration, listener);
this.easement = easement;
}
public AbstractAnimatable(Duration duration, Easement easement, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
this(duration, easement, listener);
this.lifeCycleListener = lifeCycleListener;
}
public void setEasement(Easement easement) {
this.easement = easement;
}
#Override
public Easement getEasement() {
return easement;
}
public Duration getDuration() {
return duration;
}
protected void setDuration(Duration duration) {
this.duration = duration;
}
public double getCurrentProgress(double rawProgress) {
Easement easement = getEasement();
double progress = Math.min(1.0, Math.max(0.0, getRawProgress()));
if (easement != null) {
progress = easement.interpolate(progress);
}
return Math.min(1.0, Math.max(0.0, progress));
}
public double getRawProgress() {
if (startTime == null) {
return 0.0;
}
Duration duration = getDuration();
Duration runningTime = Duration.between(startTime, LocalDateTime.now());
double progress = rawOffset + (runningTime.toMillis() / (double) duration.toMillis());
return Math.min(1.0, Math.max(0.0, progress));
}
#Override
public void tick() {
if (startTime == null) {
startTime = LocalDateTime.now();
fireAnimationStarted();
}
double rawProgress = getRawProgress();
double progress = getCurrentProgress(rawProgress);
if (rawProgress >= 1.0) {
progress = 1.0;
}
tick(progress);
fireAnimationChanged();
if (rawProgress >= 1.0) {
fireAnimationCompleted();
}
}
protected abstract void tick(double progress);
#Override
public void start() {
if (startTime != null) {
// Restart?
return;
}
Animator.INSTANCE.add(this);
}
#Override
public void stop() {
stopWithNotitifcation(true);
}
#Override
public void pause() {
rawOffset += getRawProgress();
stopWithNotitifcation(false);
double remainingProgress = 1.0 - rawOffset;
Duration remainingTime = getDuration().minusMillis((long) remainingProgress);
setDuration(remainingTime);
lifeCycleListener.animationStopped(this);
}
protected void fireAnimationChanged() {
if (animatableListener == null) {
return;
}
animatableListener.animationChanged(this);
}
protected void fireAnimationCompleted() {
stopWithNotitifcation(false);
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationCompleted(this);
}
protected void fireAnimationStarted() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStarted(this);
}
protected void fireAnimationPaused() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationPaused(this);
}
protected void stopWithNotitifcation(boolean notify) {
Animator.INSTANCE.remove(this);
startTime = null;
if (notify) {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStopped(this);
}
}
}
public abstract class AbstractAnimatableRange<T> extends AbstractAnimatable<T> {
private Range<T> range;
private T value;
public AbstractAnimatableRange(Range<T> range, Duration duration, AnimatableListener<T> listener) {
super(duration, listener);
this.range = range;
}
public AbstractAnimatableRange(Range<T> range, Duration duration, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
super(duration, listener, lifeCycleListener);
this.range = range;
}
public AbstractAnimatableRange(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener) {
super(duration, easement, listener);
this.range = range;
}
public AbstractAnimatableRange(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
super(duration, easement, listener, lifeCycleListener);
this.range = range;
}
protected void tick(double progress) {
setValue(range.valueAt(progress));
}
protected void setValue(T value) {
this.value = value;
}
#Override
public T getValue() {
return value;
}
}
/*
Easement, complicated, but fun
*/
public enum Easement {
SLOWINSLOWOUT(1d, 0d, 0d, 1d), FASTINSLOWOUT(0d, 0d, 1d, 1d), SLOWINFASTOUT(0d, 1d, 0d, 0d), SLOWIN(1d, 0d, 1d, 1d), SLOWOUT(0d, 0d, 0d, 1d);
private final double[] points;
private final List<PointUnit> normalisedCurve;
private Easement(double x1, double y1, double x2, double y2) {
points = new double[]{x1, y1, x2, y2};
final List<Double> baseLengths = new ArrayList<>();
double prevX = 0;
double prevY = 0;
double cumulativeLength = 0;
for (double t = 0; t <= 1; t += 0.01) {
Point2D xy = getXY(t);
double length = cumulativeLength + Math.sqrt((xy.getX() - prevX) * (xy.getX() - prevX) + (xy.getY() - prevY) * (xy.getY() - prevY));
baseLengths.add(length);
cumulativeLength = length;
prevX = xy.getX();
prevY = xy.getY();
}
normalisedCurve = new ArrayList<>(baseLengths.size());
int index = 0;
for (double t = 0; t <= 1; t += 0.01) {
double length = baseLengths.get(index++);
double normalLength = length / cumulativeLength;
normalisedCurve.add(new PointUnit(t, normalLength));
}
}
public double interpolate(double fraction) {
int low = 1;
int high = normalisedCurve.size() - 1;
int mid = 0;
while (low <= high) {
mid = (low + high) / 2;
if (fraction > normalisedCurve.get(mid).getPoint()) {
low = mid + 1;
} else if (mid > 0 && fraction < normalisedCurve.get(mid - 1).getPoint()) {
high = mid - 1;
} else {
break;
}
}
/*
* The answer lies between the "mid" item and its predecessor.
*/
final PointUnit prevItem = normalisedCurve.get(mid - 1);
final double prevFraction = prevItem.getPoint();
final double prevT = prevItem.getDistance();
final PointUnit item = normalisedCurve.get(mid);
final double proportion = (fraction - prevFraction) / (item.getPoint() - prevFraction);
final double interpolatedT = prevT + (proportion * (item.getDistance() - prevT));
return getY(interpolatedT);
}
protected Point2D getXY(double t) {
final double invT = 1 - t;
final double b1 = 3 * t * invT * invT;
final double b2 = 3 * t * t * invT;
final double b3 = t * t * t;
final Point2D xy = new Point2D.Double((b1 * points[0]) + (b2 * points[2]) + b3, (b1 * points[1]) + (b2 * points[3]) + b3);
return xy;
}
protected double getY(double t) {
final double invT = 1 - t;
final double b1 = 3 * t * invT * invT;
final double b2 = 3 * t * t * invT;
final double b3 = t * t * t;
return (b1 * points[2]) + (b2 * points[3]) + b3;
}
protected class PointUnit {
private final double distance;
private final double point;
public PointUnit(double distance, double point) {
this.distance = distance;
this.point = point;
}
public double getDistance() {
return distance;
}
public double getPoint() {
return point;
}
}
}
/**** Core Animation Engine ****/
public enum Animator {
INSTANCE;
private Timer timer;
private List<Animatable> properies;
private Animator() {
properies = new ArrayList<>(5);
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
List<Animatable> copy = new ArrayList<>(properies);
Iterator<Animatable> it = copy.iterator();
while (it.hasNext()) {
Animatable ap = it.next();
ap.tick();
}
if (properies.isEmpty()) {
timer.stop();
}
}
});
}
public void add(Animatable ap) {
properies.add(ap);
timer.start();
}
protected void removeAll(List<Animatable> completed) {
properies.removeAll(completed);
}
public void remove(Animatable ap) {
properies.remove(ap);
if (properies.isEmpty()) {
timer.stop();
}
}
}
}

Strange behavior of drawing image in java

I am making an animation by drawing different images dozens of milliseconds after the former one. something like this:
drawimage(image1,0,0,null);
try {
Thread.sleep(100);
}catch(Exception e){//To do something
}
drawimage(image2,0,0,null);
But the first image doesn't show until the second one appear. That means they appear at the same time.
My question is why does this happen?
Me: Where exactly is this code? Is it in a paint/paintComponent method?
OP: it is in paintComponent. I use it to make animation, but I am not sure it is a good way.
You're right it isn't a good way. Don't ever call Thread.sleep in the paintComponent method. I would avoid the Thread.sleep all together and use a javax.swing.Timer. See more How to Use Swing Timers
See examples here and here and here and here.
You could...
Use a list of Images and every iteration firing of the Timer event, add another Image to the List<Image> and call repaint()
You could...
Have an MyImage object class that has an Image field and a boolean draw field. In the Timer, loop through the MyImage objects and do something like
for (MyImage image: images) {
if (!image.isDraw()) {
image.setDraw(true);
break;
}
}
repaint();
For the MyImage List just loop through them in the paintComponent method and call it drawImage method, that you create.
Run this exmaple, showing the first option
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.URL;
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.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class AnimateImages extends JPanel {
private static final int IMAGE_ROWS = 10;
private static final int IMAGE_COLS = 10;
private static final int IMAGE_SIZE = 50;
private static final int DIM_WIDTH = IMAGE_COLS * IMAGE_SIZE;
private final List<MyImage> images;
private Image image;
private int currX = -IMAGE_SIZE;
private int currY;
public AnimateImages() {
try {
image = ImageIO.read(new URL("http://swoo.co.uk/content/images/icons/stackoverflow.png"));
} catch (IOException ex) {
Logger.getLogger(AnimateImages.class.getName()).log(Level.SEVERE, null, ex);
}
images = createImages();
Timer timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (MyImage image : images) {
if (!image.isDraw()) {
image.setDraw(true);
break;
}
repaint();
}
}
});
timer.start();
}
private List<MyImage> createImages() {
List<MyImage> list = new ArrayList<>();
for (int i = 0; i < IMAGE_ROWS * IMAGE_COLS; i++) {
if (currX >= DIM_WIDTH) {
currX = 0;
currY += IMAGE_SIZE;
} else {
currX += IMAGE_SIZE;
}
list.add(new MyImage(image, currX, currY));
}
return list;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (MyImage img : images) {
img.draw(g);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(IMAGE_SIZE * IMAGE_COLS, IMAGE_SIZE * IMAGE_ROWS);
}
public class MyImage {
Image image;
int x, y;
boolean draw = false;
public MyImage(Image image, int x, int y) {
this.image = image;
this.x = x;
this.y = y;
}
public void setDraw(boolean draw) {
this.draw = draw;
}
public boolean isDraw() {
return draw;
}
public void draw(Graphics g) {
if (draw) {
g.drawImage(image, x, y, IMAGE_SIZE, IMAGE_SIZE, AnimateImages.this);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new AnimateImages());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
The program, in its current state, draws two images at the same time because there are two draw methods in the loop (if you are indeed using a loop).
In order to solve this issue, you should use only a single draw method and leave the delay in the same place. However, in order to cycle through all of the different image variables (if you have them named as numbers 1,2,3,4...etc in an array) you can use a for loop to draw them:
for (int i = 0; i< *however many images you have*; i++){
drawimage(image[i],0,0,null);
try {
Thread.sleep(100);
}catch(Exception e){//To do something
}
}
Edit
You do not use delays inside the paintComponent. Since you are, that is what is likely causing the issues. Move the delay into the main method of your program.
Your answer
You're getting this result because it is printing image1, waiting .1 seconds and then printing image2. Then it doesn't wait at all, the game is immediately updated and then prints image1. Your display time for image2 is microscopic and you're likely only seeing image1 as a result.
Furthermore, the images are never deleted or replaced and so you are constantly drawing images on top of each other which is causing a memory leak, and is likely the cause of you seeing both images.
Considerations
This is a handy little animation class that I got, and still use, from this tutorial. This class is pretty handy and you can add the animated sprites necessary for each animation.
With this class, when you do a drawImage() you can use object.getImage() to extract the current frame of the animation.
Just make sure that you're calling the animation's update() method in your game's main loop so that the animations are constantly being updated.
import java.awt.Image;
import java.util.ArrayList;
public class Animation {
private ArrayList frames;
private int currentFrame;
private long animTime;
private long totalDuration;
public Animation() {
frames = new ArrayList();
totalDuration = 0;
synchronized (this) {
animTime = 0;
currentFrame = 0;
}
}
public synchronized void addFrame(Image image, long duration) {
totalDuration += duration;
frames.add(new AnimFrame(image, totalDuration));
}
public synchronized void update(long elapsedTime) {
if (frames.size() > 1) {
animTime += elapsedTime;
if (animTime >= totalDuration) {
animTime = animTime % totalDuration;
currentFrame = 0;
}
while (animTime > getFrame(currentFrame).endTime) {
currentFrame++;
}
}
}
public synchronized Image getImage() {
if (frames.size() == 0) {
return null;
} else {
return getFrame(currentFrame).image;
}
}
private AnimFrame getFrame(int i) {
return (AnimFrame) frames.get(i);
}
private class AnimFrame {
Image image;
long endTime;
public AnimFrame(Image image, long endTime) {
this.image = image;
this.endTime = endTime;
}
}
}

Pulsing image-button in Java

I have created a simple musical metronome in Java. It starts and stops by pressing a button that has the shape of a butterfly. I would love to add a visual effect of the tempo by making the button/butterfly appear and disappear together with the metronome beat.
Would java.util.Timer be the way to go? Would that work with an image that is a button and that needs to keep its functions while pulsing?
Thank you so very much for suggestions and congratulations on the community.
Would java.util.Timer be the way to go?
Yes, this could easily be used to display a pulsating image. When I've done this before, I've created an array of sine wave constants in my constructor and have used them to set an alpha composite inside of the timer.
Would that work with an image that is a button and that needs to keep its functions while pulsing?
It's possible although a little more difficult since the button doesn't really render itself, but rather its componentUI, here one of the subclasses of the BasicButtonUI.
Well, I can do it without messing with the componentUI, but I'm not sure if this is the correct way:
import java.awt.AlphaComposite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
#SuppressWarnings("serial")
public class PulsingButton extends JPanel {
private static final int MAX_ALPHAS = 60;
private float alpha = 1.0f;
private JSpinner beatsPerMinSpinner = new JSpinner(new SpinnerNumberModel(60, 30, 120, 1));
private JButton button = new JButton("Button") {
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setComposite(((AlphaComposite)g2.getComposite()).derive(alpha));
super.paintComponent(g2);
};
};
private float[] alphas = new float[MAX_ALPHAS];
private Timer timer;
public PulsingButton() {
beatsPerMinSpinner.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
int value = ((Integer) beatsPerMinSpinner.getValue()).intValue();
setTimerDelay(value);
}
});
add(new JLabel("Beats Per Minute:"));
add(beatsPerMinSpinner);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Hello!");
}
});
add(button);
for (int i = 0; i < alphas.length; i++) {
double theta = (Math.PI * 2 * i) / alphas.length;
alphas[i] = (float) ((Math.cos(theta) + 1) / 2.0);
}
int bpm = ((Integer) beatsPerMinSpinner.getValue()).intValue();
timer = new Timer(setTimerDelay(bpm), new TimerListener());
timer.start();
System.out.println(setTimerDelay(bpm) + "");
}
private int setTimerDelay(int bpm) {
int milisecondsInMinute = 60 * 1000;
int delay = milisecondsInMinute / (bpm * alphas.length);
if (timer != null) {
timer.setDelay(delay);
}
return delay;
}
private class TimerListener implements ActionListener {
int index = 0;
#Override
public void actionPerformed(ActionEvent arg0) {
alpha = alphas[index];
index++;
index %= alphas.length;
repaint();
}
}
private static void createAndShowGui() {
PulsingButton mainPanel = new PulsingButton();
JFrame frame = new JFrame("PulsingButton");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

A Java Scroll Indicator with floating scrollbars which only appears during scrolling

I need a scroll pane with scrollbars only visible during scroll events or when needed to pull.
Something that looks like the scroll bars on the nowadays smartphones.
I searched a lot but found only realizations in javascript.
So I tried it by myself.
Has anyone a better solution or hints?
The animation classes can be found here: TimingFrameWork
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.plaf.ScrollBarUI;
import javax.swing.plaf.basic.BasicScrollBarUI;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.KeyFrames;
import org.jdesktop.animation.timing.interpolation.KeyTimes;
import org.jdesktop.animation.timing.interpolation.KeyValues;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
/**
* A scrollpane like component, where the scroll bars are floating over the
* scrollable view to indicate the current scroll positions.
* The scroll indicators appear smoothly during scroll events and disappear
* smoothly afterwards.
* <p>
* The scrollbars can be dragged just as normal.</p>
* <p>
* The usage is similar to a classic scrollpane.</p>
*
* #author Jolly Littlebottom
*/
public class JScrollIndicator extends JLayeredPane {
private static final Color THUMB_COLOR = Color.DARK_GRAY;
private static final Color THUMB_BORDER_COLOR = Color.LIGHT_GRAY;
private static final float MAX_ALPHA = 0.8f;
private static final int THUMB_THICKNESS = 7;
private static final int THUMB_MIN_SIZE = 48;
private static final int THUMB_MARGIN = 3;
private static final int FADE_IN_TIME = 300;
private static final int STAY_TIME = 2000;
private static final int FADE_OUT_TIME = 1000;
private final JScrollPane scrollPane;
private final ControlPanel controlPanel;
/**
* Creates a <code>JScrollIndicator</code> that displays the contents of the
* specified component, where both horizontal and vertical scrollbars appear
* whenever the component's contents are larger than the view and scrolling
* in underway or the mouse is over the scrollbar position.
*
* #see #setViewportView
* #param view the component to display in the scrollpane's viewport
*/
public JScrollIndicator(final JComponent view) {
this(view, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
}
/**
* Creates a <code>JScrollIndicator</code> that displays the view component
* in a viewport whose view position can be controlled with a pair of
* scrollbars.
* The scrollbar policies specify when the scrollbars are displayed,
* For example, if <code>vsbPolicy</code> is
* <code>JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED</code>
* then the vertical scrollbar only appears if the view doesn't fit
* vertically. The available policy settings are listed at
* {#link #JScrollPane.setVerticalScrollBarPolicy} and
* {#link #JScrollPane.setHorizontalScrollBarPolicy}.
*
* #param view the component to display in the scrollpanes viewport
* #param vsbPolicy an integer that specifies the vertical scrollbar policy
* #param hsbPolicy an integer that specifies the horizontal scrollbar policy
*/
public JScrollIndicator(final JComponent view, int vsbPolicy, int hsbPolicy) {
scrollPane = new JScrollPane(view, vsbPolicy, hsbPolicy);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
add(scrollPane, JLayeredPane.DEFAULT_LAYER);
controlPanel = new ControlPanel(scrollPane);
add(controlPanel, JLayeredPane.PALETTE_LAYER);
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
// listen to changes of JLayeredPane size
scrollPane.setSize(getSize());
scrollPane.getViewport().revalidate();
controlPanel.setSize(getSize());
controlPanel.revalidate();
}
});
}
/**
* Returns the scroll pane used by this scroll indicator.
* Use carefully (e.g. to set unit increments) because not all changes have an
* effect. You have to write listeners in this cases (e.g. for changing the
* scrollbar policy)
*
* #return
*/
public JScrollPane getScrollPane() {
return scrollPane;
}
private class ControlPanel extends JPanel {
private final JMyScrollBar vScrollBar;
private final JMyScrollBar hScrollBar;
private ControlPanel(JScrollPane scrollPane) {
setLayout(new BorderLayout());
setOpaque(false);
vScrollBar = new JMyScrollBar(JScrollBar.VERTICAL);
scrollPane.setVerticalScrollBar(vScrollBar);
scrollPane.remove(vScrollBar);
if (scrollPane.getVerticalScrollBarPolicy() != JScrollPane.VERTICAL_SCROLLBAR_NEVER) {
add(vScrollBar, BorderLayout.EAST);
}
hScrollBar = new JMyScrollBar(JScrollBar.HORIZONTAL);
scrollPane.setHorizontalScrollBar(hScrollBar);
scrollPane.remove(hScrollBar);
if (scrollPane.getHorizontalScrollBarPolicy() != JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) {
add(hScrollBar, BorderLayout.SOUTH);
}
}
}
private class JMyScrollBar extends JScrollBar {
protected final MyScrollBarUI scrollUI;
public JMyScrollBar(int direction) {
super(direction);
scrollUI = new MyScrollBarUI(this);
super.setUI(scrollUI);
int size = THUMB_THICKNESS + THUMB_MARGIN;
setPreferredSize(new Dimension(size, size));
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
scrollUI.setVisible(true, true);
}
#Override
public void mouseExited(MouseEvent e) {
scrollUI.setVisible(false, false);
}
});
addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
scrollUI.setVisible(true, false);
}
});
}
#Override
public void setUI(ScrollBarUI ui) {
}
#Override
public void updateUI() {
}
#Override
public void paint(Graphics g) {
scrollUI.paintThumb(g, this); // just the thumb
}
#Override
public void repaint(Rectangle r) {
JScrollIndicator scrollIndicator = JScrollIndicator.this;
Rectangle rect = SwingUtilities.convertRectangle(this, r, scrollIndicator);
rect.grow(1, 1);
// ensure for a translucent thumb, that the view is first painted
scrollIndicator.repaint(rect);
}
}
public class MyScrollBarUI extends BasicScrollBarUI {
private JMyScrollBar scrollBar;
private float alpha = 0.0f;
private FadeAnimation fadeAnimation;
private MyScrollBarUI(JMyScrollBar scrollBar) {
this.scrollBar = scrollBar;
fadeAnimation = new FadeAnimation(this);
}
#Override
protected void installComponents() {
incrButton = new JButton();
decrButton = new JButton();
if (scrollBar.getOrientation() == JScrollBar.HORIZONTAL) {
int size = THUMB_THICKNESS + THUMB_MARGIN; // let lower right corner empty
incrButton.setPreferredSize(new Dimension(size, size));
}
else {
incrButton.setPreferredSize(new Dimension(THUMB_MARGIN, THUMB_MARGIN));
}
decrButton.setPreferredSize(new Dimension(THUMB_MARGIN, THUMB_MARGIN));
}
#Override
protected void installDefaults() {
super.installDefaults();
// ensure the minimum size of the thumb
int w = minimumThumbSize.width;
int h = minimumThumbSize.height;
if (scrollBar.getOrientation() == JScrollBar.VERTICAL) {
h = Math.max(h, Math.min(maximumThumbSize.height, THUMB_MIN_SIZE));
}
else {
w = Math.max(w, Math.min(maximumThumbSize.width, THUMB_MIN_SIZE));
}
minimumThumbSize = new Dimension(w, h);
}
private void paintThumb(Graphics g, JComponent c) {
if (alpha == 0.0f) {
return; // don't paint anything
}
g.setColor(getAlphaColor(THUMB_COLOR));
int radius = THUMB_THICKNESS >>> 1; // half width
Rectangle thumbBounds = getThumbBounds();
int x = thumbBounds.x;
int y = thumbBounds.y;
int w = thumbBounds.width;
int h = thumbBounds.height;
if (scrollBar.getOrientation() == JScrollBar.VERTICAL) {
w -= THUMB_MARGIN;
}
else {
h -= THUMB_MARGIN;
}
g.fillRoundRect(x, y, w, h, radius, radius);
g.setColor(getAlphaColor(THUMB_BORDER_COLOR));
g.drawRoundRect(x, y, w, h, radius, radius);
}
private Color getAlphaColor(Color color) {
if (alpha == 1.0f) {
return color;
}
int rgb = color.getRGB() & 0xFFFFFF; // color without alpha values
rgb |= ((int)(alpha*255)) << 24; // add alpha value
return new Color(rgb, true);
}
public void setAlpha(float alpha) {
this.alpha = alpha;
scrollBar.repaint(getThumbBounds());
}
public void setVisible(boolean visible, boolean mouseOver) {
if (visible) {
fadeAnimation.fadeIn(mouseOver);
}
else {
fadeAnimation.fadeOut();
}
scrollBar.repaint(getThumbBounds());
}
}
private class FadeAnimation {
private final MyScrollBarUI scrollUI;
private Animator fadeAnimator;
private Timer fadeOutTimer;
private boolean isFadeIn;
private FadeAnimation(MyScrollBarUI scrollUI) {
this.scrollUI = scrollUI;
}
public synchronized void fadeIn(boolean mouseOver) {
if (mouseOver) {
cancelTimer();
}
if (!isFadeIn) {
isFadeIn = true;
cancelAnimationAndTimer();
fadeAnimator = PropertySetter.createAnimator(
FADE_IN_TIME, scrollUI, "alpha",
new KeyFrames(KeyValues.create(scrollUI.alpha, MAX_ALPHA),
new KeyTimes(0.0f, 1.0f)));
fadeAnimator.start();
if (!mouseOver) {
fadeOutTimer = new Timer(FADE_IN_TIME + STAY_TIME,
new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
FadeAnimation.this.fadeOut();
}
});
fadeOutTimer.start();
}
}
}
public synchronized void fadeOut() {
cancelAnimationAndTimer();
if (isFadeIn) {
isFadeIn = false;
fadeAnimator = PropertySetter.createAnimator(
FADE_OUT_TIME, scrollUI, "alpha",
new KeyFrames(KeyValues.create(scrollUI.alpha, 0.0f),
new KeyTimes(0.0f, 1.0f)));
fadeAnimator.start();
}
}
private void cancelAnimationAndTimer() {
if (fadeAnimator != null && fadeAnimator.isRunning()) {
fadeAnimator.cancel();
fadeAnimator = null;
}
cancelTimer();
}
private void cancelTimer() {
if (fadeOutTimer != null) {
fadeOutTimer.stop();
fadeOutTimer = null;
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String text = "";
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
text += i + " - " + j;
}
text += "\n";
}
JTextArea area = new JTextArea(text);
JScrollIndicator scrollIndicator = new JScrollIndicator(area);
frame.getContentPane().add(scrollIndicator);
frame.setBounds(100, 100, 200, 300);
frame.setVisible(true);
}
});
}
}

Categories

Resources