使用动画调整jPanels的大小



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

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

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

要进行这些转换,我只重新计算每个面板的大小,效果很好,但我希望用一个动画来完成。随着jPanel2"滑动"进入屏幕。

下图对此进行了说明:链接到图像

我尝试了一些办法,但每次都失败了。基本上,"动画"正在发生,但屏幕只在最后刷新。在我的最后一次尝试中,我尝试使用摆动计时器,我的代码是:

变量:

-dataPanel:主面板,带有AbsoluteLayout,包含jPanel1和jPanel2

-visCons:jPanel1位置的绝对约束,当我更新动画中的witdh时

-listCons:AbsolutionConstraints用于jPanel2位置,当我更新动画中的位置时。

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();
}

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

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

基本上,"动画"正在发生,但屏幕只在最后刷新。

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

我试图使用摆动定时器

是的,这是正确的方法,但您使用计时器不正确。您不应该在Timer代码中有一个循环。这就是使用计时器的意义所在。您可以安排"计时器"在需要动画时随时启动。在您的情况下,看起来您希望每10ms播放一次动画(这可能太快了),所以您应该将计时器安排为每10ms启动一次。

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

最新更新