@GeorgeZ。我认为@camickr 提出的概念与当你实际进行布局时有关。作为覆盖doLayout 的替代方案,我建议将GridLayout 子类化以仅在动画结束时布置组件(不覆盖doLayout)。但这与 camickr 的概念相同。
虽然if你的组件在右侧面板的内容(即标签的文本)在分隔线动画的过程中保持不变,你也可以创建右侧面板的Image当用户单击按钮并显示该按钮而不是实际面板时。我想,这个解决方案包括:
-
CardLayout 用于右侧面板。一张卡片具有实际的rightPanel 内容(即JLabels)。第二张卡只有一个JLabel,它将加载第一张卡的Image(作为ImageIcon)。
- 据我所知,通过查看
CardLayout 的实现,Container 的所有子组件的边界是在layoutContainer 方法期间设置的。这可能意味着标签会被布置,尽管第二张卡片会被显示,但它们是不可见的。因此,您可能应该将其与子类 GridLayout 结合起来,以便仅在动画结束时进行布局。
- 要绘制第一张卡片的
Image,首先应该创建一个BufferedImage,然后在其上创建createGraphics,然后在创建的Graphics2D对象上调用rightPanel.paint,最后处置Graphics2D对象之后。
- 创建第二张卡片,使
JLabel 居中。为此,您只需为第二张卡提供GridBagLayout 并在其中仅添加一个Component(JLabel),这应该是唯一的。 GridBagLayout 始终将内容居中。
让我知道这样的解决方案是否对您有用。它可能没有用,因为您可能希望在动画进行时实际看到标签更改其布局配置文件,或者您甚至可能希望用户能够与Components 的rightPanel 交互在动画进行时。在这两种情况下,拍摄rightPanel 的照片并在动画发生时显示它而不是真实标签是不够的。因此,在这种情况下,这真的取决于rightPanel 的内容的动态程度。请在 cmets 中告诉我。
如果每个程序运行的内容始终相同,那么您可能可以预先创建 Image 并存储它。甚至,大量的Images 并将它们存储起来,然后在动画打开时一个接一个地显示。
同样,如果每个程序运行的内容并不总是相同,那么您也可以继承GridLayout 并在启动时预先计算每个组件的边界。然后这将使GridLayout 在布局组件时更快一点(这就像用每个对象的位置编码视频),但是当我正在测试它时,GridLayout 已经很快了:它只计算了大约 10布局开始时的变量,然后立即传递到设置每个 Component 的边界。
编辑 1:
这是我对我的想法的尝试(使用Image):
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.IntBinaryOperator;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class SplitPaneTest {
//Just a Timer which plays the animation of the split pane's divider going from side to side...
public static class SplitPaneAnimationTimer extends Timer {
private final JSplitPane splitPane;
private int speed, newDivLoc;
private IntBinaryOperator directionf;
private Consumer<SplitPaneAnimationTimer> onFinish;
public SplitPaneAnimationTimer(final int delay, final JSplitPane splitPane) {
super(delay, null);
this.splitPane = Objects.requireNonNull(splitPane);
super.setRepeats(true);
super.setCoalesce(false);
super.addActionListener(e -> {
splitPane.setDividerLocation(directionf.applyAsInt(newDivLoc, splitPane.getDividerLocation() + speed));
if (newDivLoc == splitPane.getDividerLocation()) {
stop();
if (onFinish != null)
onFinish.accept(this);
}
});
speed = 0;
newDivLoc = 0;
directionf = null;
onFinish = null;
}
public int getSpeed() {
return speed;
}
public JSplitPane getSplitPane() {
return splitPane;
}
public void play(final int newDividerLocation, final int speed, final IntBinaryOperator directionf, final Consumer<SplitPaneAnimationTimer> onFinish) {
if (newDividerLocation != splitPane.getDividerLocation() && Math.signum(speed) != Math.signum(newDividerLocation - splitPane.getDividerLocation()))
throw new IllegalArgumentException("Speed needs to be in the direction towards the newDividerLocation (from the current position).");
this.directionf = Objects.requireNonNull(directionf);
newDivLoc = newDividerLocation;
this.speed = speed;
this.onFinish = onFinish;
restart();
}
}
//Just a GridLayout subclassed to only allow laying out the components only if it is enabled.
public static class ToggleGridLayout extends GridLayout {
private boolean enabled;
public ToggleGridLayout(final int rows, final int cols) {
super(rows, cols);
enabled = true;
}
@Override
public void layoutContainer(final Container parent) {
if (enabled)
super.layoutContainer(parent);
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
}
//How to create a BufferedImage (instead of using the constructor):
private static BufferedImage createBufferedImage(final int width, final int height, final boolean transparent) {
final GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice gdev = genv.getDefaultScreenDevice();
final GraphicsConfiguration gcnf = gdev.getDefaultConfiguration();
return transparent
? gcnf.createCompatibleImage(width, height, Transparency.TRANSLUCENT)
: gcnf.createCompatibleImage(width, height);
}
//This is the right panel... It is composed by two cards: one for the labels and one for the image.
public static class RightPanel extends JPanel {
private static final String CARD_IMAGE = "IMAGE",
CARD_LABELS = "LABELS";
private final JPanel labels, imagePanel; //The two cards.
private final JLabel imageLabel; //The label in the second card.
private final int speed; //The speed to animate the motion of the divider.
private final SplitPaneAnimationTimer spat; //The Timer which animates the motion of the divider.
private String currentCard; //Which card are we currently showing?...
public RightPanel(final JSplitPane splitPane, final int delay, final int speed, final int rows, final int cols) {
super(new CardLayout());
super.setBorder(BorderFactory.createLineBorder(Color.red));
spat = new SplitPaneAnimationTimer(delay, splitPane);
this.speed = Math.abs(speed); //We only need a positive (absolute) value.
//Label and panel of second card:
imageLabel = new JLabel();
imageLabel.setHorizontalAlignment(JLabel.CENTER);
imageLabel.setVerticalAlignment(JLabel.CENTER);
imagePanel = new JPanel(new GridBagLayout());
imagePanel.add(imageLabel);
//First card:
labels = new JPanel(new ToggleGridLayout(rows, cols));
for (int i = 0; i < rows * cols; ++i)
labels.add(new JLabel("|"));
//Adding cards...
final CardLayout clay = (CardLayout) super.getLayout();
super.add(imagePanel, CARD_IMAGE);
super.add(labels, CARD_LABELS);
clay.show(this, currentCard = CARD_LABELS);
}
//Will flip the cards.
private void flip() {
final CardLayout clay = (CardLayout) getLayout();
final ToggleGridLayout labelsLayout = (ToggleGridLayout) labels.getLayout();
if (CARD_LABELS.equals(currentCard)) { //If we are showing the labels:
//Disable the laying out...
labelsLayout.setEnabled(false);
//Take a picture of the current panel state:
final BufferedImage pic = createBufferedImage(labels.getWidth(), labels.getHeight(), true);
final Graphics2D g2d = pic.createGraphics();
labels.paint(g2d);
g2d.dispose();
imageLabel.setIcon(new ImageIcon(pic));
imagePanel.revalidate();
imagePanel.repaint();
//Flip the cards:
clay.show(this, currentCard = CARD_IMAGE);
}
else { //Else if we are showing the image:
//Enable the laying out...
labelsLayout.setEnabled(true);
//Revalidate and repaint so as to utilize the laying out of the labels...
labels.revalidate();
labels.repaint();
//Flip the cards:
clay.show(this, currentCard = CARD_LABELS);
}
}
//Called when we need to animate fully left motion (ie until reaching left side):
public void goLeft() {
final JSplitPane splitPane = spat.getSplitPane();
final int currDivLoc = splitPane.getDividerLocation(),
minDivLoc = splitPane.getMinimumDividerLocation();
if (CARD_LABELS.equals(currentCard) && currDivLoc > minDivLoc) { //If the animation is stopped:
flip(); //Show the image label.
spat.play(minDivLoc, -speed, Math::max, ignore -> flip()); //Start the animation to the left.
}
}
//Called when we need to animate fully right motion (ie until reaching right side):
public void goRight() {
final JSplitPane splitPane = spat.getSplitPane();
final int currDivLoc = splitPane.getDividerLocation(),
maxDivLoc = splitPane.getMaximumDividerLocation();
if (CARD_LABELS.equals(currentCard) && currDivLoc < maxDivLoc) { //If the animation is stopped:
flip(); //Show the image label.
spat.play(maxDivLoc, speed, Math::min, ignore -> flip()); //Start the animation to the right.
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
int rows, cols;
rows = cols = 60;
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
final RightPanel rightPanel = new RightPanel(splitPane, 10, 3, rows, cols);
splitPane.setLeftComponent(leftPanel);
splitPane.setRightComponent(rightPanel);
JButton left = new JButton("Go left"),
right = new JButton("Go right");
left.addActionListener(e -> rightPanel.goLeft());
right.addActionListener(e -> rightPanel.goRight());
final JPanel buttons = new JPanel(new GridLayout(1, 0));
buttons.add(left);
buttons.add(right);
frame.add(splitPane, BorderLayout.CENTER);
frame.add(buttons, BorderLayout.PAGE_START);
frame.setSize(1000, 800);
frame.setMaximumSize(frame.getSize());
frame.setLocationByPlatform(true);
frame.setVisible(true);
splitPane.setDividerLocation(0.5);
});
}
}