@GeorgeZ. I think the concept presented by @camickr has to do with when you actually do the layout. As an alternative to overriding doLayout
, I would suggest subclassing the GridLayout
to only lay out the components at the end of the animation (without overriding doLayout
). But this is the same concept as camickr's.
Although if the contents of your components in the right panel (ie the text of the labels) remain unchanged during the animation of the divider, you can also create an Image
of the right panel when the user clicks the button and display that instead of the actual panel. This solution, I would imagine, involves:
- A
CardLayout
for the right panel. One card has the actual rightPanel
contents (ie the JLabel
s). The second card has only one JLabel
which will be loaded with the Image
(as an ImageIcon
) of the first card.
- As far as I know, by looking at the
CardLayout
's implementation, the bounds of all the child components of the Container
are set during layoutContainer
method. That would probably mean that the labels would be layed out inspite being invisible while the second card would be shown. So you should probably combine this with the subclassed GridLayout
to lay out only at the end of the animation.
- To draw the
Image
of the first card, one should first create a BufferedImage
, then createGraphics
on it, then call rightPanel.paint
on the created Graphics2D
object and finally dispose the Graphics2D
object after that.
- Create the second card such that the
JLabel
would be centered in it. To do this, you just have to provide the second card with a GridBagLayout
and add only one Component
in it (the JLabel
) which should be the only. GridBagLayout
always centers the contents.
Let me know if such a solution could be useful for you. It might not be useful because you could maybe want to actually see the labels change their lay out profile while the animation is in progress, or you may even want the user to be able to interact with the Component
s of the rightPanel
while the animation is in progress. In both cases, taking a picture of the rightPanel
and displaying it instead of the real labels while the animation takes place, should not suffice. So it really depends, in this case, on how dynamic will be the content of the rightPanel
. Please let me know in the comments.
If the contents are always the same for every program run, then you could probably pre-create that Image
and store it. Or even, a multitude of Image
s and store them and just display them one after another when the animation turns on.
Similarly, if the contents are not always the same for every program run, then you could also subclass GridLayout
and precalculate the bounds of each component at startup. Then that would make GridLayout
a bit faster in laying out the components (it would be like encoding a video with the location of each object), but as I am testing it, GridLayout
is already fast: it just calculates about 10 variables at the start of laying out, and then imediately passes over to setting the bounds of each Component
.
Edit 1:
And here is my attempt of my idea (with the 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);
});
}
}