1

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.

Gigi Bayte 2
  • 838
  • 1
  • 8
  • 20
  • 1
    That is the expected behavior when receiving repeated update events that invoke `paintComponent()`; resize this [`AnimaitonTest`](https://stackoverflow.com/a/3256941/230513) to reproduce the effect; for more details, please [edit] your question to include a [mcve] that exhibits the problem you describe. – trashgod Oct 27 '17 at 17:58
  • Done! @trashgod – Gigi Bayte 2 Oct 29 '17 at 18:20
  • Sorry, no time to fill in the missing pieces. When is your `ActionListener` called? See also [*Initial Threads*](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html). – trashgod Oct 30 '17 at 13:06
  • It is called whenever the timer ticks. I'll look into the link you sent. @trashgod – Gigi Bayte 2 Oct 30 '17 at 13:38
  • @trashgod Perhaps you could help me fill in the missing pieces to my answer? – Gigi Bayte 2 Nov 07 '17 at 06:33

2 Answers2

2

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.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
1

Well, I found the problem. The culprit was the line here:

setBounds((int) trans.getTranslateX(), (int) trans.getTranslateY(), (int) (width * expansionStage/steps), (int) (height * expansionStage/steps));

Apparently scaling a JPanel to different sizes in rapid succession causes some speed warping for reasons I do not know. I'd appreciate an edit to this answer with a better explanation for this phenomenon.

I just set a static size for the JPanel and had the graphics do the rest of the work.

Gigi Bayte 2
  • 838
  • 1
  • 8
  • 20
  • As noted [here](https://stackoverflow.com/questions/46980415/popup-class-seems-to-speed-up-animations?noredirect=1#comment80909289_46980415), "repeated update events…invoke `paintComponent()`." – trashgod Nov 07 '17 at 14:20
  • The problem was resizing the JPanel in repeatedly. Once the line above was removed, I still had a Timer repeatedly calling paintComponent(), and it worked fine. @trashgod – Gigi Bayte 2 Nov 07 '17 at 22:31
  • The same effect is seen while resizing the `AnimationTest` cited above. – trashgod Nov 07 '17 at 23:47