Reputation: 31
Updated Code:
package JAnimator;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.Timer;
public class JAnimator {
public static enum AnimationType {
FADE_ANIMATION,
ERASE_ANIMATION,
ROTATE_ANIMATION,
ZOOM_ANIMATION,
SLIDE_ANIMATION,
FLASH_ANIMATION
}
public fade_animation fadeobj;
private int AniType;
private final Point xytarget;
public JAnimator(JComponent Target_component, int Speed_in_miliseconds, int Delay_in_miliseconds, AnimationType Animation_type) {
xytarget = Target_component.getLocation();
switch (Animation_type) {
case FADE_ANIMATION:
fadeobj = new fade_animation(Target_component, Speed_in_miliseconds, Delay_in_miliseconds);
AniType = 1;
break;
}
}
public void playIn() {
if (AniType == 1) {
fadeobj.fadeIntoView();
}
}
public void playOut() {
if (AniType == 1) {
fadeobj.fadeOutOfView();
}
}
public class fade_animation extends JPanel {
private float opacity = 0.0f;
private Timer timer;
private JComponent target;
private boolean fadingIn;
private int Delay;
private fade_animation(JComponent targetComponent, int SpeedMilisec, int DelayMilisec) {
super.setLayout(null);
this.target = targetComponent;
this.Delay = DelayMilisec;
this.setSize(target.getSize());
this.setLayout(new BorderLayout());
add(target);
this.timer = new Timer(SpeedMilisec, (ActionEvent e) -> fade());
}
private void fade() {
if (fadingIn) {
opacity += 0.1f;
target.setEnabled(true);
if (opacity >= 1.0f) {
opacity = 1.0f;
timer.stop();
}
} else {
opacity -= 0.1f;
if (opacity <= 0.0f) {
opacity = 0.0f;
target.setEnabled(false);
timer.stop();
}
}
repaint();
getToolkit().sync();
}
public void fadeIntoView() {
EventQueue.invokeLater(() -> {
opacity = 0.0f;
fadingIn = true;
Timer delayTimer = new Timer(Delay, e -> timer.restart());
delayTimer.setRepeats(false);
delayTimer.start();
});
}
public void fadeOutOfView() {
EventQueue.invokeLater(() -> {
opacity = 1.0f;
fadingIn = false;
Timer delayTimer = new Timer(Delay, e -> timer.restart());
delayTimer.setRepeats(false);
delayTimer.start();
});
}
@Override
protected void paintChildren(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setComposite(AlphaComposite.SrcOver.derive(opacity));
super.paintChildren(g2);
g2.dispose();
}
}
}
Here I improoved my code by adding a wraper to the fade class called JAnimator where I can call diferent animations in the future. The problem is that when I call playOut();
or playIn();
It has that weird glitch where the button gets mooved and fades at 0,0 Instead of fading at the same location which is target.getLocation();
setting location at paint children seem to fix it though only for static values, when i use target location it keeps fading at xy 0,0.
Upvotes: 2
Views: 205
Reputation: 44414
You are trying to create a component which paints the contents of another component. To do this, your paintComponent method must actually make use of that other component. It will not somehow know to paint that component just because there is a reference to that component somewhere in your class.
One way to do that is with SwingUtilities.paintComponent, which is specifically designed to paint a component in an arbitrary Graphics.
SwingUtilities.paintComponent requires these arguments: a Graphics, the component to paint, a temporary parent container, and the rectangular area where painting will take place.
The temporary parent container is just that: an invisible parent (typically a JPanel) to which the component that will be painted can be added, long enough to make sure it is in a drawable state.
So, in the fade animation class, we will need these fields:
public final class Fader
extends JPanel {
@Serial
private static final long serialVersionUID = 1;
private final JComponent view;
private final JComponent parent;
private final Timer timer;
private float opacity;
private boolean fadingIn;
(Usually, it is better to extend JPanel for custom painting, rather than extending JComponent directly, as doing so will take care of some subtle behaviors of Swing painting.)
Then we can initialize them:
public Fader(JComponent target,
Duration frameDelay) {
Objects.requireNonNull(target, "Target component cannot be null.");
Objects.requireNonNull(frameDelay, "Delay cannot be null.");
this.target = target;
this.parent = new JPanel(new BorderLayout());
this.timer = new Timer((int) frameDelay.toMillis(), e -> fade());
}
That fade()
method is similar to what you have already written:
private void fade() {
if (fadingIn) {
opacity += 0.1f;
if (opacity >= 1) {
opacity = 1;
timer.stop();
}
} else {
opacity -= 0.1f;
if (opacity <= 0) {
opacity = 0;
timer.stop();
}
}
repaint();
getToolkit().sync();
}
(Calling Toolkit.sync() after frequent repaints will make your animation smoother.)
The methods to start the fade timer can be pretty short:
public void fadeIntoView() {
opacity = 0;
fadingIn = true;
timer.restart();
}
public void fadeOutOfView() {
opacity = 1;
fadingIn = false;
timer.restart();
}
Before we write a paintComponent method, we first have to make sure this custom component is large enough to display the target component. We do that by overriding its getPreferredSize() method:
@Override
public Dimension getPreferredSize() {
Dimension size = target.getPreferredSize();
// Account for border, if any.
Insets insets = getInsets();
size.width += insets.left + insets.right;
size.height += insets.top + insets.bottom;
return size;
}
Finally, we can write the paintComponent method:
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2 = (Graphics2D) g2.create();
g2.setComposite(AlphaComposite.SrcOver.derive(opacity));
SwingUtilities.calculateInnerArea(this, innerArea);
SwingUtilities.paintComponent(g2, target, parent, innerArea);
g2.dispose();
}
(AlphaComposite.SrcOver.derive(opacity) is a shorter equivalent to AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity).)
innerArea
is a Rectangle which holds the region of the custom component which is inside any Border which may have been added. We can declare innerArea
as another private field in the class:
private final Rectangle innerArea = new Rectangle();
Putting it all together looks something like this:
import java.io.Serial;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.Objects;
import javax.imageio.ImageIO;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.AlphaComposite;
import java.awt.Rectangle;
import java.awt.EventQueue;
import java.awt.Image;
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JFrame;
import javax.swing.ImageIcon;
import javax.swing.Timer;
import javax.swing.SwingUtilities;
import javax.swing.BorderFactory;
public final class Fader
extends JPanel {
@Serial
private static final long serialVersionUID = 1;
private final JComponent target;
private final JComponent parent;
private final Timer timer;
private final Rectangle innerArea = new Rectangle();
private float opacity;
private boolean fadingIn;
public Fader(JComponent target,
Duration frameDelay) {
Objects.requireNonNull(target, "Target component cannot be null.");
Objects.requireNonNull(frameDelay, "Delay cannot be null.");
this.target = target;
this.parent = new JPanel(new BorderLayout());
this.timer = new Timer((int) frameDelay.toMillis(), e -> fade());
}
@Override
public Dimension getPreferredSize() {
Dimension size = target.getPreferredSize();
// Account for border, if any.
Insets insets = getInsets();
size.width += insets.left + insets.right;
size.height += insets.top + insets.bottom;
return size;
}
private void fade() {
if (fadingIn) {
opacity += 0.1f;
if (opacity >= 1) {
opacity = 1;
timer.stop();
}
} else {
opacity -= 0.1f;
if (opacity <= 0) {
opacity = 0;
timer.stop();
}
}
repaint();
getToolkit().sync();
}
public void fadeIntoView() {
opacity = 0;
fadingIn = true;
timer.restart();
}
public void fadeOutOfView() {
opacity = 1;
fadingIn = false;
timer.restart();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2 = (Graphics2D) g2.create();
g2.setComposite(AlphaComposite.SrcOver.derive(opacity));
SwingUtilities.calculateInnerArea(this, innerArea);
SwingUtilities.paintComponent(g2, target, parent, innerArea);
g2.dispose();
}
public static void main(String[] args)
throws IOException {
URI imageLocation = URI.create(args.length > 0 ? args[0] :
"https://cdn.sstatic.net/Sites/stackoverflow" +
"/Img/apple-touch-icon.png");
Image image = ImageIO.read(imageLocation.toURL());
if (image == null) {
throw new RuntimeException("No image found at " + imageLocation);
}
EventQueue.invokeLater(() -> {
JLabel target = new JLabel(new ImageIcon(image));
Fader fader = new Fader(target, Duration.ofMillis(100));
fader.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
JButton fadeInButton = new JButton("Fade In");
fadeInButton.addActionListener(e -> fader.fadeIntoView());
JButton fadeOutButton = new JButton("Fade Out");
fadeOutButton.addActionListener(e -> fader.fadeOutOfView());
JPanel buttonPanel = new JPanel();
buttonPanel.add(fadeInButton);
buttonPanel.add(fadeOutButton);
JFrame frame = new JFrame("Fader");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(fader, BorderLayout.CENTER);
frame.getContentPane().add(buttonPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
}
Update:
You have provided the additional clarification that you want the target component to be interactive, not just painted.
For that, I would not use SwingUtilities.paintComponent. You don’t just want the likeness of the target component; you want it to be usable as it would be normally, but you also want to change its visual opacity.
You can accomplish that by overriding the paintChildren method of a JPanel:
import java.io.Serial;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.Objects;
import javax.imageio.ImageIO;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.AlphaComposite;
import java.awt.Image;
import java.awt.LayoutManager;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.BorderFactory;
import javax.swing.Timer;
import javax.swing.ImageIcon;
public final class Fader2
extends JPanel {
@Serial
private static final long serialVersionUID = 1;
private final JComponent target;
private final Timer timer;
private float opacity;
private boolean fadingIn;
public Fader2(JComponent target,
Duration frameDelay) {
super(new BorderLayout());
Objects.requireNonNull(target, "Target component cannot be null.");
Objects.requireNonNull(frameDelay, "Delay cannot be null.");
this.target = target;
add(target);
this.timer = new Timer((int) frameDelay.toMillis(), e -> fade());
target.setEnabled(false);
}
@Override
public void setLayout(LayoutManager manager) {
if (!(manager instanceof BorderLayout)) {
throw new IllegalArgumentException(
"Instances of this class must use a BorderLayout.");
}
super.setLayout(manager);
}
private void fade() {
if (fadingIn) {
opacity += 0.1f;
target.setEnabled(true);
if (opacity >= 1) {
opacity = 1;
timer.stop();
}
} else {
opacity -= 0.1f;
if (opacity <= 0) {
opacity = 0;
target.setEnabled(false);
timer.stop();
}
}
repaint();
getToolkit().sync();
}
public void fadeIntoView() {
opacity = 0;
fadingIn = true;
timer.restart();
}
public void fadeOutOfView() {
opacity = 1;
fadingIn = false;
timer.restart();
}
@Override
protected void paintChildren(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setComposite(AlphaComposite.SrcOver.derive(opacity));
super.paintChildren(g2);
g2.dispose();
}
public static void main(String[] args)
throws IOException {
URI imageLocation = URI.create(args.length > 0 ? args[0] :
"https://cdn.sstatic.net/Sites/stackoverflow" +
"/Img/apple-touch-icon.png");
Image image = ImageIO.read(imageLocation.toURL());
if (image == null) {
throw new RuntimeException("No image found at " + imageLocation);
}
EventQueue.invokeLater(() -> {
JButton target = new JButton(new ImageIcon(image));
Fader2 fader = new Fader2(target, Duration.ofMillis(100));
fader.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
JButton fadeInButton = new JButton("Fade In");
fadeInButton.addActionListener(e -> fader.fadeIntoView());
JButton fadeOutButton = new JButton("Fade Out");
fadeOutButton.addActionListener(e -> fader.fadeOutOfView());
JPanel buttonPanel = new JPanel();
buttonPanel.add(fadeInButton);
buttonPanel.add(fadeOutButton);
JFrame frame = new JFrame("Fader");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(fader, BorderLayout.CENTER);
frame.getContentPane().add(buttonPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
}
Upvotes: 2