使用动画调整jPanels的大小

7
我正在开发一个带有全屏面板的应用程序,有时需要在屏幕右侧显示第二个面板。
为了开发它,我使用了NetBeans中的AbsoluteLayout创建了一个包含另外两个面板的jPanel。我根据屏幕分辨率计算每个面板的位置和大小。
基本上,我有两个“状态”。在第一个状态下,jPanel1的宽度为100%,jPanel2的宽度为0%(因此我不显示此面板)。而在第二个状态下,当jPanel1的宽度为75%时,jPanel2的宽度为25%。
为了实现这些转换,我只需重新计算每个面板的大小即可,但我希望能够通过单个动画来实现它。让jPanel2“滑入”屏幕。
下图解释了它: 图片链接 我尝试了一些选项来实现它,但每次尝试都失败了。基本上,“动画”正在发生,但屏幕仅在最后刷新。在我的最后一次尝试中,我尝试使用swing计时器,我的代码是:
变量:
-dataPanel:主面板,具有AbsoluteLayout并包含jPanel1和jPanel2
-visCons:在更新动画中witdh时,jPanel1位置的AbsoluteConstraints
-listCons:在更新动画中位置时,jPanel2位置的AbsoluteConstraints。
public static void showListPanel() {
    timer = new Timer(1000, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
            int width = gd.getDisplayMode().getWidth();
            int height = gd.getDisplayMode().getHeight();
            int endVis =  width - (width/4 + 20);
            for (int i = width; i >= endVis; i--) {
                visCons.width = endVis;
                listCons.x = endVis;
                dataPanel.repaint();
                dataPanel.revalidate();
                try {
                    TimeUnit.MICROSECONDS.sleep(10);
                } catch (InterruptedException e2) {
                    // TODO Auto-generated catch block
                    e2.printStackTrace();
                }
            }
            timer.stop();
        }
    });
    timer.start();
}

我希望有人能告诉我一些替代方案,或者在动画期间刷新屏幕需要做什么。


1
你可能想要看一下 sliding-layout,它使用了 Universal Tween Engine - MadProgrammer
2个回答

8
两年前,我编写了一个名为“LayoutAnimator”的工具,可以在任意布局之间执行过渡效果。也许你会觉得它很有用:
import java.awt.Component;
import java.awt.Container;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;

import javax.swing.Timer;

/**
 * A class that may perform animations between different layouts
 * for containers.
 */
class LayoutAnimator
{
    /**
     * The map from containers to {@link LayoutAnimation LayoutAnimations}
     * that are currently running.
     */
    private static final Map<Container, LayoutAnimation> running =
        new IdentityHashMap<Container, LayoutAnimation>();

    /**
     * Execute the transition between the current layout of the given
     * container to the new layout. This method will animate the
     * contents of the given container, starting at its current state,
     * towards the state that is defined by setting the given layout
     * to the given container. The duration for the animation
     * (in seconds) may be specified. After the animation has finished,
     * the container will have the given layout.
     *
     * @param container The container
     * @param newLayout The new layout
     * @param durationS The duration, in seconds.
     */
    public static synchronized void execute(
        Container container, LayoutManager newLayout, double durationS)
    {
        // If there is already a LayoutAnimation running for the
        // container, cancel it and remove it from the map
        LayoutAnimation runningLayoutAnimtion = running.get(container);
        if (runningLayoutAnimtion != null)
        {
            runningLayoutAnimtion.cancel();
            running.remove(container);
        }

        // Execute the layout animation. When it is finished,
        // the callback will remove it from the map of
        // running layout animations
        final LayoutAnimation layoutAnimtion =
            new LayoutAnimation(container, newLayout);
        running.put(container, layoutAnimtion);
        layoutAnimtion.execute(durationS, new LayoutAnimationCallback()
        {
            @Override
            public void animationFinished()
            {
                running.remove(layoutAnimtion);
            }
        });
    }

    /**
     * Interface for classes that may be called when
     * a {@link LayoutAnimation} is finished.
     */
    private static interface LayoutAnimationCallback
    {
        /**
         * Will be called when the {@link LayoutAnimation} is finished
         */
        void animationFinished();
    }

    /**
     * A layout animation. This class performs the animation between
     * an initial state of a container, towards the state that is
     * defined by applying a new layout to the container.
     */
    private static class LayoutAnimation
    {
        /**
         * The container on which the animation is performed
         */
        private final Container container;

        /**
         * The new layout towards which the container is animated
         */
        private final LayoutManager newLayout;

        /**
         * The timer that performs the actual layout
         */
        private final Timer timer;

        /**
         * The delay for the timer
         */
        private final int delayMS = 20;

        /**
         * Creates a new LayoutAnimation for the given container,
         * which animates towards the given layout.
         *
         * @param container The container
         * @param newLayout The new layout
         */
        LayoutAnimation(Container container, LayoutManager newLayout)
        {
            this.container = container;
            this.newLayout = newLayout;
            this.timer = new Timer(delayMS, null);
        }

        /**
         * Execute the animation. This will store the current state of
         * the container, compute the target state based on the new
         * layout, and perform an animation towards the new state
         * that will take the specified duration (in seconds).
         * When the animation is finished, the given callback will
         * be notified.
         *
         * @param durationS The duration for the animation, in seconds
         * @param layoutAnimatorCallback The callback that will be
         * notified when the animation is finished.
         */
        void execute(final double durationS,
            final LayoutAnimationCallback layoutAnimatorCallback)
        {

            // Store all old bounds of the components of the container
            final Map<Component, Rectangle> oldBounds =
                getAllBounds(container.getComponents());

            // Apply the new layout, and store the new bounds
            // of all components
            container.setLayout(newLayout);
            newLayout.layoutContainer(container);
            final Map<Component, Rectangle> newBounds =
                getAllBounds(container.getComponents());

            // Restore the old bounds
            container.setLayout(null);
            setAllBounds(container.getComponents(), oldBounds);

            // Create the bounds that will be animated
            final Map<Component, Rectangle> currentBounds =
                getAllBounds(container.getComponents());

            // Set up the timer that will perform the animation
            timer.addActionListener(new ActionListener()
            {
                /**
                 * The current alpha value decribing the interpolation
                 * state, between 0 and 1
                 */
                double alpha = 0;

                /**
                 * The step size for the alpha.
                 */
                double alphaStep = 1.0 / (durationS * (1000.0 / delayMS));

                @Override
                public void actionPerformed(ActionEvent e)
                {
                    if (alpha == 1.0)
                    {
                        timer.stop();
                        container.setLayout(newLayout);
                        layoutAnimatorCallback.animationFinished();
                    }
                    alpha += alphaStep;
                    alpha = Math.min(1.0, alpha);

                    interpolate(oldBounds, newBounds, currentBounds, alpha);
                    setAllBounds(container.getComponents(), currentBounds);
                }
            });
            timer.setCoalesce(true);
            timer.start();
        }

        /**
         * Cancel this animation
         */
        void cancel()
        {
            timer.stop();
        }
    }


    /**
     * Create a map from the given components to their bounds.
     *
     * @param components The components
     * @return The resulting map
     */
    private static Map<Component, Rectangle> getAllBounds(
        Component components[])
    {
        Map<Component, Rectangle> currentBounds =
            new HashMap<Component, Rectangle>();
        for (Component component : components)
        {
            Rectangle bounds = component.getBounds();
            currentBounds.put(component, bounds);
        }
        return currentBounds;
    }

    /**
     * Set the bounds of the given components to the bounds that
     * are stored in the given map.
     *
     * @param components The components
     * @param newBounds The new bounds of the components
     */
    private static void setAllBounds(
        Component components[], Map<Component, Rectangle> newBounds)
    {
        for (Component component : components)
        {
            Rectangle bounds = newBounds.get(component);
            component.setBounds(bounds);
            component.validate();
        }
    }


    /**
     * Interpolate between all rectangles from the maps <code>b0</code>
     * and <code>b1</code> according to the given alpha value
     * (between 0 and 1), and store the interpolated rectangles
     * in <code>b</code>
     *
     * @param b0 The first input rectangles
     * @param b1 The second input rectangles
     * @param b The interpolated rectangles
     * @param alpha The alpha value, between 0 and 1
     */
    private static void interpolate(
        Map<Component, Rectangle> b0, Map<Component, Rectangle> b1,
        Map<Component, Rectangle> b, double alpha)
    {
        for (Component component : b0.keySet())
        {
            Rectangle r0 = b0.get(component);
            Rectangle r1 = b1.get(component);
            Rectangle r = b.get(component);
            interpolate(r0, r1, r, alpha);
        }
    }

    /**
     * Linearly interpolate between <code>r0</code> and <code>r1</code>
     * according to the given alpha value (between 0 and 1), and store
     * the result in <code>r</code>.
     *
     * @param r0 The first rectangle
     * @param r1 The second rectangle
     * @param r The interpolated rectangle
     * @param alpha
     */
    private static void interpolate(
        Rectangle r0, Rectangle r1, Rectangle r, double alpha)
    {
        r.x = (int)(r0.x + alpha * (r1.x - r0.x));
        r.y = (int)(r0.y + alpha * (r1.y - r0.y));
        r.width = (int)(r0.width + alpha * (r1.width - r0.width));
        r.height = (int)(r0.height + alpha * (r1.height - r0.height));
    }


    /**
     * Private constructor to prevent instantiation
     */
    private LayoutAnimator()
    {
    }

}

一个小样例:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BoundedRangeModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;

// Demo for the LayoutAnimator
public class LayoutAnimatorDemo
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Create a component where optimized drawing
        // is disabled, to avoid flickering when
        // components overlap
        JComponent c = new JComponent()
        {
            private static final long serialVersionUID =
                -8793865141504880212L;
            @Override
            public boolean isOptimizedDrawingEnabled()
            {
                return false;
            }
        };
        f.setContentPane(c);

        Container container = f.getContentPane();
        container.setLayout(new FlowLayout());

        // Create buttons to switch between layouts
        JButton c0 = new JButton("FlowLayout");
        JButton c1 = new JButton("GridLayout");
        JButton c2 = new JButton("BorderLayout");
        JButton c3 = new JButton("GridBagLayout");

        // Create a slider for the animation duration
        JComponent c4 = new JPanel(new BorderLayout());
        c4.add(new JLabel("Duration (ms) :"), BorderLayout.WEST);
        JSlider slider = new JSlider(0, 2000);
        slider.setMinimumSize(new Dimension(100, 100));
        slider.setPaintTicks(true);
        slider.setMajorTickSpacing(500);
        slider.setPaintLabels(true);
        c4.add(slider, BorderLayout.CENTER);
        BoundedRangeModel b = slider.getModel();

        // Attach ActionListeners to the buttons that perform
        // animations to the different layouts
        connect(c0, container, new FlowLayout(), b);
        connect(c1, container, new GridLayout(2,3), b);
        connect(c2, container, createBorderLayout(c0, c1, c2, c3, c4), b);
        connect(c3, container, createGridBagLayout(c0, c1, c2, c3, c4), b);

        container.add(c0);
        container.add(c1);
        container.add(c2);
        container.add(c3);
        container.add(c4);

        f.setSize(800, 600);
        f.setVisible(true);
    }

    // Attach an ActionListener to the given button that will animate
    // the contents of the given container towards the given layout,
    // with a duration (in milliseconds) that is taken from the
    // given BoundedRangeModel
    private static void connect(
        JButton button, final Container container,
        final LayoutManager layoutManager,
        final BoundedRangeModel boundedRangeModel)
    {
        button.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                double durationS = boundedRangeModel.getValue() / 1000.0;
                LayoutAnimator.execute(container, layoutManager, durationS);
            }
        });
    }

    // Create a predefined BorderLayout
    private static LayoutManager createBorderLayout(
        Component c0, Component c1, Component c2, Component c3, Component c4)
    {
        BorderLayout borderLayout = new BorderLayout();
        borderLayout.addLayoutComponent(c0, BorderLayout.NORTH);
        borderLayout.addLayoutComponent(c1, BorderLayout.CENTER);
        borderLayout.addLayoutComponent(c2, BorderLayout.SOUTH);
        borderLayout.addLayoutComponent(c3, BorderLayout.WEST);
        borderLayout.addLayoutComponent(c4, BorderLayout.EAST);
        return borderLayout;
    }

    // Create a predefined GridBagLayout
    private static LayoutManager createGridBagLayout(
        Component c0, Component c1, Component c2, Component c3, Component c4)
    {
        GridBagLayout gridBagLayout = new GridBagLayout();
        GridBagConstraints c = null;

        c = new GridBagConstraints();
        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 0.5;
        c.weighty = 0.5;
        c.gridwidth = 2;
        c.fill = GridBagConstraints.BOTH;
        gridBagLayout.addLayoutComponent(c0, c);

        c = new GridBagConstraints();
        c.gridx = 2;
        c.gridy = 0;
        c.weightx = 0.5;
        c.weighty = 0.5;
        c.fill = GridBagConstraints.BOTH;
        gridBagLayout.addLayoutComponent(c1, c);

        c = new GridBagConstraints();
        c.gridx = 0;
        c.gridy = 1;
        c.weightx = 0.25;
        c.weighty = 0.5;
        c.fill = GridBagConstraints.BOTH;
        gridBagLayout.addLayoutComponent(c2, c);

        c = new GridBagConstraints();
        c.gridx = 1;
        c.gridy = 1;
        c.weightx = 0.75;
        c.weighty = 0.5;
        c.fill = GridBagConstraints.BOTH;
        gridBagLayout.addLayoutComponent(c3, c);

        c = new GridBagConstraints();
        c.gridx = 2;
        c.gridy = 1;
        c.weightx = 0.5;
        c.weighty = 0.5;
        c.fill = GridBagConstraints.BOTH;
        gridBagLayout.addLayoutComponent(c4, c);

        return gridBagLayout;
    }

}

5
基本上,“动画”是发生了,但屏幕只在最后刷新。
当代码在事件分派线程(EDT)上执行时,不要使用sleep(..)方法,并且所有来自Swing监听器的代码都在EDT上执行。
我尝试使用swing定时器。
是的,那是正确的方法,但您正在错误地使用定时器。您不应该在定时器代码中使用循环。这就是使用定时器的关键点。您可以安排定时器在您想要进行动画的任何时间触发。在您的情况下,似乎您希望每10毫秒进行一次动画(这可能太快了),因此您应该安排定时器每10毫秒触发一次。
由于您正在使用空布局,所以没有必要使用revalidate()(用于调用布局管理器)或repaint,因为当更改组件的大小/位置时会自动调用repaint()。

谢谢你,carmickr……你给了我“路径”,只用几分钟我就让我的应用程序正常工作了。 - fhenrique

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