使用计时器实现JPanel的动画效果(滑入)

3

我正在尝试使用我编写的这个类使JPanel从侧面滑入:

public class AnimationClass {

    private int i;
    private int y;
    private JPanel panel;
    private int xTo;
    private Timer timer;
    private int xFrom;

    synchronized void slidePanelInFromRight(JPanel panelInput, int xFromInput, int xToInput, int yInput, int width, int height) {
        this.panel = panelInput;
        this.xFrom = xFromInput;
        this.xTo = xToInput;
        this.y = yInput;

            panel.setSize(width, height);

            timer = new Timer(0, new ActionListener() {
                public void actionPerformed(ActionEvent ae) {

                    for (int i = xFrom; i > xTo; i--) {
                        panel.setLocation(i, y);
                        panel.repaint();
                        i--;

                        timer.stop();
                        timer.setDelay(100);

                        if (i >= xTo) {
                            timer.stop();
                        }
                    }
                    timer.stop();

                }
            });
            timer.start();

    }

}

哎呀,我不知道问题出在哪里。虽然我尝试了许多不同的方法,但似乎并没有办法让它正常运行。


你遇到了什么问题?它目前的表现是怎样的? - MarioDS
4个回答

10

计时器应在每个tick中更改位置,直到达到目标位置。但实际上,在每个tick中,您都会运行一个for-next循环,导致EDT被阻塞,直到该循环完成,从而妨碍了UI的更新。

附带示例

例如...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestAnimatedPane {

    public static void main(String[] args) {
        new TestAnimatedPane();
    }

    public TestAnimatedPane() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JPanel panel;

        public TestPane() {
            setLayout(null);
            panel = new JPanel();
            panel.setBackground(Color.RED);
            add(panel);
            Dimension size = getPreferredSize();

            Rectangle from = new Rectangle(size.width, (size.height - 50) / 2, 50, 50);
            Rectangle to = new Rectangle((size.width - 50) / 2, (size.height - 50) / 2, 50, 50);

            Animate animate = new Animate(panel, from, to);
            animate.start();

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

    }

    public static class Animate {

        public static final int RUN_TIME = 2000;

        private JPanel panel;
        private Rectangle from;
        private Rectangle to;

        private long startTime;

        public Animate(JPanel panel, Rectangle from, Rectangle to) {
            this.panel = panel;
            this.from = from;
            this.to = to;
        }

        public void start() {
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    long duration = System.currentTimeMillis() - startTime;
                    double progress = (double)duration / (double)RUN_TIME;
                    if (progress > 1f) {
                        progress = 1f;
                        ((Timer)e.getSource()).stop();
                    }
                    Rectangle target = calculateProgress(from, to, progress);
                    panel.setBounds(target);
                }
            });
            timer.setRepeats(true);
            timer.setCoalesce(true);
            timer.setInitialDelay(0);
            startTime = System.currentTimeMillis();
            timer.start();
        }

    }

    public static Rectangle calculateProgress(Rectangle startBounds, Rectangle targetBounds, double progress) {

        Rectangle bounds = new Rectangle();

        if (startBounds != null && targetBounds != null) {

            bounds.setLocation(calculateProgress(startBounds.getLocation(), targetBounds.getLocation(), progress));
            bounds.setSize(calculateProgress(startBounds.getSize(), targetBounds.getSize(), progress));

        }

        return bounds;

    }

    public static Point calculateProgress(Point startPoint, Point targetPoint, double progress) {

        Point point = new Point();

        if (startPoint != null && targetPoint != null) {

            point.x = calculateProgress(startPoint.x, targetPoint.x, progress);
            point.y = calculateProgress(startPoint.y, targetPoint.y, progress);

        }

        return point;

    }

    public static int calculateProgress(int startValue, int endValue, double fraction) {

        int value = 0;
        int distance = endValue - startValue;
        value = (int)Math.round((double)distance * fraction);
        value += startValue;

        return value;

    }

    public static Dimension calculateProgress(Dimension startSize, Dimension targetSize, double progress) {

        Dimension size = new Dimension();

        if (startSize != null && targetSize != null) {

            size.width = calculateProgress(startSize.width, targetSize.width, progress);
            size.height = calculateProgress(startSize.height, targetSize.height, progress);

        }

        return size;

    }
}

更新

昨晚本应该加上这个(1年大人不想睡觉,2个父母也是,就不多说了...)

动画是一个复杂的话题,特别是当你开始考虑变速时(这个示例是静态的)。

与其重新发明轮子,你应该认真考虑看看...


非常感谢。它可以工作,但我仍然在想移动速度的问题。如何设置计时器中每个“循环”之间的等待时间?目前,它运行得非常缓慢(每次1像素),但如果我能减少每个像素移动之间的等待时间,那就太完美了。可是我还是想不出来。 - Langkiller
你有一些可以影响速度的数字变量。第一个是实际计时器的滴答延迟,我已将其设置为40,这应该给你大约25fps的速度。RUN_TIME参数确定动画应播放的时间,设置为2秒。我建议你尝试调整RUN_TIME。动画示例允许在每个周期之间设置可变延迟,这将使动画更加平滑。 - MadProgrammer
+1 表示代码结构良好;我忍不住要加入这个变体 - trashgod

3

这个经过良好构造的示例,可以很容易地进行以下变化。它利用了包含面板在JLayeredPane中的首选大小。

TestPane

/**
 * @see https://dev59.com/eXHYa4cB1Zd3GeqPNZs3#16322007
 * @see https://dev59.com/eXHYa4cB1Zd3GeqPNZs3#16316345
 */
public class TestPane extends JLayeredPane {

    private static final int WIDE = 200;
    private static final int HIGH = 5 * WIDE / 8; // ~1/phi
    private JPanel panel;

    public TestPane() {
        panel = new JPanel();
        panel.setBackground(Color.RED);
        panel.add(new JButton("Test"));
        add(panel);

        Dimension size = panel.getPreferredSize();
        int half = HIGH / 2 - size.height / 2;
        Rectangle from = new Rectangle(size);
        from.translate(WIDE, half);
        Rectangle to = new Rectangle(size);
        to.translate(0, half);
        panel.setBounds(from);

        Animate animate = new Animate(panel, from, to);
        animate.start();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(WIDE, HIGH);
    }
}

2

OP 的代码存在许多问题。正如 MadProrammer 指出的那样,应该每个计时器节拍只移动一步。这里是对 OP 代码的简单测试更正,它每秒移动 JPanel 25 像素。请注意评论:

synchronized void slidePanelInFromRight(JPanel panelInput, int xFromInput, int xToInput, int yInput, int width, int height) {
this.panel = panelInput;
this.xFrom = xFromInput;
this.xTo = xToInput;
this.y = yInput;

    panel.setSize(width, height);

    // timer runs 25 times per second
    timer = new Timer(40, new ActionListener() {
        public void actionPerformed(ActionEvent ae) {

            // Must 'remember' where we have slid panel to by using instance variable rather than automatic variable
            // Only move one step at a time.
            // No need to restart timer, it continues to run until stopped
            if (xFrom > xTo){
                xFrom = xFrom - 1;
                panel.setLocation(xFrom, y);
                panel.repaint();
            } else {
                timer.stop();
           }

            panel.setLocation(xFrom, y);
            panel.repaint();

        }
    });
    timer.start();

}

1
例子来滑动任何东西。
    package TestingPackage;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class ToggleBtn extends JPanel {

    JFrame frame;
    JPanel panelOut;
    JLabel labelOn;
    JLabel labelOff;
    JButton btn;
    int count = 1;

    public ToggleBtn() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setBounds(500, 300, 300, 300);
        frame.setLayout(null);

        panelOut = new JPanel(null);
        panelOut.setBounds(50, 100, 120, 30);
        panelOut.setBackground(Color.gray);
        frame.add(panelOut);

        btn = new JButton("::");
        btn.setBounds(0, 0, 60, 30);
        panelOut.add(btn);
        btn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                startThread();
            }
        });
        labelOn = new JLabel("ON");
        labelOn.setBounds(0, 0, 60, 30);
        panelOut.add(labelOn);

        labelOff = new JLabel("OFF");
        labelOff.setBounds(60, 0, 60, 30);
        panelOut.add(labelOff);

        frame.setVisible(true);
    }

    public void startThread() {
        count++;
        new Move().start();
    }

    public static void main(String[] args) {
        new ToggleBtn();
    }

    class Move extends Thread {

        @Override
        public void run() {
            if (count % 2 == 0) {
                System.out.println("if");
                for (int i = 0; i <= 60; i++) {
                    try {
                        Thread.sleep(3);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(ToggleBtn.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    btn.setBounds(i, 0, 60, 30);
                }

            } else {
                System.out.println("else");
                for (int i = 60; i >= 0; i--) {
                    try {
                        Thread.sleep(3);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(ToggleBtn.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    btn.setBounds(i, 0, 60, 30);
                }
            }
        }
    }
}

这种方法没有正确同步对 btn 的访问。 - trashgod

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接