在Swing中布局键盘

6
我一直在尝试复制这个布局: enter image description here 现在我做出来的是这样的: enter image description here 使用的技术为:
import javax.swing.*;
import java.awt.*;

public class Keyb {

    private JFrame f = new JFrame("Keyboard");

    private JPanel keyboard = new JPanel();

    private static final String[][] key = {
        {"`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Backspace"},
        {"Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\"},
        {"Caps", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "Enter"},
        {"Shift", "Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "Shift", "\u2191"},
        {" ", "\u2190", "\u2193", "\u2192"}
    };

    public Keyb() {
        keyboard.setLayout(new GridBagLayout());

        JPanel pRow;
        GridBagConstraints c = new GridBagConstraints();
        c.anchor = GridBagConstraints.WEST;
        c.weightx = 1d;

        for (int row = 0; row < key.length; ++row) {
            pRow = new JPanel(new GridBagLayout());

            c.gridy = row;

            for (int col = 0; col < key[row].length; ++col)
                pRow.add(new JButton(key[row][col]));

            keyboard.add(pRow, c);
        }

        f.add(keyboard);
    }

    public void launch() {
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.pack();
        f.setVisible(true);
    }

    public static void main(String[] args) {
        Keyb ui = new Keyb();
        ui.launch();
    }
}

如何调整按钮的大小并使它们完美地对齐,尤其是 Tab、Caps、Shift、Backspace、Enter 和空格键,是否可以不使用 set?Size 方法?

其他布局管理器有更好的方法吗?

作为 Java 新手,欢迎提出其他建议。


4
作为起点,类似于 这里 的内容可能会有所帮助。 - MadProgrammer
好的,TabCaps 键位于 1.5 的网格宽度,但是网格宽度是一个整数。 - user2570380
1
你可以有点“作弊”,让第一个键具有gridwidth1,其余键具有gridwidth2,这样可以允许组件“重叠”,有点像... - MadProgrammer
必须要这么“hacky”吗?此外,我该怎么做才能增加垂直高度? - user2570380
你有更好的解决方案吗? - MadProgrammer
你认为如果我不会,我会在这里吗? - user2570380
2个回答

10
当一切都失败了,就自己动手写吧...
这里使用了一个自定义的布局管理器,它定义了一个“基本”网格,但允许给定行中的组件扩展到以下列的某些部分...
默认单元格大小由可用组件的最大宽度/高度定义,不会超出自己的列,使事情更加均匀。
目前输出被锚定在左上角,但我相信你可以计算出需要的x/y偏移量来使其居中 ;)
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.LayoutManager2;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Keyb {

    private static final Key[][] keys = new Key[][]{
        {
            createKey("`", 0, 0),
            createKey("1", 0, 1),
            createKey("2", 0, 2),
            createKey("3", 0, 3),
            createKey("4", 0, 4),
            createKey("5", 0, 5),
            createKey("6", 0, 6),
            createKey("7", 0, 7),
            createKey("8", 0, 8),
            createKey("9", 0, 9),
            createKey("0", 0, 10),
            createKey("-", 0, 11),
            createKey("=", 0, 12),
            createKey("Backspace", 0, 13, 2d)},
        {
            createKey("Tab", 1, 0, 1.5d),
            createKey("W", 1, 2),
            createKey("E", 1, 3),
            createKey("R", 1, 4),
            createKey("T", 1, 5),
            createKey("Y", 1, 6),
            createKey("U", 1, 7),
            createKey("I", 1, 8),
            createKey("O", 1, 9),
            createKey("P", 1, 10),
            createKey("[", 1, 11),
            createKey("]", 1, 12),
            createKey("\\", 1, 13)
        },
        {
            createKey("Caps", 2, 0, 1.5d),
            createKey("A", 2, 2),
            createKey("S", 2, 3),
            createKey("D", 2, 4),
            createKey("F", 2, 5),
            createKey("G", 2, 6),
            createKey("H", 2, 7),
            createKey("J", 2, 8),
            createKey("K", 2, 9),
            createKey("L", 2, 10),
            createKey(";", 2, 11),
            createKey("'", 2, 12),     
            createKey("Enter", 2, 13, 2d)
        },
        {
            createKey("Shift", 3, 0, 2d),
            createKey("Z", 3, 2),
            createKey("X", 3, 3),
            createKey("C", 3, 4),
            createKey("V", 3, 5),
            createKey("B", 3, 6),
            createKey("N", 3, 7),
            createKey("M", 3, 8),
            createKey(",", 3, 9),
            createKey(".", 3, 10),
            createKey("/", 3, 11),
            createKey("fill", 3, 12, 0.5d),
            createKey("\u2191", 3, 13),
        },
        {
            createKey("fill", 4, 0, 4d),
            createKey(" ", 4, 1, 6d),
            createKey("fill", 4, 2, 1.5d),
            createKey("\u2190", 4, 3),
            createKey("\u2193", 4, 4),
            createKey("\u2192", 4, 5),
        },
    };

    public static void main(String[] args) {
        new Keyb();
    }

    public Keyb() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new KeyBoardLayout());
            for (int row = 0; row < keys.length; row++) {
                for (int col = 0; col < keys[row].length; col++) {
                    Key key = keys[row][col];
                    add(createButton(key.getText()), key.getKeyConstraint());
                }
            }
        }

        protected JComponent createButton(String text) {
            JComponent comp = null;
            if (text == null || text.equalsIgnoreCase("fill")) {
                comp = new JLabel();
            } else {
                comp = new JButton(text);
            }
            return comp;
        }

    }

    public static Key createKey(String text, int x, int y, double span) {
        return new Key(text).setKeyConstraint(new KeyConstraint(x, y, span));
    }

    public static Key createKey(String text, int x, int y) {
        return new Key(text).setKeyConstraint(new KeyConstraint(x, y));
    }

    public static class Key {

        private String text;
        private KeyConstraint keyConstraint;

        public Key(String text) {
            this.text = text;
        }

        public String getText() {
            return text;
        }

        public Key setKeyConstraint(KeyConstraint keyConstraint) {
            this.keyConstraint = keyConstraint;
            return this;
        }

        public KeyConstraint getKeyConstraint() {
            return keyConstraint;
        }

    }

    public static class KeyConstraint {

        public int row, column;
        public double span = 1d;

        public KeyConstraint(int row, int column) {
            this.row = row;
            this.column = column;
        }

        public KeyConstraint(int row, int column, double span) {
            this.row = row;
            this.column = column;
            this.span = span;
        }

    }

    public class KeyBoardLayout implements LayoutManager2 {

        private Map<Component, KeyConstraint> mapComponents;
        private Map<KeyConstraint, Component> mapConstraints;
        private Matrix<Integer, List<JComponent>> matrix = new Matrix<>(); // Virtual grid...

        private Dimension gridSize;

        public KeyBoardLayout() {
            mapComponents = new HashMap<>(25);
            mapConstraints = new HashMap<>(25);
        }

        @Override
        public void addLayoutComponent(String name, Component comp) {
            throw new UnsupportedOperationException("addLayoutComponent(String, Comp) is not supported");
        }

        @Override
        public void removeLayoutComponent(Component comp) {
            KeyConstraint kc = mapComponents.get(comp);
            mapComponents.remove(comp);
            if (kc != null) {
                mapConstraints.remove(kc);
                getCellContents(matrix, kc).remove(comp);
            }
        }

        @Override
        public Dimension preferredLayoutSize(Container parent) {
            int rowHeight = getRowHeight();
            Dimension size = new Dimension();
            size.width = getMaxRowWidth();
            size.height = rowHeight * matrix.getRowCount();
            return size;
        }

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

        protected List<JComponent> getCellContents(Matrix matrix, KeyConstraint constraint) {
            return getCellContents(matrix, constraint.column, constraint.row);
        }

        protected List<JComponent> getCellContents(Matrix<Integer, List<JComponent>> matrix, int col, int row) {
            if (!matrix.contains(col, row)) {
                matrix.add(col, row, new ArrayList<>());
            }
            return matrix.get(col, row);
        }

        protected Dimension getGridSize() {
            if (gridSize == null) {
                int maxCellWidth = 0;
                int maxCellHeight = 0;
                for (int row = 0; row < matrix.getRowCount(); row++) {
                    for (int col = 0; col < matrix.getColumnCount(); col++) {
                        List<JComponent> cell = getCellContents(matrix, col, row);
                        int cellWidth = 0;
                        int cellHeight = 0;
                        for (JComponent comp : cell) {
                            KeyConstraint kc = mapComponents.get(comp);
                            if (kc.span == 1) {
                                cellWidth = Math.max(cellWidth, comp.getPreferredSize().width);
                            }
                            cellHeight = Math.max(cellHeight, comp.getPreferredSize().height);
                        }
                        maxCellWidth = Math.max(cellWidth, maxCellWidth);
                        maxCellHeight = Math.max(cellHeight, maxCellHeight);
                    }
                }
                gridSize = new Dimension(maxCellWidth, maxCellHeight);
            }
            return gridSize;
        }

        protected int getRowHeight() {
            Dimension size = getGridSize();
            return size.height;
        }

        protected int getRowWidth(int row) {
            int rowWidth = 0;
            for (int col = 0; col < matrix.getColumnCount(); col++) {
                Dimension size = getCellSize(col, row);
                rowWidth += size.width;
            }
            return rowWidth;
        }

        protected int getMaxRowWidth() {
            int rowWidth = 0;
            for (int row = 0; row < matrix.getRowCount(); row++) {
                rowWidth = Math.max(getRowWidth(row), rowWidth);
            }
            return rowWidth;
        }

        protected int getColumnWidth(int col) {
            int width = 0;
            for (int row = 0; row < matrix.getRowCount(); row++) {

                Dimension size = getCellSize(col, row);
                width = Math.max(size.width, width);

            }
            return width;
        }

        protected Dimension getCellSize(int col, int row) {
            List<JComponent> comps = matrix.get(col, row);
            Dimension size = new Dimension();
            size.height = getRowHeight();
            for (JComponent comp : comps) {
                Dimension subSize = getCellSize(col, row, comp);
                size.width = Math.max(size.width, subSize.width);
            }

            return size;
        }

        protected Dimension getCellSize(int col, int row, JComponent comp) {
            List<JComponent> comps = matrix.get(col, row);
            Dimension size = new Dimension();
            size.height = getRowHeight();
            int defaultWidth = getGridSize().width;
            KeyConstraint kc = mapComponents.get(comp);
            if (kc.span == 1) {
                size.width = defaultWidth;
            } else {
                int totalWidth = (int)Math.round(defaultWidth * kc.span);
                size.width = totalWidth;
            }

            return size;
        }

        @Override
        public void layoutContainer(Container parent) {
            int rowHeight = getRowHeight();
            int y = 0;
            for (int row = 0; row < matrix.getRowCount(); row++) {
                int x = 0;
                for (int col = 0; col < matrix.getColumnCount(); col++) {
                    List<JComponent> comps = matrix.get(col, row);
                    Rectangle bounds = new Rectangle();
                    bounds.x = x;
                    bounds.y = y;
                    int maxWidth = 0;
                    for (JComponent comp : comps) {

                        Dimension size = getCellSize(col, row, comp);
                        bounds.setSize(size);
                        maxWidth = Math.max(maxWidth, size.width);
                        comp.setBounds(bounds);

                    }
                    x += maxWidth;
                }
                y += rowHeight;
            }
        }

        @Override
        public void addLayoutComponent(Component comp, Object constraints) {
            if (constraints instanceof KeyConstraint) {
                mapComponents.put(comp, (KeyConstraint) constraints);
                mapConstraints.put((KeyConstraint) constraints, comp);
                getCellContents(matrix, (KeyConstraint) constraints).add((JComponent) comp);
            }
        }

        @Override
        public Dimension maximumLayoutSize(Container target) {
            return preferredLayoutSize(target);
        }

        @Override
        public float getLayoutAlignmentX(Container target) {
            return 0.5f;
        }

        @Override
        public float getLayoutAlignmentY(Container target) {
            return 0.5f;
        }

        @Override
        public void invalidateLayout(Container target) {
            gridSize = null;
        }

        public class Matrix<I, O> {

            private Map<I, Map<I, O>> mapRows;

            public Matrix() {
            }

            protected Map<I, Map<I, O>> getRowMap() {
                if (mapRows == null) {
                    mapRows = new HashMap<>(25);
                }
                return mapRows;
            }

            protected Map<I, O> getColumnMap(I row) {
                Map<I, Map<I, O>> rowMap = getRowMap();
                Map<I, O> mapCols = rowMap.get(row);
                if (mapCols == null) {
                    mapCols = new HashMap<>(25);
                    rowMap.put(row, mapCols);
                }
                return mapCols;
            }

            public void add(I col, I row, O obj) {
                Map<I, O> columnMap = getColumnMap(row);
                columnMap.put(col, obj);
            }

            public void remove(I col, I row, O obj) {
                if (contains(col, row)) {
                    Map<I, O> columnMap = getColumnMap(row);
                    columnMap.put(col, obj);
                }
            }

            public void removeColumn(I col) {
                for (I row : getRowMap().keySet()) {
                    Map<I, O> columnMap = getRowMap().get(row);
                    if (columnMap != null) {
                        columnMap.remove(col);
                    }
                }
            }

            public void removeRow(I row) {
                getRowMap().remove(row);
            }

            public int getRowCount() {
                return getRowMap().size();
            }

            public int getColumnCount() {
                int max = 0;
                for (I row : getRowMap().keySet()) {
                    Map<I, O> mapColumns = getRowMap().get(row);
                    max = Math.max(mapColumns.size(), max);
                }
                return max;
            }

            protected boolean containsRow(I row) {
                return getRowMap().containsKey(row);
            }

            protected boolean containsColumn(I col) {
                boolean contains = false;
                for (I row : getRowMap().keySet()) {
                    Map<I, O> columnMap = getRowMap().get(row);
                    if (columnMap != null && columnMap.containsKey(col)) {
                        contains = true;
                        break;
                    }
                }
                return contains;
            }

            public boolean contains(I col, I row) {
                boolean contains = false;
                Map<I, O> colMap = getRowMap().get(row);
                if (colMap != null) {
                    if (colMap.containsKey(col)) {
                        contains = true;
                    }
                }

                return contains;
            }

            public O get(I col, I row) {
                O value = null;
                if (contains(col, row)) {
                    Map<I, O> columnMap = getRowMap().get(row);
                    value = columnMap.get(col);
                }
                return value;
            }

            public boolean contains(O value) {
                boolean contains = false;
                for (I row : getRowMap().keySet()) {
                    Map<I, O> mapColumns = getRowMap().get(row);
                    for (I col : mapColumns.keySet()) {
                        if (mapColumns.containsValue(value)) {
                            contains = true;
                            break;
                        }
                    }
                }
                return contains;
            }

            public boolean rowContains(I row, O value) {
                boolean contains = false;
                Map<I, O> mapColumns = getRowMap().get(row);
                for (I col : mapColumns.keySet()) {
                    if (mapColumns.containsValue(value)) {
                        contains = true;
                        break;
                    }
                }
                return contains;
            }

            public boolean columnContains(I column, O value) {
                boolean contains = false;
                for (I row : getRowMap().keySet()) {
                    Map<I, O> mapColumns = getRowMap().get(row);
                    O colValue = mapColumns.get(column);
                    if (colValue == value) {
                        contains = true;
                        break;
                    }
                }
                return contains;
            }

            public O[] rowToArray(I row, O[] values) {
                List<O> lstValues = new ArrayList<O>(25);
                Map<I, O> mapColumns = getRowMap().get(row);
                lstValues.addAll(mapColumns.values());
                return lstValues.toArray(values);
            }

            public O[] columnToArray(I col, O[] values) {
                List<O> lstValues = new ArrayList<O>(25);
                for (I row : getRowMap().keySet()) {
                    Map<I, O> mapCols = getRowMap().get(row);
                    lstValues.add(mapCols.get(col));
                }
                return lstValues.toArray(values);
            }

            public Iterator<O> columnIterator(I col) {
                List<O> lstValues = new ArrayList<O>(25);
                for (I row : getRowMap().keySet()) {
                    Map<I, O> mapCols = getRowMap().get(row);
                    lstValues.add(mapCols.get(col));
                }
                return lstValues.iterator();
            }

            public Iterator<O> rowIterator(I row) {
                List<O> lstValues = new ArrayList<O>(25);
                Map<I, O> mapColumns = getRowMap().get(row);
                lstValues.addAll(mapColumns.values());
                return lstValues.iterator();
            }
        }
    }
}

我很感激你的努力,但似乎对于一个(看起来)简单的任务来说有些过度了。如果一切都失败了,我会再联系你的。顺便说一下,我现在明白你的用户名是从哪里来的了。 - user2570380
1
很棒的解决方案。+1 布局看起来很简单,但实际上并不是。它实际上是一个很好的自定义布局候选者。 如果可以相对于其他组设置大小组,那么使用MigLayout可以轻松完成。到目前为止我还没有弄清楚。 - Jan Bodnar
我很快地浏览了一下MigLayout,但我对它的经验不足以达到我想要的结果... - MadProgrammer
我能够通过使用等宽字体,并为六个键指定填充和边距来完成它。最终(未压缩的?)代码有80行。虽然不是最优雅的解决方案,但效果很好。 - user2570380
@MadProgrammer 我会在第一行使用许多小的JLabel矩阵(特别是细的,就像顶部的边框),其中奇数像素的最后一个带有锚定点(减少调整大小后的空白空间,结果如LayoutManager将所有元素分割,GBC在末尾创建空白空间),因为GBC是列...而不是你在代码中使用Dimension做的任何事情,虽然不太准确,但比Oracle教程好+1。 - mKorbel
是的,我尝试使用GridBagLayout,但它似乎无法处理多层重叠。我也尝试了MigLayout,但我没有足够的经验让它按照我想要的方式工作,在沮丧中,我编写了自己的布局管理器...它是一种水平网格布局... - MadProgrammer

3
另一种方法是使用等宽字体来保持对齐。
import javax.swing.*;   // JFrame, JPanel, JLabel, JButton
import java.awt.*;      // GridBagLayout, GridBagConstraints, Insets, Font

public class Keyboard {
    private final JFrame f = new JFrame("Keyboard");

    private final JPanel keyboard = new JPanel();

    private static final String[][] key = {
        {"`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Backspace"},
        {"Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\"},
        {"Caps", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "Enter"},
        {"Shift", "Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "\u2191"},
        {" ", "<", "\u2193", ">"}
    };

    public Keyboard() {
        keyboard.setLayout(new GridBagLayout());

        Insets zeroInset = new Insets(0, 0, 0, 0);
        Font monospace = new Font(Font.MONOSPACED, Font.PLAIN, 12);

        JPanel pRow;
        JButton b;

        GridBagConstraints cRow = new GridBagConstraints(),
                           cButton = new GridBagConstraints();
        cRow.anchor = GridBagConstraints.WEST;
        cButton.ipady = 21;

        // first dimension of the key array
        // representing a row on the keyboard
        for (int row = 0, i = 0; row < key.length; ++row) {
            pRow = new JPanel(new GridBagLayout());

            cRow.gridy = row;

            // second dimension representing each key
            for (int col = 0; col < key[row].length; ++col, ++i) {

                // specify padding and insets for the buttons
                switch (key[row][col]) {
                    case "Backspace":   cButton.ipadx = 0; break;
                    case "Tab":         cButton.ipadx = 17; break;
                    case "Caps":        cButton.ipadx = 10; break;
                    case "Enter":       cButton.ipadx = 27; break;
                    case "Shift":       cButton.ipadx = 27; break;
                    case "/":
                        cButton.insets = new Insets(0, 0, 0, 24);
                        break;
                    case " ":
                        cButton.ipadx = 247;
                        cButton.insets = new Insets(0, 192, 0, 72);
                        break;
                    default:
                        cButton.ipadx = 7;
                        cButton.insets = zeroInset;
                }

                b = new JButton(key[row][col]);
                b.setFont(monospace);
                b.setFocusable(false);
                pRow.add(b, cButton);
            }

            keyboard.add(pRow, cRow);
        }

        f.add(keyboard);
    }

    public void launch() {
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.pack();
        f.setResizable(false);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        Keyboard ui = new Keyboard();
        ui.launch();
    }
}

Swing Keyboard Layout


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接