java - 当有数千个组件时使动画更快

标签 java swing awt edt componentlistener

我试图用动画隐藏 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 的右侧是一个带有 CardLayoutJPanel,每个卡片都有很多组件。

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

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

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

我不知道如何找到解决方案 - 如果有的话。

根据我的研究,我注册了一个全局ComponentListener:

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

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

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

最佳答案

I know that 60*60 might sound crazy to you, but in a real application (mine has ~1500) as it makes sense, the painting is heavier.

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

一种解决方案可能是在分隔符动画时停止调用布局管理器。这可以通过重写右侧面板的 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();
    }

}

关于java - 当有数千个组件时使动画更快,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63158480/

相关文章:

java - JDialog 没有出现

java - 在字符串中查找标记后面的括号中的元素

java - @DependsOn 注解的逆

java - 在Java中异步加载基于网络的图像

java安全不允许我使用Jfilechooser打开拍照

Java AWT 文本生成工件

java - 看起来像曲棍球棒的数据公式

java - 未应用 boolean 值的 avro 默认值

java - Swingworker.done() 在完成后卡住 GUI 几秒钟

java - 如何正确读取 awt 文本字段中的数字?