使用动画调整jPanel的大小

时间:2014-03-03 00:06:04

标签: java swing jpanel

我正在使用FullScreen Panel开发应用程序,有时我需要在屏幕右侧显示第二个Panel。

为了开发它,我创建了一个带有AbsoluteLayout(来自NetBeans)的jPanel,其中包含另外两个面板。我计算每个面板位置和相对于屏幕分辨率的大小。

基本上,我有两个“状态”。在第一个中,jPanel1的宽度= 100%,jPanel 2的宽度= 0%(所以我没有显示这个面板)。第二种状态,当jPanel1的宽度= 75%且jPanel2的宽度= 25%时。

要做这些过渡我只重新计算每个面板大小并且效果很好,但我希望用一个动画来完成。随着jPanel2“滑动”进入屏幕。

以下图片解释了它: Link to image

我尝试了一些选择,但我在每次尝试都失败了。基本上,“动画”正在发生,但屏幕最终只会刷新。在我的最后一次尝试中,我尝试使用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();
}

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

2 个答案:

答案 0 :(得分:5)

两年前,我写了一个“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;
    }

}

答案 1 :(得分:4)

  

基本上,“动画”正在发生,但屏幕最终只会刷新。

当代码在Event Dispatch Thread上执行时,不要使用sleep(..)方法,并且Swing侦听器的所有代码都在EDT上执行。

  

我尝试使用摇摆计时器

是的,这是正确的方法,但您使用的计时器不正确。你不应该在Timer代码中有一个循环。这是使用Timer的关键。您可以安排定时器在您想要动画时触发。在你的情况下看起来你想要动画每10毫秒(这可能太快),所以你应该安排定时器每隔10毫秒发射一次。

由于您使用的是null布局,因此不需要revalidate()(用于调用布局管理器)或重绘,因为当您更改组件的大小/位置时,repaint()会自动进行调用