Java 当有数千个组件时,使动画速度加快

Java 当有数千个组件时,使动画速度加快,java,swing,awt,edt,componentlistener,Java,Swing,Awt,Edt,Componentlistener,我试图用动画隐藏一个JSplitPane。通过隐藏,我的意思是setDividerLocation(0),因此它的左侧组件是不可见的(从技术上讲,它是可见的,但宽度为零): 然后重新运行上面的示例,您将看到动画不再平稳运行。因此,问题是,是否有可能使其平滑(-ier)? 我不知道如何找到解决方案——如果有的话 根据我的研究,我注册了一个全局组件监听器: Toolkit.getDefaultToolkit() .addAWTEventListener(System.out::println

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

然后重新运行上面的示例,您将看到动画不再平稳运行。因此,问题是,是否有可能使其平滑(-ier)?

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

根据我的研究,我注册了一个全局
组件监听器

Toolkit.getDefaultToolkit()
    .addAWTEventListener(System.out::println, AWTEvent.COMPONENT_EVENT_MASK);
看到了大量正在发生的事件。因此,我认为问题的根源在于为每个组件触发的大量组件事件。此外,似乎具有自定义渲染器的组件(如
JList
-
ListCellRenderer
JTable
-
tablecellenderer
)正在为所有渲染器触发组件事件。例如,如果一个
JList
有30个元素,则只会为其触发30个事件(组件)。对于CardLayout,似乎(这就是我提到它的原因)也在发生“不可见”组件的事件

我知道
60*60
对你来说可能听起来很疯狂,但在实际应用中(我的约1500个),这幅画更重

我知道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.*;
导入java.awt.event.*;
导入javax.swing.*;
公共类SplitPaneTest2{
公共静态布尔值doLayout=true;
公共静态void main(字符串[]args){
SwingUtilities.invokeLater(()->{
JFrame=新JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(新的BorderLayout());
JPanel leftPanel=newjpanel(newborderlayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel=新JPanel(新网格布局(60,60))
{
@凌驾
公共空间布局()
{
if(SplitPaneTest2.doLayout)
super.doLayout();
}
};
对于(int i=0;i<60*60;i++){
右面板。添加(新JLabel(“s”);
}
右面板.setOrder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane=新的JSplitPane(JSplitPane.HORIZONTAL_SPLIT,leftPanel,righpanel);
frame.add(拆分窗格);
JButton button=新JButton(“按我隐藏”);
addActionListener(e->hideWithAnimation(拆分窗格));
leftPanel.add(按钮、边框布局、页面开始);
框架设置最大尺寸(新尺寸(800800));
框架设置尺寸(800800);
frame.setLocationByPlatform(真);
frame.setVisible(true);
});
}
私有静态void隐藏方法(JSplitPane拆分窗格){
SplitPaneTest2.doLayout=false;
最终计时器=新计时器(10,空);
timer.addActionListener(e->{
setDividerLocation(Math.max(0,splitPane.getDividerLocation()-3));
如果(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();
    }

}
import java.awt.*;
导入java.awt.event.*;
导入javax.swing.*;
导入java.awt.image.*;
公共类SplitPaneTest2{
公共静态void main(字符串[]args){
SwingUtilities.invokeLater(()->{
JFrame=新JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(新的BorderLayout());
JPanel leftPanel=newjpanel(newborderlayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel=新JPanel(新网格布局(60,60));
对于(int i=0;i<60*60;i++){
右面板。添加(新JLabel(“s”);
}
右面板.setOrder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane=新的JSplitPane(JSplitPane.HORIZONTAL_SPLIT,leftPanel,righpanel);
frame.add(拆分窗格);
JButton button=新JButton(“按我隐藏”);
addActionListener(e->hideWithAnimation(拆分窗格));
leftPanel.add(按钮、边框布局、页面开始);
框架设置最大尺寸(新尺寸(800800));
框架设置尺寸(800800);
frame.setLocationByPlatform(真);
frame.setVisible(true);
});
}
私有静态void隐藏方法(JSplitPane拆分窗格){
Component right=splitPane.getRightComponent();
维度大小=右。getSize();
BuffereImage bi=新的BuffereImage(size.width、size.height、buffereImage.TYPE\u INT\u ARGB);
图形2d g
    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();
    }

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