Reputation: 29
I'd like to do JButton with nice transition effect. I write a class which extend by JButton and add to it custom MouseAdapter. It almost works, but if opacity should have 0 my one BufferedImage don't vanish.
Here my all source code:
public class ImageHoverButton extends JButton {
public class MouseListener extends MouseAdapter
{
public void mouseExited(MouseEvent me)
{
new Thread(new Runnable()
{
public void run()
{
for (float i = 1f; i >= 0f; i -= .03f)
{
setOpacity(i);
try
{
Thread.sleep(10);
}
catch (Exception e)
{
}
}
}
}).start();
}
public void mouseEntered(MouseEvent me)
{
new Thread(new Runnable()
{
public void run()
{
for (float i = 0f; i <= 1f; i += .03f)
{
setOpacity(i);
try
{
Thread.sleep(10);
}
catch (Exception e)
{
}
}
}
}).start();
}
public void mousePressed(MouseEvent me)
{
new Thread(new Runnable()
{
public void run()
{
for (float i = 1f; i >= 0.6f; i -= .1f)
{
setOpacity(i);
try
{
Thread.sleep(1);
}
catch (Exception e)
{
}
}
}
}).start();
}
}
private static final long serialVersionUID = 1L;
private BufferedImage imgBottom;
private BufferedImage imgHover;
private BufferedImage imgHoverRGB;
// filter to imgInActive
float[] scales = { 1f, 1f, 1f, 0f};
float[] offsets = new float[4];
RescaleOp rop = new RescaleOp(scales, offsets, null);
/**
* Constructor for image path
* @param img
* @param x
* @param y
*/
public ImageHoverButton(String imgBottomPath, String imgHoverPath, int x, int y) {
try {
this.imgBottom = ImageIO.read(new File(imgBottomPath));
this.imgHover = ImageIO.read(new File(imgHoverPath));
imgHoverRGB = new BufferedImage(imgHover.getWidth(null),
imgHover.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
Graphics g = imgHoverRGB.getGraphics();
g.drawImage(imgHover, 0, 0, null);
} catch (IOException e) {
}
this.setBounds(x, y, imgBottom.getWidth() + 40 , imgBottom.getHeight() + 50);
addMouseListener(new MouseListener());
setOpacity(0f);
setOpaque(false);
setBorderPainted(false);
setRolloverEnabled(false);
setCursor(new Cursor(Cursor.HAND_CURSOR));
setLayout(null);
}
public void setOpacity(float opacity) {
scales[3] = opacity;
rop = new RescaleOp(scales, offsets, null);
repaint();
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.drawImage(imgBottom, 50, 50, null);
g2d.drawImage(imgHoverRGB, rop, 0, 0);
}
}
Have any idea how to improve this?
Upvotes: 0
Views: 1537
Reputation: 54709
I'm not so familiar with RescaleOp
, and can't remember having used this before. But it seems like the results of applying it in this case are somewhat unexpected.
As an alternative, you might consider an AlphaComposite
. The minimum modification that is necessary to achieve the desired effect would then be to change the line
g2d.drawImage(imgHoverRGB, rop, 0, 0);
to
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, scales[3]));
g2d.drawImage(imgHoverRGB, 0, 0, null);
However, there are several other issues with the code:
paint
. Instead, override paintComponent
setBounds
on a component (particlularly not in a constructor). The placement should be done by a layout managergetPreferredSize
properlyI created an example showing one possible approach: It contains an OpacityAnimator
that allows a transition between two opacities, with a predefined delay in milliseconds. This animator is used to increase the opacity of the foreground image when the button is hovered with the mouse, and to decrease it when the mouse leaves the button.
(Note that this could be generalized further, and there are many possible "configuration settings" (like the transition delay) that could be exposed, but this is just intended as an example)
import java.awt.AlphaComposite;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
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 javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class HoverButtonTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
try
{
createAndShowGUI();
}
catch (IOException e)
{
e.printStackTrace();
}
}
});
}
private static void createAndShowGUI() throws IOException
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BufferedImage backgroundImage = loadImage("background.png");
BufferedImage foregroundImage = loadImage("foreground.png");
f.getContentPane().setLayout(new FlowLayout());
f.getContentPane().add(
new ImageHoverButton(backgroundImage, foregroundImage));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static BufferedImage loadImage(String path) throws IOException
{
return convertToARGB(ImageIO.read(new File(path)));
}
public static BufferedImage convertToARGB(BufferedImage image)
{
BufferedImage newImage = new BufferedImage(image.getWidth(),
image.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
}
class ImageHoverButton extends JButton
{
private class MouseHoverListener extends MouseAdapter
{
@Override
public void mouseExited(MouseEvent me)
{
opacityAnimator.changeOpacity(0.0f, 250);
}
@Override
public void mouseEntered(MouseEvent me)
{
opacityAnimator.changeOpacity(1.0f, 1000);
}
@Override
public void mousePressed(MouseEvent me)
{
opacityAnimator.changeOpacity(0.5f, 50);
}
}
private class OpacityAnimator
{
private final int DELAY_MS = 10;
private final Timer timer;
private float targetOpacity;
private float currentOpacity;
private float opacityStep;
OpacityAnimator()
{
timer = new Timer(DELAY_MS, new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
if (currentOpacity > targetOpacity)
{
currentOpacity += opacityStep;
currentOpacity = Math.max(
currentOpacity, targetOpacity);
}
else if (currentOpacity < targetOpacity)
{
currentOpacity += opacityStep;
currentOpacity = Math.min(
currentOpacity, targetOpacity);
}
if (currentOpacity == targetOpacity)
{
timer.stop();
}
setOpacity(currentOpacity);
}
});
}
void changeOpacity(float targetOpacity, int durationMs)
{
timer.stop();
this.targetOpacity = targetOpacity;
float delta = targetOpacity - currentOpacity;
if (durationMs > 0)
{
opacityStep = (delta / durationMs) * DELAY_MS;
}
else
{
opacityStep = delta;
}
timer.start();
}
}
private final OpacityAnimator opacityAnimator;
private final BufferedImage backgroundImage;
private final BufferedImage foregroundImage;
private float opacity = 0.0f;
public ImageHoverButton(BufferedImage backgroundImage,
BufferedImage foregroundImage)
{
this.backgroundImage = backgroundImage;
this.foregroundImage = foregroundImage;
this.opacityAnimator = new OpacityAnimator();
addMouseListener(new MouseHoverListener());
setOpaque(false);
setBorderPainted(false);
setRolloverEnabled(false);
setCursor(new Cursor(Cursor.HAND_CURSOR));
}
@Override
public Dimension getPreferredSize()
{
if (super.isPreferredSizeSet())
{
return super.getPreferredSize();
}
int w = Math
.max(backgroundImage.getWidth(), foregroundImage.getWidth());
int h = Math.max(backgroundImage.getHeight(),
foregroundImage.getHeight());
return new Dimension(w, h);
}
public void setOpacity(float opacity)
{
this.opacity = opacity;
repaint();
}
@Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.drawImage(backgroundImage, 0, 0, null);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
opacity));
g.drawImage(foregroundImage, 0, 0, null);
}
}
Upvotes: 2
Reputation: 3496
Don't access Swing components from other threads. Use a swing Timer instead. See How to use swing timers
Upvotes: 0