JAVA 可折叠垂直布局

JAVA Foldable vertical layout

我在位于 JTabbedPane 的一个选项卡中的 JScollPane 中的另一个 JPanel(我们将其命名为 "CENTER_DECORS")中有 3 个 JPanel(让我们将每个命名为 "BLOCK")。

现在:当我动态更改任何这些 BLOCK-s 的高度时("fold" 它们通过将它们的高度从 150 设置为 20)我希望其他 BLOCK-s 相应地更新它们的垂直位置所以它们仍然会相互堆叠,但截至目前,我在更改(降低)高度的 BLOCK 和下一个之间有 "hole"。

我正在为 CENTER_DECORS JPanel 使用 BoxLayout:

CENTER_DECORS.setLayout(new BoxLayout(CENTER_DECORS, BoxLayout.Y_AXIS));

我改变了一个BLOCK的高度后...

BLOCK.setSize(BLOCK.getWidth(), 20);

...我立即调用此代码:

CENTER_DECORS.repaint();
CENTER_DECORS.validate();
CENTER_DECORS.revalidate();

奇怪的是什么都没有发生,甚至高度也没有改变,但是当我离开时:

CENTER_DECORS.repaint();

...那么它至少会改变高度但不会发生堆叠。

更新: 解决方案必须可以选择在每一行中存储 folded/unfolded 状态,这样当程序启动时它可以 gp 到适当的状态(folded/expanded ).

有没有人知道一个解决方案,使这些 BLOCK-s 在高度改变时仍能垂直粘附在一起?

编辑: 只有在我完全测试了@MadProgrammer 解决方案后,我才意识到重复的答案不是正确的,因为它只允许 1 "block" 是 expanded/opened 一次,我需要它自由扩展,无论有多少 "blocks" + 他的代码以一切 folded/collapsed 开头,我需要它处于正常状态,即 - 大多数时候 -扩展状态 + 我的解决方案不需要使用任何特殊的侦听器(当然,按钮状态的 MouseListener 除外,这是正常的),因此编码更少。

所以,在折腾了一整天之后,我终于根据@MadProgrammer 的方法制作了自己的版本 - 我的代码现在的行为完全符合我的要求(我正在寻找这个解决方案的原因是我的应用程序可能有几十个不同的"block" 在 JPanel 中很难管理,因为它们占据了太多 space 这可能大部分时间都不需要,所以选择将它们折叠起来将是一个很好的选择),所以这个下面的代码实际上是正确的解决方案,请删除 "duplicate" 的东西,因为它不再准确(请参阅下面我的单独回答)。

下面是一个允许您一次展开多个面板的方法示例。

该解决方案包含一个 "ExpandingPanel",它基本上有两个组成部分:

  1. 在 BorderLayout.PAGE_START 中有一个按钮,点击后 expand/collapse 面板
  2. 在 BorderLayout.CENTER 中将被切换的组件 visible/invisible

使用垂直 BoxLayout 将 ExpandingPanel 添加到父容器,因此垂直尺寸会随着每个 ExpandingPanel 的状态切换而变化。

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

public class ExpandingPanel2 extends JPanel
{
    private JPanel expanding;

    public ExpandingPanel2(String text, Component expanding)
    {
        setLayout( new BorderLayout() );

        JButton button = new JButton( text );
        add(button, BorderLayout.PAGE_START);

        expanding.setVisible(false);
        add(expanding, BorderLayout.CENTER);

        button.addActionListener( new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                expanding.setVisible( !expanding.isVisible() );

                revalidate();
                repaint();
            }
        });
    }

    @Override
    public Dimension getMaximumSize()
    {
        Dimension size = getPreferredSize();
        size.width = Integer.MAX_VALUE;

        return size;
    }

    private static void createAndShowGUI()
    {
        Box content = Box.createVerticalBox();

        content.add( createExpandingPanel("Red", Color.RED, 50));
        content.add( createExpandingPanel("Blue", Color.BLUE, 100));
        content.add( createExpandingPanel("Green", Color.GREEN, 200));

        JFrame frame = new JFrame("Expanding Panel");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//        frame.add( content);
        frame.add( new JScrollPane(content));
        frame.setLocationByPlatform( true );
        frame.setSize(200, 300);
        frame.setVisible( true );
    }

    private static ExpandingPanel2 createExpandingPanel(String text, Color background, int size)
    {
        JPanel content = new JPanel();
        content.setBackground(background);
        content.setPreferredSize(new Dimension(100, size));

        return new ExpandingPanel2(text, content);
    }


    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
}

这正是我想要的 - 这是我的 question/problem 的确切解决方案(记住:这只是我完整代码的一小部分,其中有无数其他东西被省略了,所以看起来那里有很多不必要的东西,但我有理由把它放在那里,我不会从头开始创建示例 "just-like-that" - 如果有任何东西你觉得没必要就忽略它,或者如果你不能忍受就删除它,我不能那样做,因为它需要我完全重写我在我的实际代码中使用的东西和其他单独的量子 class是的,一旦引入我原来的庞大程序代码,它就会失去它的功能(破坏我的程序中的代码)——简单地说:你在演示中看到的一切都必须是它的样子,虽然它对你来说可能看起来不必要 +它对代码功能没有任何影响!

测试我的代码(我使用的是 NetBeans 8.0.2):

  • 开始新的 NetBeans 项目
  • 创建名为 "foldingcomponentsdemo"
  • 的新 class
  • 复制下面的代码
  • 粘贴到那里
  • 运行(编译)它

我不是来这里取悦某些程序员的感受的,我是针对我提出的问题发布工作解决方案。

package foldingcomponentsdemo;

import static foldingcomponentsdemo.Constants.BLOCK_COLOR_BG;
import static foldingcomponentsdemo.Constants.BLOCK_H;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_BG_COLOR;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_BORDER_COLOR;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_FG_COLOR;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_H;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_LABEL_FG_COLOR;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_LABEL_FONT_SIZE;
import static foldingcomponentsdemo.Constants.BUTTON_FOLD_COLOR_BG;
import static foldingcomponentsdemo.Constants.BUTTON_FOLD_COLOR_BG_DOWN;
import static foldingcomponentsdemo.Constants.BUTTON_FOLD_COLOR_BG_OVER;
import static foldingcomponentsdemo.Constants.BUTTON_WH;
import static foldingcomponentsdemo.Constants.GAP;
import static foldingcomponentsdemo.Constants.INNER_W;
import static foldingcomponentsdemo.Constants.LABEL_TEXT;
import static foldingcomponentsdemo.Constants.MAINWINDOW_H;
import static foldingcomponentsdemo.Constants.MAINWINDOW_W;
import static foldingcomponentsdemo.Constants.MARGIN;
import static foldingcomponentsdemo.Constants.NUMBER_OF_BLOCKS;
import static foldingcomponentsdemo.FoldingComponentsDemo.getScene;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

class Constants {

    public static final int MARGIN = 2;
    public static final int BLOCK_H = 100;
    public static final int NUMBER_OF_BLOCKS = 5;
    public static final int SCROLLER_HOR_WIDTH = 26;
    public static final int MAINWINDOW_W = 300;
    public static final int MAINWINDOW_H = BLOCK_H * (NUMBER_OF_BLOCKS - 2);
    public static final int INNER_W = MAINWINDOW_W - SCROLLER_HOR_WIDTH;
    public static final int BLOCK_HEADER_H = 20;
    public static final int BUTTON_WH = BLOCK_HEADER_H - (MARGIN * 2);
    public static final int BLOCK_HEADER_LABEL_FONT_SIZE = 11;
    public static final Color BLOCK_HEADER_BG_COLOR = Color.decode("#000000");
    public static final Color BLOCK_HEADER_FG_COLOR = Color.decode("#cccccc");
    public static final Color BLOCK_HEADER_BORDER_COLOR = Color.decode("#777777");
    public static final Color BUTTON_FOLD_COLOR_BG = Color.decode("#ff0000");
    public static final Color BUTTON_FOLD_COLOR_BG_OVER = Color.decode("#999999");
    public static final Color BUTTON_FOLD_COLOR_BG_DOWN = Color.decode("#cccccc");
    public static final Color BLOCK_COLOR_BG = Color.decode("#555555");
    public static final Color BLOCK_HEADER_LABEL_FG_COLOR = Color.decode("#ffffff");
    public static final String GAP = " ";
    public static final String LABEL_TEXT = "click red button on right to (un)fold";

}

public class FoldingComponentsDemo {

    private static Block block;
    private static final FoldingContainer SCENE = new FoldingContainer();
    private static final JScrollPane SCROLLER = new JScrollPane(SCENE);

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            createGUI();
        });
    }

    private static void createGUI() {

        for (int i = 0; i < NUMBER_OF_BLOCKS; i++) {
            block = new Block("BLOCK_" + i, INNER_W, BLOCK_H);
            SCENE.add(block);
        }

        JFrame frame = new JFrame("Folding components");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(SCROLLER);
        frame.setSize(MAINWINDOW_W, MAINWINDOW_H);
        frame.setVisible(true);
    }

    public static FoldingContainer getScene() {
        return SCENE;
    }
}

class Block extends JPanel {

    private static Dimension BLOCK_SIZE;
    public boolean isFolded;

    public Block(String name, int innerW, int blockHeight) {

        this.isFolded = false;
        BLOCK_SIZE = new Dimension((int) innerW, blockHeight);

        setName(name);
        setLayout(null);
        setPreferredSize(BLOCK_SIZE);
        setMinimumSize(BLOCK_SIZE);
        setMaximumSize(BLOCK_SIZE);
        setBackground(BLOCK_COLOR_BG);
        setBorder(BorderFactory.createLineBorder(Color.black, 1, false));
        add(new Header());
    }
}

class Label extends JLabel {

    public Label(String text, int innerW) {

        setText(text);
        setSize(innerW, BLOCK_HEADER_H);
        setForeground(BLOCK_HEADER_LABEL_FG_COLOR);
        setFont(new Font("Tahoma", Font.PLAIN, BLOCK_HEADER_LABEL_FONT_SIZE));
    }
}

class Header extends JPanel {

    public Header() {
        setLayout(null);
        setSize(INNER_W, BLOCK_HEADER_H);
        setBackground(BLOCK_HEADER_BG_COLOR);
        setForeground(BLOCK_HEADER_FG_COLOR);
        setBorder(BorderFactory.createLineBorder(BLOCK_HEADER_BORDER_COLOR, 1, false));

        add(new Label(GAP + LABEL_TEXT, INNER_W));

        JButton BUTTON_FOLD = new JButton();
        BUTTON_FOLD.setBounds(INNER_W - (BUTTON_WH * 2), MARGIN, BUTTON_WH, BUTTON_WH);
        BUTTON_FOLD.setMargin(new Insets(0, 0, 0, 0));
        BUTTON_FOLD.setFocusPainted(false);
        BUTTON_FOLD.setBackground(BUTTON_FOLD_COLOR_BG);
        BUTTON_FOLD.setCursor(new Cursor(Cursor.HAND_CURSOR));
        BUTTON_FOLD.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                BUTTON_FOLD.setBackground(BUTTON_FOLD_COLOR_BG_OVER);
            }

            public void mouseExited(MouseEvent e) {
                BUTTON_FOLD.setBackground(BUTTON_FOLD_COLOR_BG);
            }

            public void mousePressed(MouseEvent e) {
                BUTTON_FOLD.setBackground(BUTTON_FOLD_COLOR_BG_DOWN);
            }

            public void mouseClicked(MouseEvent e) {
                BUTTON_FOLD.setBackground(BUTTON_FOLD_COLOR_BG_DOWN);
                Block blck = (Block) BUTTON_FOLD.getParent().getParent();
                blck.isFolded = !blck.isFolded;
                ((FoldingLayout) getScene().getLayout()).setExpanded(blck);
            }
        });

        add(BUTTON_FOLD);
    }
}

class FoldingContainer extends JPanel {

    FoldingLayout layout;

    public FoldingContainer() {
        setName("Folding container");
        layout = new FoldingLayout();
        setLayout(layout);
    }
}

class FoldingLayout implements LayoutManager {

    private Component block;
    private String initialised;

    public void setExpanded(Component BLOCK) {
        this.block = BLOCK;
        this.initialised = "yes";
        layoutContainer(block.getParent());
    }

    public Component getExpanded() {
        return block;
    }

    @Override
    public void addLayoutComponent(String name, Component comp) {
    }

    @Override
    public void removeLayoutComponent(Component comp) {
    }

    @Override
    public Dimension preferredLayoutSize(Container cont) {
        return minimumLayoutSize(cont);
    }

    @Override
    public Dimension minimumLayoutSize(Container cont) {
        int width = INNER_W;
        int height = 0;
        for (Component comp : cont.getComponents()) {
            height += comp.getHeight();
        }
        return new Dimension(width, height);
    }

    @Override
    public void layoutContainer(Container cont) {
        Insets insets = cont.getInsets();
        int x = insets.left;
        int y = insets.top;

        for (Component comp : cont.getComponents()) {
            if (initialised == null) {
                comp.setSize(comp.getMinimumSize().width, comp.getMinimumSize().height);
            } else {
                if (((Block) comp).isFolded) {
                    comp.setSize(comp.getMinimumSize().width, BLOCK_HEADER_H);
                } else {
                    comp.setSize(comp.getMinimumSize().width, comp.getMinimumSize().height);
                }
            }
            comp.setLocation(x, y);
            y += comp.getHeight();
        }

        // update for scroller
        cont.getParent().revalidate();
    }
}