微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 WrapLayout/FlowLayout 时是否可以水平对齐组件?

如何解决使用 WrapLayout/FlowLayout 时是否可以水平对齐组件?

我使用 WrapLayout 其中 extends FlowLayout

现在,我有了这个 GUI:

enter image description here

我想要的是这个:

enter image description here

我尝试了一些类似的东西:label.setVerticalAlignment(JLabel.TOP); 但布局似乎不尊重它。我猜这个行为是从 FlowLayout 继承的?

完整代码

public class WrapLayoutExample {
    public static void main(String[] args) {
        SwingUtilities.invokelater(WrapLayoutExample::runGui);
    }

    private static void runGui() {
        JFrame frame = new JFrame("A");
        frame.setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel(new WrapLayout());

        JLabel l = new JLabel("CCC");
        l.setBorder(BorderFactory.createLineBorder(Color.red,10));
        l.setVerticalAlignment(JLabel.TOP);
        panel.add(l);

        l = new JLabel("BBBB");
        l.setBorder(BorderFactory.createLineBorder(Color.red,20));
        panel.add(l);

        frame.setLayout(new BorderLayout());
        frame.add(new JScrollPane(panel));

        frame.pack();
        frame.setVisible(true);
    }
}

解决方法

来自@camickr 在我的问题中的评论:

您应该能够编写自己的布局管理器。只需复制 FlowLayout 并替换在行内居中组件的逻辑,将组件定位在顶部。

在 FlowLayout 中,在 moveComponents 方法中,有这样一行:

cy = y + (height - m.height) / 2;

将行更改为:

cy = y;

实现我想要的。

它可能会破坏 flowLayout.setAlignOnBaseline 功能。不过反正我也不用。

布局代码(WrapLayout 和自定义 FlowLayout 组合):

public class WrapAndAlignHorizontallyTopLayout implements LayoutManager,java.io.Serializable {

    /**
     * This value indicates that each row of components should be left-justified.
     */
    public static final int LEFT = 0;

    /**
     * This value indicates that each row of components should be centered.
     */
    public static final int CENTER = 1;

    /**
     * This value indicates that each row of components should be right-justified.
     */
    public static final int RIGHT = 2;

    /**
     * This value indicates that each row of components should be justified to the
     * leading edge of the container's orientation,for example,to the left in
     * left-to-right orientations.
     *
     * @see java.awt.Component#getComponentOrientation
     * @see java.awt.ComponentOrientation
     * @since 1.2
     */
    public static final int LEADING = 3;

    /**
     * This value indicates that each row of components should be justified to the
     * trailing edge of the container's orientation,to the right in
     * left-to-right orientations.
     *
     * @see java.awt.Component#getComponentOrientation
     * @see java.awt.ComponentOrientation
     * @since 1.2
     */
    public static final int TRAILING = 4;

    /**
     * <code>align</code> is the property that determines how each row distributes
     * empty space. It can be one of the following values:
     * <ul>
     * <li><code>LEFT</code>
     * <li><code>RIGHT</code>
     * <li><code>CENTER</code>
     * </ul>
     *
     * @serial
     * @see #getAlignment
     * @see #setAlignment
     */
    int align; // This is for 1.1 serialization compatibility

    /**
     * <code>newAlign</code> is the property that determines how each row
     * distributes empty space for the Java 2 platform,v1.2 and greater. It can be
     * one of the following three values:
     * <ul>
     * <li><code>LEFT</code>
     * <li><code>RIGHT</code>
     * <li><code>CENTER</code>
     * <li><code>LEADING</code>
     * <li><code>TRAILING</code>
     * </ul>
     *
     * @serial
     * @since 1.2
     * @see #getAlignment
     * @see #setAlignment
     */
    int newAlign; // This is the one we actually use

    /**
     * The flow layout manager allows a seperation of components with gaps. The
     * horizontal gap will specify the space between components and between the
     * components and the borders of the <code>Container</code>.
     *
     * @serial
     * @see #getHgap()
     * @see #setHgap(int)
     */
    int hgap;

    /**
     * The flow layout manager allows a seperation of components with gaps. The
     * vertical gap will specify the space between rows and between the the rows and
     * the borders of the <code>Container</code>.
     *
     * @serial
     * @see #getHgap()
     * @see #setHgap(int)
     */
    int vgap;

    /**
     * If true,components will be aligned on their baseline.
     */
    private boolean alignOnBaseline;

    /*
     * JDK 1.1 serialVersionUID
     */
    private static final long serialVersionUID = -7262534875583282631L;

    /**
     * Constructs a new <code>FlowLayout</code> with a centered alignment and a
     * default 5-unit horizontal and vertical gap.
     */
    public WrapAndAlignHorizontallyTopLayout() {
        this(CENTER,5,5);
    }

    /**
     * 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>FlowLayout.LEFT</code>,* <code>FlowLayout.RIGHT</code>,<code>FlowLayout.CENTER</code>,* <code>FlowLayout.LEADING</code>,or <code>FlowLayout.TRAILING</code>.
     * 
     * @param align the alignment value
     */
    public WrapAndAlignHorizontallyTopLayout(int align) {
        this(align,5);
    }

    /**
     * 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>FlowLayout.LEFT</code>,<code>FlowLayout.RIGHT</code>,* <code>FlowLayout.CENTER</code>,<code>FlowLayout.LEADING</code>,or
     * <code>FlowLayout.TRAILING</code>.
     * 
     * @param align the alignment value
     * @param hgap  the horizontal gap between components and between the components
     *              and the borders of the <code>Container</code>
     * @param vgap  the vertical gap between components and between the components
     *              and the borders of the <code>Container</code>
     */
    public WrapAndAlignHorizontallyTopLayout(int align,int hgap,int vgap) {
        this.hgap = hgap;
        this.vgap = vgap;
        setAlignment(align);
    }

    /**
     * Gets the alignment for this layout. Possible values are
     * <code>FlowLayout.LEFT</code>,or
     * <code>FlowLayout.TRAILING</code>.
     * 
     * @return the alignment value for this layout
     * @see java.awt.FlowLayout#setAlignment
     * @since JDK1.1
     */
    public int getAlignment() {
        return newAlign;
    }

    /**
     * Sets the alignment for this layout. Possible values are
     * <ul>
     * <li><code>FlowLayout.LEFT</code>
     * <li><code>FlowLayout.RIGHT</code>
     * <li><code>FlowLayout.CENTER</code>
     * <li><code>FlowLayout.LEADING</code>
     * <li><code>FlowLayout.TRAILING</code>
     * </ul>
     * 
     * @param align one of the alignment values shown above
     * @see #getAlignment()
     * @since JDK1.1
     */
    public void setAlignment(int align) {
        this.newAlign = align;

        // this.align is used only for serialization compatibility,// so set it to a value compatible with the 1.1 version
        // of the class

        switch (align) {
        case LEADING:
            this.align = LEFT;
            break;
        case TRAILING:
            this.align = RIGHT;
            break;
        default:
            this.align = align;
            break;
        }
    }

    /**
     * Gets the horizontal gap between components and between the components and the
     * borders of the <code>Container</code>
     *
     * @return the horizontal gap between components and between the components and
     *         the borders of the <code>Container</code>
     * @see java.awt.FlowLayout#setHgap
     * @since JDK1.1
     */
    public int getHgap() {
        return hgap;
    }

    /**
     * Sets the horizontal gap between components and between the components and the
     * borders of the <code>Container</code>.
     *
     * @param hgap the horizontal gap between components and between the components
     *             and the borders of the <code>Container</code>
     * @see java.awt.FlowLayout#getHgap
     * @since JDK1.1
     */
    public void setHgap(int hgap) {
        this.hgap = hgap;
    }

    /**
     * Gets the vertical gap between components and between the components and the
     * borders of the <code>Container</code>.
     *
     * @return the vertical gap between components and between the components and
     *         the borders of the <code>Container</code>
     * @see java.awt.FlowLayout#setVgap
     * @since JDK1.1
     */
    public int getVgap() {
        return vgap;
    }

    /**
     * Sets the vertical gap between components and between the components and the
     * borders of the <code>Container</code>.
     *
     * @param vgap the vertical gap between components and between the components
     *             and the borders of the <code>Container</code>
     * @see java.awt.FlowLayout#getVgap
     * @since JDK1.1
     */
    public void setVgap(int vgap) {
        this.vgap = vgap;
    }

    /**
     * Sets whether or not components should be vertically aligned along their
     * baseline. Components that do not have a baseline will be centered. The
     * default is false.
     *
     * @param alignOnBaseline whether or not components should be vertically aligned
     *                        on their baseline
     * @since 1.6
     */
    public void setAlignOnBaseline(boolean alignOnBaseline) {
        this.alignOnBaseline = alignOnBaseline;
    }

    /**
     * Returns true if components are to be vertically aligned along their baseline.
     * The default is false.
     *
     * @return true if components are to be vertically aligned along their baseline
     * @since 1.6
     */
    public boolean getAlignOnBaseline() {
        return alignOnBaseline;
    }

    /**
     * Adds the specified component to the layout. Not used by this class.
     * 
     * @param name the name of the component
     * @param comp the component to be added
     */
    @Override
    public void addLayoutComponent(String name,Component comp) {
    }

    /**
     * Removes the specified component from the layout. Not used by this class.
     * 
     * @param comp the component to remove
     * @see java.awt.Container#removeAll
     */
    @Override
    public void removeLayoutComponent(Component comp) {
    }

    /**
     * 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;
            Container container = target;

            while (container.getSize().width == 0 && container.getParent() != null) {
                container = container.getParent();
            }

            targetWidth = container.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,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 containter 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 && target.isValid()) {
                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;
    }

    /**
     * Centers the elements in the specified row,if there is any slack.
     * 
     * @param target      the component which needs to be moved
     * @param x           the x coordinate
     * @param y           the y coordinate
     * @param width       the width dimensions
     * @param height      the height dimensions
     * @param rowStart    the beginning of the row
     * @param rowEnd      the the ending of the row
     * @param useBaseline Whether or not to align on baseline.
     * @param ascent      Ascent for the components. This is only valid if
     *                    useBaseline is true.
     * @param descent     Ascent for the components. This is only valid if
     *                    useBaseline is true.
     * @return actual row height
     */
    private int moveComponents(Container target,int x,int y,int width,int height,int rowStart,int rowEnd,boolean ltr,boolean useBaseline,int[] ascent,int[] descent) {
        switch (newAlign) {
        case LEFT:
            x += ltr ? 0 : width;
            break;
        case CENTER:
            x += width / 2;
            break;
        case RIGHT:
            x += ltr ? width : 0;
            break;
        case LEADING:
            break;
        case TRAILING:
            x += width;
            break;
        }
        int maxAscent = 0;
        int nonbaselineHeight = 0;
        int baselineOffset = 0;
        if (useBaseline) {
            int maxDescent = 0;
            for (int i = rowStart; i < rowEnd; i++) {
                Component m = target.getComponent(i);
                if (m.isVisible()) {
                    if (ascent[i] >= 0) {
                        maxAscent = Math.max(maxAscent,ascent[i]);
                        maxDescent = Math.max(maxDescent,descent[i]);
                    } else {
                        nonbaselineHeight = Math.max(m.getHeight(),nonbaselineHeight);
                    }
                }
            }
            height = Math.max(maxAscent + maxDescent,nonbaselineHeight);
            baselineOffset = (height - maxAscent - maxDescent) / 2;
        }
        for (int i = rowStart; i < rowEnd; i++) {
            Component m = target.getComponent(i);
            if (m.isVisible()) {
                int cy;
                if (useBaseline && ascent[i] >= 0) {
                    cy = y + baselineOffset + maxAscent - ascent[i];
                } else {
                    cy = y;
                }
                if (ltr) {
                    m.setLocation(x,cy);
                } else {
                    m.setLocation(target.getWidth() - x - m.getWidth(),cy);
                }
                x += m.getWidth() + hgap;
            }
        }
        return height;
    }

    /**
     * Lays out the container. This method lets each <i>visible</i> component take
     * its preferred size by reshaping the components in the target container in
     * order to satisfy the alignment of this <code>FlowLayout</code> object.
     *
     * @param target the specified component being laid out
     * @see Container
     * @see java.awt.Container#doLayout
     */
    @Override
    public void layoutContainer(Container target) {
        synchronized (target.getTreeLock()) {
            Insets insets = target.getInsets();
            int maxwidth = target.getWidth() - (insets.left + insets.right + hgap * 2);
            int nmembers = target.getComponentCount();
            int x = 0,y = insets.top + vgap;
            int rowh = 0,start = 0;

            boolean ltr = target.getComponentOrientation().isLeftToRight();

            boolean useBaseline = getAlignOnBaseline();
            int[] ascent = null;
            int[] descent = null;

            if (useBaseline) {
                ascent = new int[nmembers];
                descent = new int[nmembers];
            }

            for (int i = 0; i < nmembers; i++) {
                Component m = target.getComponent(i);
                if (m.isVisible()) {
                    Dimension d = m.getPreferredSize();
                    m.setSize(d.width,d.height);

                    if (useBaseline) {
                        int baseline = m.getBaseline(d.width,d.height);
                        if (baseline >= 0) {
                            ascent[i] = baseline;
                            descent[i] = d.height - baseline;
                        } else {
                            ascent[i] = -1;
                        }
                    }
                    if ((x == 0) || ((x + d.width) <= maxwidth)) {
                        if (x > 0) {
                            x += hgap;
                        }
                        x += d.width;
                        rowh = Math.max(rowh,d.height);
                    } else {
                        rowh = moveComponents(target,insets.left + hgap,y,maxwidth - x,rowh,start,i,ltr,useBaseline,ascent,descent);
                        x = d.width;
                        y += vgap + rowh;
                        rowh = d.height;
                        start = i;
                    }
                }
            }
            moveComponents(target,nmembers,descent);
        }
    }

    //
    // the internal serial version which says which version was written
    // - 0 (default) for versions before the Java 2 platform,v1.2
    // - 1 for version >= Java 2 platform v1.2,which includes "newAlign" field
    //
    private static final int currentSerialVersion = 1;
    /**
     * This represent the <code>currentSerialVersion</code> which is bein used. It
     * will be one of two values : <code>0</code> versions before Java 2 platform
     * v1.2.. <code>1</code> versions after Java 2 platform v1.2..
     *
     * @serial
     * @since 1.2
     */
    private int serialVersionOnStream = currentSerialVersion;

    /**
     * Reads this object out of a serialization stream,handling objects written by
     * older versions of the class that didn't contain all of the fields we use
     * now..
     */
    private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException {
        stream.defaultReadObject();

        if (serialVersionOnStream < 1) {
            // "newAlign" field wasn't present,so use the old "align" field.
            setAlignment(this.align);
        }
        serialVersionOnStream = currentSerialVersion;
    }

    /**
     * Returns a string representation of this <code>FlowLayout</code> object and
     * its values.
     * 
     * @return a string representation of this layout
     */
    @Override
    public String toString() {
        String str = "";
        switch (align) {
        case LEFT:
            str = ",align=left";
            break;
        case CENTER:
            str = ",align=center";
            break;
        case RIGHT:
            str = ",align=right";
            break;
        case LEADING:
            str = ",align=leading";
            break;
        case TRAILING:
            str = ",align=trailing";
            break;
        }
        return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + str + "]";
    }

}

示例代码:

public class WrapLayoutExample {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(WrapLayoutExample::runGui);
    }

    private static void runGui() {
        JFrame frame = new JFrame("A");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel = new JPanel(new WrapAndAlignHorizontallyTopLayout());

        JLabel l = new JLabel("CCC");
        l.setBorder(BorderFactory.createLineBorder(Color.red,10));
        l.setVerticalAlignment(JLabel.TOP);

        panel.add(l);

        for (int i = 0; i < 44; i++) {
            l = new JLabel("BBBB");
            l.setBorder(BorderFactory.createLineBorder(Color.red,(int) (Math.random() * 50)));
            panel.add(l);
        }

        frame.setSize(300,300);
        frame.setLayout(new BorderLayout());
        frame.add(new JScrollPane(panel));
        frame.setVisible(true);
    }
}

结果:

enter image description here

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。