我正在创建 Uno 作为我的 CompSci 课程的期末项目。
playerHandPanel
中的卡片由 JButton 组成。按钮应按照发牌顺序依次显示,但也应重叠以容纳面板中超过起始 7 张牌的牌。 我的老师建议我使用
JLayeredPane
而不是 JPanel
作为容器,因为项目可以放在不同的层上。
BoxLayout
使用 LINE_AXIS
自动放置它们,并且该布局完全忽略图层并间隔它们,就像普通的 JPanel 一样。BoxLayout 忽略图层:
相反,它们应该像这样重叠:
这就是
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
,因为我认为我可以以某种方式跨层对卡片进行分组。我真的不知道如何解释,但这并不重要,因为它不起作用。一种可能的解决方案是创建您自己的布局管理器。例如,如果您想布局一堆代表 Uno 扑克牌的 JLabels,使它们彼此重叠,最后添加的一个放置在前面的牌之上并稍微靠右,您可以创建一个看起来像这样的布局
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 一起使用
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 来告诉布局管理器布局组件并告诉组件重新绘制,删除脏像素。
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 牌和一堆弃牌。单击该牌组时,一张牌将被移除并添加到弃牌堆中并显示在其中。当点击弃牌堆时,当前显示的牌会被踢到玻璃窗格中,并允许用户用鼠标拖动。释放时,任何侦听器都会收到已释放的卡牌(图像图标)的通知,以便父组件可以将卡牌放入游戏面板中。
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 添加到甲板面板上,以便在卡片被拖动和释放时可以收到通知。在监听器内,卡片被存入游戏面板。
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);
});
}
}