I have a class called PopUp, which is a JPanel that, when activated, expands to a given size and location based on the parameters given from the center and contracts the same way when clicked.
For some reason, when the PopUp is expanding and contracting, the animations sharing the same JPanel speed up. I've witnessed this on the two programs I've used my PopUp class on.
Here is what I believe the relevant code is:
/**
* The {@code PopUp} class is a JPanel that expands to the rectangle
* created from the given x, y, width and height that expands from
* the center of the rectangle.
* <p>
* Here is an example of how the {@code PopUp} object can be initialized:
* <blockquote><pre>
* PopUp pu = new PopUp(25, 25, 575, 575, 25, Color.GRAY);
* </pre></blockquote>
* <p>
* The class {@code PopUp} includes methods for drawing the pop-up;
* choosing whether the pop-up is expanding or not; getting the
* percentage that the pop-up is expanded; and getting the maximum x, y,
* width, and height
*
* @author Gigi Bayte 2
*/
public class PopUp extends JPanel implements MouseListener {
private static final long serialVersionUID = 1L;
/**
* Expanded x coordinate
*/
private double x;
/**
* Expanded y coordinate
*/
private double y;
/**
* Expanded width value
*/
private double width;
/**
* Expanded height value
*/
private double height;
/**
* Number of steps until fully expanded
*/
private int steps;
/**
* This divided by steps is the percentage the pop-up is expanded
*/
private int expansionStage = 0;
/**
* Whether or not the pop-up is expansing
*/
private boolean isExpanding = false;
/**
* Color of the pop-up
*/
private Color color;
/**
* The rectangle that represents the bounds of the pop-up
*/
private Rectangle2D popUp;
/**
* Initializes a newly created {@code PopUp} with a uniform color
* @param x The x coordinate of the expanded pop-up
* @param y The y coordinate of the expanded pop-up
* @param w The width of the expanded pop-up
* @param h The height of the expanded pop-up
* @param expansionSteps The number of steps until fully expanded
* @param popUpColor The color of the pop-up
*/
public PopUp(double x, double y, double w, double h, int expansionSteps, Color popUpColor) {
this.x = x;
this.y = y;
width = w;
height = h;
color = popUpColor;
steps = expansionSteps;
popUp = new Rectangle2D.Double(0, 0, width, height);
addMouseListener(this);
}
/**
* Draws the pop-up
* @param g Graphics object from paintComponent
*/
public final void draw(Graphics g) {
if(isExpanding)
expansionStage = Math.min(expansionStage + 1, steps);
else
expansionStage = Math.max(expansionStage - 1, 0);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
AffineTransform trans = new AffineTransform();
trans.translate(x + width/2 * (1 - (double) expansionStage/steps), y + height/2 * (1 - (double) expansionStage/steps));
trans.scale((double) expansionStage/steps, (double) expansionStage/steps);
setBounds((int) trans.getTranslateX(), (int) trans.getTranslateY(), (int) (width * expansionStage/steps), (int) (height * expansionStage/steps));
g2d.setColor(color);
Shape transformed = trans.createTransformedShape(popUp);
g2d.fill(transformed);
}
/**
* Sets whether the pop-up is expanding or not
* @param expanding Whether or not the pop-up should expand
*/
public final void setExpanding(boolean expanding) {
isExpanding = expanding;
}
@Override
public final void mouseClicked(MouseEvent e) {
isExpanding = false;
}
}
Here is a test class to run:
public class Test extends JPanel implements ActionListener, MouseListener {
private static final long serialVersionUID = 1L;
private static PopUp popUp;
private int stringX = 610;
private int stringCounter = 0;
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(600, 600);
Test t = new Test();
t.setBounds(0, 0, 600, 600);
frame.add(t);
t.setVisible(true);
Timer timer = new Timer(5, t);
popUp = new PopUp(100, 100, 400, 400, 100, Color.WHITE);
frame.add(popUp);
popUp.setVisible(true);
timer.start();
frame.addMouseListener(t);
frame.setLayout(null);
frame.setUndecorated(true);
frame.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
repaint();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, 600, 600);
popUp.draw(g);
g.setColor(Color.WHITE);
g.drawString("This is a test", stringX, 580);
if(++stringCounter % 3 == 0) {
--stringX;
stringCounter = 0;
}
if(stringX == -10 - g.getFontMetrics().stringWidth("This is a test"))
stringX = 610;
}
@Override
public void mouseClicked(MouseEvent e) {
popUp.setExpanding(!popUp.getExpanding());
}
@Override
public void mousePressed(MouseEvent e) {}
@Override
public void mouseReleased(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
}
As can be seen in the above example, the text scrolling from right to left speeds up every time the pop-up is expanding or contracting.
That is the expected behavior when receiving repeated update events that invoke paintComponent()
; resize this AnimationTest
to reproduce the effect.
Why exactly do the repeated update events cause this? What's the logic behind it?
Each call to setBounds()
in your draw()
method "invalidates the component hierarchy." The Component
API ensures that
When the hierarchy gets invalidated, like after changing the bounds of components, or adding/removing components to/from containers, the whole hierarchy must be validated afterwards by means of the
Container.validate()
method invoked on the top-most invalid container of the hierarchy.
Because the validate()
method "may be a quite time-consuming operation," you can "postpone the validation of the hierarchy till a set of layout-related operations completes," as you show here; or you can pace the animation with a javax.swing.Timer
, illustrated here.