PopUp Class 似乎可以加快动画速度

PopUp Class Seems To Speed Up Animations

我有一个名为 PopUp 的 class,它是一个 JPanel,当它被激活时,会根据中心给定的参数扩展到给定的大小和位置,并在单击时以相同的方式收缩。

由于某些原因,当 PopUp 展开和收缩时,共享同一 JPanel 的动画会加速。我在我使用 PopUp class 的两个程序中看到了这一点。

我认为相关代码如下:

/**
 * 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;
    }
}

这是一个测试 class 到 运行:

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) {}

}

从上面的例子可以看出,每次弹出窗口展开或收缩时,文本从右向左滚动的速度都会加快。

嗯,我找到问题了。罪魁祸首是这里的行:

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

显然,由于我不知道的原因,快速连续地将 JPanel 缩放到不同的大小会导致一些速度扭曲。我希望能对这个答案进行编辑,并对这种现象做出更好的解释。

我只是为 JPanel 设置了静态大小,剩下的工作由图形完成。

这是接收到调用 paintComponent() 的重复更新事件时的预期行为;调整大小 AnimationTest 以重现效果。

Why exactly do the repeated update events cause this? What's the logic behind it?

每次调用 setBounds() in your draw() method "invalidates the component hierarchy." The Component API 确保

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.

因为 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 ; or you can pace the animation with a javax.swing.Timer, illustrated here.