1

我已经到了可以使用Swing代码在 Java中显示我的OpenCloud词云的阶段。它从文件中逐行加载文本,将其吞噬并吐出我想要显示的单词。这将传递给我的云,然后在每次读取和更新一批行时添加它们。目前我正在使用默认布局管理器,但它只是将新单词附加到窗口底部,最终流过窗口底部。这不是我想要的。

我追求的是更像 wordle 的东西:

在此处输入图像描述

理想情况下,我希望它:

  • 瞄准JPanel的中心
  • 径向向外添加单词
  • 允许多个单词“每行”(注意方式希望您有一个并且当前在新行之间的方式。)
  • Trickier:修剪得分低且在达到边缘时不适合的单词。

根据我链接到的解决方案,我将这些添加为 JLabels,并根据每个标签的权重使用字体。

我浏览了所有不同的挥杆布局管理器,但似乎没有一个适合编程。如果我想自定义我的布局,我唯一的选择是使用绝对布局定位,因为我被 Swing 卡住了(是的,几乎完全卡住了)。这似乎是一个难以驯服的野兽。不幸的是,对于那些想要推荐一些很棒的非标准 LayoutManager 的人,我无法将任何包添加到我的开发机器/访问互联网。我认为我需要做的是在页面某处随机添加单词,然后对其他单词执行相同操作并调整它们,使它们不会在窗口边缘相互碰撞。

所以看来我需要知道:

  • 是否有使用标准布局管理器之一进行这种随机/延迟调整布局的“已知”方式?
  • 有没有办法在没有布局管理器的情况下进行这种布局?这更容易吗?
  • 编写自己的布局管理器会更容易吗?这肯定是一项艰巨的任务,不是吗?

抱歉,如果这看起来我只是在寻求一个拼盘上的解决方案,我对 Java 有点陌生(我能说多久!)而且我不知道从哪里开始事情,任何帮助/提示将不胜感激。

4

1 回答 1

3

这是一个使用 WrapLayout 的示例,它似乎可以正常工作:

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

import org.mcavallo.opencloud.Cloud;
import org.mcavallo.opencloud.Tag;

public class TestOpenCloud {

    private static final String[] WORDS = { "art", "australia", "baby", "beach", "birthday", "blue", "bw", "california", "canada", "canon",
            "cat", "chicago", "china", "christmas", "city", "dog", "england", "europe", "family", "festival", "flower", "flowers", "food",
            "france", "friends", "fun", "germany", "holiday", "india", "italy", "japan", "london", "me", "mexico", "music", "nature",
            "new", "newyork", "night", "nikon", "nyc", "paris", "park", "party", "people", "portrait", "sanfrancisco", "sky", "snow",
            "spain", "summer", "sunset", "taiwan", "tokyo", "travel", "trip", "uk", "usa", "vacation", "water", "wedding" };

    protected void initUI() {
        JFrame frame = new JFrame(TestOpenCloud.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JPanel panel = new JPanel(new WrapLayout());
        Cloud cloud = new Cloud();
        final Random random = new Random();
        for (String s : WORDS) {
            for (int i = random.nextInt(50); i > 0; i--) {
                cloud.addTag(s);
            }
        }
        for (Tag tag : cloud.tags()) {
            final JLabel label = new JLabel(tag.getName());
            label.setOpaque(false);
            label.setFont(label.getFont().deriveFont((float) tag.getWeight() * 10));
            panel.add(label);
        }
        JScrollPane scrollPane = new JScrollPane(panel);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        frame.add(scrollPane);
        frame.setSize(500, 550);
        frame.setVisible(true);
        Timer t = new Timer(1000, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                final JLabel label = new JLabel(WORDS[random.nextInt(WORDS.length)]);
                label.setOpaque(false);
                int fontSize = random.nextInt(20) + 8;
                label.setFont(label.getFont().deriveFont((float) fontSize));
                panel.add(label);
                panel.revalidate();
                panel.repaint();
            }
        });
        t.start();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new TestOpenCloud().initUI();
            }
        });
    }

    public class WrapLayout extends FlowLayout {
        private Dimension preferredLayoutSize;

        /**
         * Constructs a new <code>WrapLayout</code> with a left alignment and a default 5-unit horizontal and vertical gap.
         */
        public WrapLayout() {
            super();
        }

        /**
         * Constructs a new <code>FlowLayout</code> with the specified alignment and a default 5-unit horizontal and vertical gap. The value
         * of the alignment argument must be one of <code>WrapLayout</code>, <code>WrapLayout</code>, or <code>WrapLayout</code>.
         * 
         * @param align
         *            the alignment value
         */
        public WrapLayout(int align) {
            super(align);
        }

        /**
         * Creates a new flow layout manager with the indicated alignment and the indicated horizontal and vertical gaps.
         * <p>
         * The value of the alignment argument must be one of <code>WrapLayout</code>, <code>WrapLayout</code>, or <code>WrapLayout</code>.
         * 
         * @param align
         *            the alignment value
         * @param hgap
         *            the horizontal gap between components
         * @param vgap
         *            the vertical gap between components
         */
        public WrapLayout(int align, int hgap, int vgap) {
            super(align, hgap, vgap);
        }

        /**
         * Returns the preferred dimensions for this layout given the <i>visible</i> components in the specified target container.
         * 
         * @param target
         *            the component which needs to be laid out
         * @return the preferred dimensions to lay out the subcomponents of the specified container
         */
        @Override
        public Dimension preferredLayoutSize(Container target) {
            return layoutSize(target, true);
        }

        /**
         * Returns the minimum dimensions needed to layout the <i>visible</i> components contained in the specified target container.
         * 
         * @param target
         *            the component which needs to be laid out
         * @return the minimum dimensions to lay out the subcomponents of the specified container
         */
        @Override
        public Dimension minimumLayoutSize(Container target) {
            Dimension minimum = layoutSize(target, false);
            minimum.width -= getHgap() + 1;
            return minimum;
        }

        /**
         * Returns the minimum or preferred dimension needed to layout the target container.
         * 
         * @param target
         *            target to get layout size for
         * @param preferred
         *            should preferred size be calculated
         * @return the dimension to layout the target container
         */
        private Dimension layoutSize(Container target, boolean preferred) {
            synchronized (target.getTreeLock()) {
                // Each row must fit with the width allocated to the containter.
                // When the container width = 0, the preferred width of the container
                // has not yet been calculated so lets ask for the maximum.

                int targetWidth = target.getSize().width;

                if (targetWidth == 0) {
                    targetWidth = Integer.MAX_VALUE;
                }

                int hgap = getHgap();
                int vgap = getVgap();
                Insets insets = target.getInsets();
                int horizontalInsetsAndGap = insets.left + insets.right + hgap * 2;
                int maxWidth = targetWidth - horizontalInsetsAndGap;

                // Fit components into the allowed width

                Dimension dim = new Dimension(0, 0);
                int rowWidth = 0;
                int rowHeight = 0;

                int nmembers = target.getComponentCount();

                for (int i = 0; i < nmembers; i++) {
                    Component m = target.getComponent(i);

                    if (m.isVisible()) {
                        Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();

                        // Can't add the component to current row. Start a new row.

                        if (rowWidth + d.width > maxWidth) {
                            addRow(dim, rowWidth, rowHeight);
                            rowWidth = 0;
                            rowHeight = 0;
                        }

                        // Add a horizontal gap for all components after the first

                        if (rowWidth != 0) {
                            rowWidth += hgap;
                        }

                        rowWidth += d.width;
                        rowHeight = Math.max(rowHeight, d.height);
                    }
                }

                addRow(dim, rowWidth, rowHeight);

                dim.width += horizontalInsetsAndGap;
                dim.height += insets.top + insets.bottom + vgap * 2;

                // When using a scroll pane or the DecoratedLookAndFeel we need to
                // make sure the preferred size is less than the size of the
                // target container so shrinking the container size works
                // correctly. Removing the horizontal gap is an easy way to do this.

                Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);

                if (scrollPane != null) {
                    dim.width -= hgap + 1;
                }

                return dim;
            }
        }

        /*
         *  A new row has been completed. Use the dimensions of this row
         *  to update the preferred size for the container.
         *
         *  @param dim update the width and height when appropriate
         *  @param rowWidth the width of the row to add
         *  @param rowHeight the height of the row to add
         */
        private void addRow(Dimension dim, int rowWidth, int rowHeight) {
            dim.width = Math.max(dim.width, rowWidth);

            if (dim.height > 0) {
                dim.height += getVgap();
            }

            dim.height += rowHeight;
        }
    }
}
于 2012-10-09T15:24:58.327 回答