2

我试图JSplitPane用动画隐藏一个。通过隐藏,我的意思是setDividerLocation(0)它的左侧组件是不可见的(技术上它是可见的,但宽度为零):

public class SplitPaneTest {

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

            JPanel rightPanel = new JPanel(new GridLayout(60, 60));
            for (int i = 0; i < 60 * 60; i++) {
//              rightPanel.add(new JLabel("s"));
            }
            rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));

            JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
            frame.add(splitPane);

            JButton button = new JButton("Press me to hide");
            button.addActionListener(e -> hideWithAnimation(splitPane));
            leftPanel.add(button, BorderLayout.PAGE_START);

            frame.setMaximumSize(new Dimension(800, 800));
            frame.setSize(800, 800);
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        });
    }

    private static void hideWithAnimation(JSplitPane splitPane) {
        final Timer timer = new Timer(10, null);
        timer.addActionListener(e -> {
            splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
            if (splitPane.getDividerLocation() == 0)
                timer.stop();
        });
        timer.start();
    }

}

如果你运行它,会发现一切看起来都很好,并且动画运行流畅。

但是,在实际应用中,右边JSplitPane是一个JPanelwith CardLayout,每张卡片都有很多组件。

如果您取消注释此行以模拟组件的数量:

// rightPanel.add(new JLabel("s"));

重新运行上面的例子,你会看到动画不再流畅了。所以,问题是,是否有可能使其平滑(-ier)?

我不知道如何解决 - 如果有的话。

根据我的研究,我注册了一个全球性的ComponentListener

Toolkit.getDefaultToolkit()
    .addAWTEventListener(System.out::println, AWTEvent.COMPONENT_EVENT_MASK);

并看到了正在触发的大量事件。所以,我认为问题的根源在于为每个组件触发的大量组件事件。此外,似乎具有自定义渲染器(如JList-ListCellRendererJTable- TableCellRenderer)的组件,组件事件正在为所有渲染器触发。例如,如果 aJList有 30 个元素,则仅为它触发 30 个事件(组件)。似乎(这就是我提到它的原因)对于 CardLayout,“不可见”组件也发生了事件。

我知道这60*60对你来说可能听起来很疯狂,但在真正的应用程序中(我的应用程序有 ~1500),因为它是有道理的,所以这幅画更重。

4

2 回答 2

1

@GeorgeZ。我认为@camickr 提出的概念与您实际进行布局有关。作为覆盖的替代方案doLayout,我建议将 子类化GridLayout以仅在动画结束时布置组件(不覆盖doLayout)。但这与camickr的概念相同。

虽然如果您的组件在右侧面板中的内容(即标签的文本)在分隔线动画期间保持不变,您也可以Image在用户单击按钮时创建右侧面板并显示它而不是实际控制板。我想,这个解决方案包括:

  1. ACardLayout为右面板。一张卡片具有实际rightPanel内容(即JLabels)。第二张卡只有一张JLabel将加载第一张卡的Image(作为ImageIcon)。
  2. 据我所知,通过查看CardLayout的实现,所有子组件的边界Container都是在layoutContainer方法期间设置的。这可能意味着标签将被布置,尽管第二张卡片将被显示,但它们是不可见的。因此,您可能应该将其与子类结合起来,GridLayout以便仅在动画结束时进行布局。
  3. 要绘制Image第一张卡片,首先应该创建一个BufferedImage,然后createGraphics在它上面,然后调用rightPanel.paint创建的Graphics2D对象,最后处理该Graphics2D对象。
  4. 创建第二张卡片,JLabel使其居中。为此,您只需为第二张卡提供 aGridBagLayout并在其中仅添加一个ComponentJLabel应该是唯一的)。GridBagLayout始终以内容为中心。

让我知道这样的解决方案是否对您有用。它可能没有用,因为您可能希望在动画进行时实际看到标签更改其布局配置文件,或者您甚至可能希望用户能够在动画进行时与Components进行交互rightPanel. 在这两种情况下,rightPanel在动画发生时拍摄并显示它而不是真实标签是不够的。因此,在这种情况下,它真的取决于rightPanel. 请在评论中告诉我。

如果每个程序运行的内容始终相同,那么您可能可以预先创建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);
        });
    }
}
于 2020-07-29T20:09:33.390 回答
1

我知道 60*60 对你来说可能听起来很疯狂,但在实际应用中(我的有 ~1500),因为它是有道理的,所以绘画更重。

每次更改分隔线位置时都会调用布局管理器,这会增加很多开销。

一种解决方案可能是在分隔线动画时停止调用布局管理器。这可以通过覆盖doLayout()右侧面板的方法来完成:

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    
    public class SplitPaneTest2 {
    
        public static boolean doLayout = true;
    
        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));
    
                JPanel rightPanel = new JPanel(new GridLayout(60, 60))
                {
                    @Override
                    public void doLayout()
                    {
                        if (SplitPaneTest2.doLayout)
                            super.doLayout();
                    }
                };
                for (int i = 0; i < 60 * 60; i++) {
                  rightPanel.add(new JLabel("s"));
                }
                rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
    
                JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
                frame.add(splitPane);
    
                JButton button = new JButton("Press me to hide");
                button.addActionListener(e -> hideWithAnimation(splitPane));
                leftPanel.add(button, BorderLayout.PAGE_START);
    
                frame.setMaximumSize(new Dimension(800, 800));
                frame.setSize(800, 800);
                frame.setLocationByPlatform(true);
                frame.setVisible(true);
            });
        }
    
        private static void hideWithAnimation(JSplitPane splitPane) {
            SplitPaneTest2.doLayout = false;
            final Timer timer = new Timer(10, null);
            timer.addActionListener(e -> {
                splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
                if (splitPane.getDividerLocation() == 0)
                {
                    timer.stop();
                    SplitPaneTest2.doLayout = true;
                    splitPane.getRightComponent().revalidate();
                }
            });
            timer.start();
        }
    
    }

编辑:

我不打算将我的测试包括用一个使用组件图像的面板换掉充满组件的面板,因为我认为动画是相同的,但由于它是由其他人建议的,因此我尝试对您进行评估:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.image.*;

public class SplitPaneTest2 {

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

            JPanel rightPanel = new JPanel(new GridLayout(60, 60));
            for (int i = 0; i < 60 * 60; i++) {
              rightPanel.add(new JLabel("s"));
            }
            rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));

            JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
            frame.add(splitPane);

            JButton button = new JButton("Press me to hide");
            button.addActionListener(e -> hideWithAnimation(splitPane));
            leftPanel.add(button, BorderLayout.PAGE_START);

            frame.setMaximumSize(new Dimension(800, 800));
            frame.setSize(800, 800);
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        });
    }

    private static void hideWithAnimation(JSplitPane splitPane) {

        Component right = splitPane.getRightComponent();
        Dimension size = right.getSize();

        BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = bi.createGraphics();
        right.paint( g );
        g.dispose();

        JLabel label = new JLabel( new ImageIcon( bi ) );
        label.setHorizontalAlignment(JLabel.LEFT);

        splitPane.setRightComponent( label );
        splitPane.setDividerLocation( splitPane.getDividerLocation() );

        final Timer timer = new Timer(10, null);
        timer.addActionListener(e -> {

            splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
            if (splitPane.getDividerLocation() == 0)
            {
                timer.stop();
                splitPane.setRightComponent( right );
            }
        });
        timer.start();
    }

}
于 2020-07-29T17:18:16.420 回答