我可以在 JLayeredPane 上使用 BoxLayout 来单独分隔每层上的项目吗?

问题描述 投票:0回答:1

我正在创建 Uno 作为我的 CompSci 课程的期末项目。

playerHandPanel
中的卡片由 JButton 组成。按钮应按照发牌顺序依次显示,但也应重叠以容纳面板中超过起始 7 张牌的牌。

我的老师建议我使用

JLayeredPane
而不是
JPanel
作为容器,因为项目可以放在不同的层上。
它不起作用,因为我使用
BoxLayout
使用
LINE_AXIS
自动放置它们,并且该布局完全忽略图层并间隔它们,就像普通的 JPanel 一样。

BoxLayout 忽略图层:
BoxLayout ignoring the layers

相反,它们应该像这样重叠:
They should overlap like this

这就是

playerHandPanel
的设置方式:

    public void setupImgs()
    {
        //gets the dealt hand and sets up the buttons/imgs
        for (int i = 0; i < deck.getPlayerHand().size(); i++)
        {
            setupHand(new JButton(deck.getHandImgs().get(i)), i);
        }
    }
    public void setupHand(JButton img, int index)
    {
        //TODO allow more than 7 cards in hand
        //TODO jlayeredpane to allow overlap by using layers
        //boxlayout puts them in sequential but ignores layers >:(

        playerHandPanel = new JLayeredPane();
        playerHandPanel.setBounds(250, 350, 1400, 300);
        playerHandPanel.setBackground(Color.GRAY);
        playerHandPanel.setLayout(new BoxLayout(playerHandPanel, BoxLayout.LINE_AXIS));
        //using the 

        if (index == 0)
        {
            //playerHand1
            ph1 = new JButton("", img.getIcon());
            ph1.setSize(150, 240);
            ph1.setLocation(0, 0);
            ph1.setMargin(new Insets(1,1,1,1));
            ph1.addActionListener(this);
            ph1.addMouseListener(this);//moves up when card focused
            playerHandPanel.add(ph1, JLayeredPane.DEFAULT_LAYER);
            //placed on bottom-most layer
        }

        if (index == 1)
        {
            ph2 = new JButton("", img.getIcon());
            ph2.setSize(150, 240);
            ph2.setLocation(20, 0);
            ph2.setMargin(new Insets(1,1,1,1));
            ph2.addActionListener(this);
            ph2.addMouseListener(this);
            playerHandPanel.add(ph2, JLayeredPane.PALETTE_LAYER);
            //placed on second lowest layer

        }
        
        if (index == 2)
        {
            ph3 = new JButton("", img.getIcon());
            ph3.setSize(150, 240);
            ph3.setLocation(50, 0);
            ph3.setMargin(new Insets(1,1,1,1));
            ph3.addActionListener(this);
            ph3.addMouseListener(this);
            playerHandPanel.add(ph3, JLayeredPane.MODAL_LAYER);
            //placed on third lowest layer
        }
        //continued to 7...
        window.add(playerHandPanel);
        playerHandPanel.setVisible(true);
    }

我尝试使用一两个其他布局,但无法真正弄清楚它们是如何工作的。 最后我就回到了

BoxLayout
。我尝试了
GroupLayout
,因为我认为我可以以某种方式跨层对卡片进行分组。我真的不知道如何解释,但这并不重要,因为它不起作用。
I tried to draw it though

java swing jbutton boxlayout jlayeredpane
1个回答
0
投票

一种可能的解决方案是创建您自己的布局管理器。例如,如果您想布局一堆代表 Uno 扑克牌的 JLabels,使它们彼此重叠,最后添加的一个放置在前面的牌之上并稍微靠右,您可以创建一个看起来像这样的布局

enter image description here

UnoCardHandLayout.java

import java.awt.*;

// layout manager that lays out Uno cards in a hand
// the cards are JLabels that are added to a JPanel
// and are placed one over the other, but leaving a GAP between them
public class UnoCardHandLayout implements LayoutManager {
    private int gap; // gap between cards

    public UnoCardHandLayout() {
        this(60);
    }

    public UnoCardHandLayout(int gap) {
        this.gap = gap;
    }

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

    @Override
    public void removeLayoutComponent(Component comp) {
    }

    @Override
    public Dimension preferredLayoutSize(Container parent) {
        int width = 0;
        int height = 0;
        for (Component comp : parent.getComponents()) {
            Dimension pref = comp.getPreferredSize();
            width = Math.max(width, pref.width);
            height = Math.max(height, pref.height);
        }
        Insets insets = parent.getInsets();
        width += insets.left + insets.right + gap;
        height += insets.top + insets.bottom + gap;
        return new Dimension(width, height);
    }

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

    @Override
    public void layoutContainer(Container parent) {
        Insets insets = parent.getInsets();
        int x = insets.left;
        int y = insets.top;
        for (int i = parent.getComponentCount() - 1; i >= 0; i--) {
            Component comp = parent.getComponent(i);
            Dimension pref = comp.getPreferredSize();
            comp.setBounds(x, y, pref.width, pref.height);
            x += gap;
        }

    }
}

此布局管理器添加组件,从组件数组中的最后一个组件(添加的第一个组件)开始,并在下一个覆盖组件之间添加

gap
距离的间隙。这可以与持有 Uno 卡“手”的 GamePanel JPanel 一起使用

GamePanel.java

import java.awt.Color;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;

import javax.swing.*;
import javax.swing.border.*;

public class GamePanel extends JPanel {
    private List<JLabel> cardLabels = new ArrayList<>();
    private static final int MIN_PREF_WIDTH = 1200;
    private static final int MIN_PREF_HEIGHT = 300;
    private static final Color BKG_COLOR = new Color(0, 80, 0); // game table dark green
    private static final int CARD_GAP = 60;

    public GamePanel() {
        setLayout(new UnoCardHandLayout(CARD_GAP)); // !! important
        setBackground(BKG_COLOR);
        Border border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.WHITE), "Game Panel",
                TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, Color.WHITE);
        setBorder(border);
    }

    @Override
    public Dimension getPreferredSize() {
        // return a size that is at least the minimum preferred size.
        Dimension superSize = super.getPreferredSize();
        int width = Math.max(superSize.width, MIN_PREF_WIDTH);
        int height = Math.max(superSize.height, MIN_PREF_HEIGHT);
        return new Dimension(width, height);
    }

    public void addUnoCard(JLabel cardLabel) {
        cardLabels.add(cardLabel);
        add(cardLabel, 0); // add to the top of the z-order
        revalidate();
        repaint();
    }
}

添加 uno 卡时,通过调用

addUnoCard(...)
,该卡将添加到第 0 个组件位置,即 z 顺序的 top。然后调用 revalidate 和 repaint 来告诉布局管理器布局组件并告诉组件重新绘制,删除脏像素。

UnoDeckPanel.java

import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.*;

public class UnoDeckPanel extends JPanel {
    public static final String CARD_DROP = "card drop";
    private static final int GAP = 10;
    private Icon emptyIcon;
    private List<Icon> unoDeckIcons;
    private List<Icon> cardPileIcons = new ArrayList<>();
    private JLabel deckLabel = null;
    private JLabel cardPileLabel = null;

    public UnoDeckPanel(Icon backIcon, Icon emptyIcon, List<Icon> icons) {
        this.unoDeckIcons = new ArrayList<>(icons);
        this.emptyIcon = emptyIcon;
        Collections.shuffle(unoDeckIcons);
        JPanel innerPanel = new JPanel();
        innerPanel.setLayout(new GridLayout(1, 0, GAP, GAP));
        innerPanel.setBorder(BorderFactory.createEmptyBorder(GAP, 20 * GAP, GAP, 20 * GAP));
        deckLabel = new JLabel(backIcon);
        innerPanel.add(deckLabel);
        cardPileLabel = new JLabel(emptyIcon);
        innerPanel.add(cardPileLabel);

        setLayout(new GridBagLayout());
        add(innerPanel);

        deckLabel.addMouseListener(new UnoDeckListener());
        CardPileListener cardPileListener = new CardPileListener();
        cardPileLabel.addMouseListener(cardPileListener);
        cardPileLabel.addMouseMotionListener(cardPileListener);
    }

    private class UnoDeckListener extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            if (unoDeckIcons.isEmpty()) {
                JOptionPane.showMessageDialog(UnoDeckPanel.this, "Deck is empty");
                return;
            }
            Icon icon = unoDeckIcons.remove(0);
            cardPileLabel.setIcon(icon);
            cardPileIcons.add(icon);

            if (unoDeckIcons.isEmpty()) {
                deckLabel.setIcon(emptyIcon);
            }
        }
    }

    private class CardPileListener extends MouseAdapter {
        private Icon poppedIcon = null;
        JLabel poppedLabel = null;

        public void mousePressed(MouseEvent e) {
            if (cardPileLabel.getIcon() != emptyIcon) {
                // first get the icon from the cardPileLabel, the top "card"
                poppedIcon = cardPileIcons.remove(cardPileIcons.size() - 1);
                // set the new top card from the icon underneath the one removed
                cardPileLabel
                        .setIcon(cardPileIcons.isEmpty() ? emptyIcon : cardPileIcons.get(cardPileIcons.size() - 1));

                // get the glass pane and add the popped label
                JComponent glassPane = (JComponent) UnoDeckPanel.this.getRootPane().getGlassPane();
                glassPane.setVisible(true);
                glassPane.setLayout(null); // null so we can drag the label
                poppedLabel = new JLabel(poppedIcon);
                glassPane.add(poppedLabel);
                poppedLabel.setSize(poppedLabel.getPreferredSize());

                // set the location of the popped label, centered on the mouse position
                Point currentPoint = e.getLocationOnScreen();
                Point glassPaneLocation = glassPane.getLocationOnScreen();
                int halfWidth = poppedLabel.getWidth() / 2;
                int halfHeight = poppedLabel.getHeight() / 2;
                Point p = new Point(currentPoint.x - glassPaneLocation.x - halfWidth,
                        currentPoint.y - glassPaneLocation.y - halfHeight);

                poppedLabel.setLocation(p);
                glassPane.revalidate();
                glassPane.repaint();
            }
        }

        public void mouseReleased(MouseEvent e) {
            if (poppedIcon != null) {
                // get the glass pane and remove the popped label
                JComponent glassPane = (JComponent) UnoDeckPanel.this.getRootPane().getGlassPane();
                glassPane.removeAll();
                glassPane.setVisible(false);

                // notify anyone listening to the card drop property that a card has been
                // dropped
                // and pass in the icon that was dropped
                UnoDeckPanel.this.firePropertyChange(CARD_DROP, null, poppedIcon);

                // reset things
                poppedIcon = null;
                poppedLabel = null;
            }
        }

        public void mouseDragged(MouseEvent e) {
            if (poppedIcon != null) {
                JComponent glassPane = (JComponent) UnoDeckPanel.this.getRootPane().getGlassPane();
                Point currentPoint = e.getLocationOnScreen();
                Point glassPaneLocation = glassPane.getLocationOnScreen();
                int halfWidth = poppedLabel.getWidth() / 2;
                int halfHeight = poppedLabel.getHeight() / 2;
                Point p = new Point(currentPoint.x - glassPaneLocation.x - halfWidth,
                        currentPoint.y - glassPaneLocation.y - halfHeight);
                poppedLabel.setLocation(p);
                glassPane.revalidate();
                glassPane.repaint();
            }
        }
    }

}

它装有一副 Uno 牌和一堆弃牌。单击该牌组时,一张牌将被移除并添加到弃牌堆中并显示在其中。当点击弃牌堆时,当前显示的牌会被踢到玻璃窗格中,并允许用户用鼠标拖动。释放时,任何侦听器都会收到已释放的卡牌(图像图标)的通知,以便父组件可以将卡牌放入游戏面板中。

UnoMainGui.java

import java.awt.BorderLayout;
import java.awt.MouseInfo;
import java.awt.Point;
import java.beans.*;
import java.util.List;
import javax.swing.*;

// main JPanel that holds both the deck panel and the game panel
public class UnoMainGui extends JPanel {
    private UnoDeckPanel deckPanel;
    private GamePanel gamePanel = new GamePanel();

    public UnoMainGui(List<Icon> icons) {
        deckPanel = new UnoDeckPanel(UnoMain.UNO_BACK_ICON, UnoMain.UNO_EMPTY_ICON, icons);

        setLayout(new BorderLayout(2, 2));
        add(deckPanel, BorderLayout.PAGE_START);
        add(gamePanel, BorderLayout.CENTER);

        // if we drop a card from the deck panel, add it to the game panel
        // this does not check for invalid drops (e.g., off of the game panel)
        deckPanel.addPropertyChangeListener(UnoDeckPanel.CARD_DROP, new DeckPanelListener());
    }

    // user has dropped a card dragged off the deck
    private class DeckPanelListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            Icon icon = (Icon) evt.getNewValue();
            if (icon == null) {
                return;

            }
            JLabel cardLabel = new JLabel(icon);
            cardLabel.setSize(cardLabel.getPreferredSize());

            // get the location of the mouse relative to the game panel's coordinate system
            Point mousePointOnScreen = MouseInfo.getPointerInfo().getLocation();
            Point gamePanelLocationOnScreen = gamePanel.getLocationOnScreen();
            int halfWidth = cardLabel.getWidth() / 2;
            int halfHeight = cardLabel.getHeight() / 2;
            Point p = new Point(mousePointOnScreen.x - gamePanelLocationOnScreen.x - halfWidth,
                    mousePointOnScreen.y - gamePanelLocationOnScreen.y - halfHeight);
            cardLabel.setLocation(p);

            gamePanel.addUnoCard(cardLabel);
        }
    }
}

这将设置并启动 GUI。它将一个 PropertyChangeListener 添加到甲板面板上,以便在卡片被拖动和释放时可以收到通知。在监听器内,卡片被存入游戏面板。

最后, UnoMain.java

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.*;

public class UnoMain {
    public static final String UNO_CARD_SHEET = "https://upload.wikimedia.org/wikipedia/commons/"
            + "thumb/9/95/UNO_cards_deck.svg/2389px-UNO_cards_deck.svg.png";
    public static final int COLUMNS = 14;
    public static final int ROWS = 8;
    public static Icon UNO_BACK_ICON = null;
    public static Icon UNO_EMPTY_ICON = null;

    public static void main(String[] args) {
        // get a Uno card sprite sheet from a public source
        BufferedImage unoImage = null;
        try {
            URL unoUrl = new URL(UNO_CARD_SHEET);
            unoImage = ImageIO.read(unoUrl);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }

        // and then subdivide the sprite sheet into individual icons
        double width = unoImage.getWidth() / (double) COLUMNS;
        double height = unoImage.getHeight() / (double) ROWS;

        Icon[][] icons = new Icon[ROWS][COLUMNS];

        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLUMNS; col++) {
                BufferedImage img = unoImage.getSubimage((int) (col * width), (int) (row * height), (int) width,
                        (int) height);
                Icon icon = new ImageIcon(img);
                icons[row][col] = icon;
            }
        }

        // put some of them into an ArrayList
        List<Icon> unoDeckIconList = new ArrayList<>();
        for (int row = 0; row < ROWS / 2; row++) {
            for (int col = 0; col < COLUMNS - 1; col++) {
                unoDeckIconList.add(icons[row][col]);
            }
        }

        // back of a UNO card
        UNO_BACK_ICON = icons[0][COLUMNS - 1];

        // empty card
        BufferedImage emptyImage = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = emptyImage.createGraphics();
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, (int) width, (int) height);
        g2d.dispose();
        UNO_EMPTY_ICON = new ImageIcon(emptyImage);

        // create the Swing GUI on the event thread
        SwingUtilities.invokeLater(() -> {
            UnoMainGui mainGui = new UnoMainGui(unoDeckIconList);
            JFrame frame = new JFrame("UNO Card Sheet");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new JScrollPane(mainGui));
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });

    }
}
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.