java - JLayeredPane布局管理器自由移动对象

标签 java swing jpanel

我有一个游戏板,有 8 个棋子,我希望能够将它们移动到 jpanel 上的任何位置。目前,我只能进行流式或网格布局,但这并不能产生预期的结果。长期目标是能够独立单击一 block 并将其拖动到所需的位置/位置。 (包括在其他部分之上)

非常欢迎任何建议或意见...(感谢 Hovercraft Full Of Eels、MadProgrammer、peeskillet、Andrew Thompson 之前的建议)...

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;

public class FireflyGameBoard extends JFrame implements MouseListener,
    MouseMotionListener
{
  JLayeredPane layeredPane;
  JPanel gameBoard;
  // JPanel background;
  JLabel gamePiece;
  int xAdjustment;
  int yAdjustment;
  ImageIcon bgicon;
  Image bg;
  String Rimagepath = "/Users/yournamehere/Documents/workspace/MotionGraphicsTest/resources/";

  public FireflyGameBoard()
  {
    Dimension boardSize = new Dimension(1920, 1080);

    bgicon = new ImageIcon(Rimagepath + "Backdroptest.png");

    bg = bgicon.getImage();

    ImagePanel background = new ImagePanel(new ImageIcon(Rimagepath
        + "Backdroptest.png").getImage());

    // Use a Layered Pane for this this application
    layeredPane = new JLayeredPane();
    getContentPane().add(layeredPane);
    layeredPane.setPreferredSize(boardSize);
    layeredPane.addMouseListener(this);
    layeredPane.addMouseMotionListener(this);

    // Add a chess board to the Layered Pane

    gameBoard = new ImagePanel(bg);
    layeredPane.add(background, JLayeredPane.DEFAULT_LAYER);
    layeredPane.add(gameBoard, JLayeredPane.MODAL_LAYER);
    gameBoard.setLayout(new FlowLayout());
    gameBoard.setPreferredSize(boardSize);
    gameBoard.setBounds(0, 0, boardSize.width, boardSize.height);

    // for (int i = 0; i < 64; i++)
    // {
    // JPanel square = new JPanel(new BorderLayout());
    // gameBoard.add(square);
    //
    // // square.setBackground(null);
    // }

    // Add a few pieces to the board

    JLabel piece = new JLabel(new ImageIcon(Rimagepath + "alliance.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece2.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece3.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece4.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "reaper.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece6.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece7.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece8.png"));
    gameBoard.add(piece);
  }

  @Override
  public void mousePressed(MouseEvent e)
  {
    gamePiece = null;
    Component c = gameBoard.findComponentAt(e.getX(), e.getY());

    if (c instanceof JPanel)
      return;

    Point parentLocation = c.getParent().getLocation();
    xAdjustment = parentLocation.x - e.getX();
    yAdjustment = parentLocation.y - e.getY();
    gamePiece = (JLabel) c;
    gamePiece.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);
    gamePiece.setSize(gamePiece.getWidth(), gamePiece.getHeight());
    layeredPane.add(gamePiece, JLayeredPane.DRAG_LAYER);
  }

  // Move the chess piece around

  @Override
  public void mouseDragged(MouseEvent me)
  {
    if (gamePiece == null)
      return;
    gamePiece.setLocation(me.getX() + xAdjustment, me.getY() + yAdjustment);
  }

  // Drop the chess piece back onto the chess board

  @Override
  public void mouseReleased(MouseEvent e)
  {
    if (gamePiece == null)
      return;

    gamePiece.setVisible(false);
    Component c = gameBoard.findComponentAt(e.getX(), e.getY());

    if (c instanceof JLabel)
    {
      Container parent = c.getParent();
      parent.remove(0);
      parent.add(gamePiece);
    }
    else
    {
      Container parent = (Container) c;
      parent.add(gamePiece);
    }

    gamePiece.setVisible(true);
  }

  @Override
  public void mouseClicked(MouseEvent e)
  {

  }

  @Override
  public void mouseMoved(MouseEvent e)
  {
  }

  @Override
  public void mouseEntered(MouseEvent e)
  {

  }

  @Override
  public void mouseExited(MouseEvent e)
  {

  }

  public static void main(String[] args)
  {
    JFrame frame = new FireflyGameBoard();
    frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    frame.pack();
    frame.setResizable(true);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
}

附注这不是学校作业,我的女朋友是一位狂热的棋盘游戏玩家,我想让她制作她最喜欢的一些游戏的数字版本......

最佳答案

拖放是一项严肃的工作。如果做得正确,它可能真的很棒,但是要做好一些严肃的繁重工作和设计工作的准备......

一种方法是尝试生成自包含的工作单元,即该部分负责管理其自身的拖动,而单元格/网格负责管理放置。

拖动/片段

棋子是一个可移动的游戏棋子,可以将其拖动到新位置。

该 block 本身负责管理用于初始化拖动过程的DragGestureRecognizer...

因为我想在作品中显示一个图标,所以我选择覆盖 JLabel,因为它为此提供了核心功能...

import java.awt.Container;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.image.BufferedImage;
import javax.swing.JLabel;

public class PieceLabel extends JLabel {
    private DragGestureHandler dragGestureHandler;
    private DragGestureRecognizer dgr;

    public PieceLabel() {
        setHorizontalAlignment(CENTER);
        setVerticalAlignment(CENTER);
    }

    @Override
    public void addNotify() {
        super.addNotify();
        if (dgr == null) {
            dragGestureHandler = new DragGestureHandler(this);
            dgr = DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, dragGestureHandler);
        }
    }

    @Override
    public void removeNotify() {
        if (dgr != null) {
            dgr.removeDragGestureListener(dragGestureHandler);
            dragGestureHandler = null;
        }
        dgr = null;
        super.removeNotify();
    }

    public static class DragGestureHandler implements DragGestureListener, DragSourceListener {

        private PieceLabel piece;
        private Container parent;

        public DragGestureHandler(PieceLabel child) {
            this.piece = child;
        }

        public PieceLabel getPiece() {
            return piece;
        }

        protected void setParent(Container parent) {
            this.parent = parent;
        }

        protected Container getParent() {
            return parent;
        }

        @Override
        public void dragGestureRecognized(DragGestureEvent dge) {
            // When the drag begins, we need to grab a reference to the
            // parent container so we can return it if the drop
            // is rejected
            Container parent = getPiece().getParent();
            setParent(parent);
            // Remove the panel from the parent.  If we don't do this, it
            // can cause serialization issues.  We could over come this
            // by allowing the drop target to remove the component, but that's
            // an argument for another day
            parent.remove(getPiece());
            // Update the display
            parent.invalidate();
            parent.repaint();
            // Create our transferable wrapper
            Transferable transferable = new PieceTransferable(getPiece());
            // Start the "drag" process...
            DragSource ds = dge.getDragSource();
            //            ds.startDrag(dge, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR), transferable, this);
            BufferedImage image = BoardDrag.createBufferedImage(piece.getIcon(), piece);
            Point pp = piece.getLocation();
            Point dp = dge.getDragOrigin();
            int x = image.getWidth() / 2;
            int y = image.getHeight() / 2;
            ds.startDrag(dge, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR), image, new Point(x, y), transferable, this);
        }

        @Override
        public void dragEnter(DragSourceDragEvent dsde) {
        }

        @Override
        public void dragOver(DragSourceDragEvent dsde) {
        }

        @Override
        public void dropActionChanged(DragSourceDragEvent dsde) {
        }

        @Override
        public void dragExit(DragSourceEvent dse) {
        }

        @Override
        public void dragDropEnd(DragSourceDropEvent dsde) {
            // If the drop was not sucessful, we need to
            // return the component back to it's previous
            // parent
            if (!dsde.getDropSuccess()) {
                getParent().add(getPiece());
                getParent().invalidate();
                getParent().repaint();
            }
        }
    }
    
}

拖放/单元格/网格

单元格/网格就是这样,它构成了整个网格/板中的单个元素。它可以包含一个Piece(事实上,您可以轻松地将其配置为拒绝其他所有内容)

它管理 DropTarget,它负责检测何时有东西掉落到其上...

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetContext;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import javax.swing.JComponent;
import javax.swing.JPanel;

public class Cell extends JPanel {
    private DropTarget dropTarget;
    private DropHandler dropHandler;

    public Cell() {
        setLayout(new BorderLayout());
    }

    @Override
    public void addNotify() {
        super.addNotify();
        if (dropHandler == null) {
            dropHandler = new DropHandler();
        }
        if (dropTarget == null) {
            dropTarget = new DropTarget(this, DnDConstants.ACTION_MOVE, dropHandler, true);
        }
    }

    @Override
    public void removeNotify() {
        if (dropTarget != null) {
            dropTarget.removeDropTargetListener(dropHandler);
        }
        dropTarget = null;
        dropHandler = null;
        super.removeNotify();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(48, 48);
    }

    public class DropHandler implements DropTargetListener {

        @Override
        public void dragEnter(DropTargetDragEvent dtde) {
            // Determine if can actual process the contents comming in.
            // You could try and inspect the transferable as well, but
            // There is an issue on the MacOS under some circumstances
            // where it does not actually bundle the data until you accept the
            // drop.
            if (dtde.isDataFlavorSupported(PieceDataFlavor.SHARED_INSTANCE)) {
                dtde.acceptDrag(DnDConstants.ACTION_MOVE);
            } else {
                dtde.rejectDrag();
            }
        }

        @Override
        public void dragOver(DropTargetDragEvent dtde) {
        }

        @Override
        public void dropActionChanged(DropTargetDragEvent dtde) {
        }

        @Override
        public void dragExit(DropTargetEvent dte) {
        }

        @Override
        public void drop(DropTargetDropEvent dtde) {
            boolean success = false;
            // Basically, we want to unwrap the present...
            if (dtde.isDataFlavorSupported(PieceDataFlavor.SHARED_INSTANCE)) {
                Transferable transferable = dtde.getTransferable();
                try {
                    Object data = transferable.getTransferData(PieceDataFlavor.SHARED_INSTANCE);
                    if (data instanceof PieceLabel) {
                        PieceLabel piece = (PieceLabel) data;
                        DropTargetContext dtc = dtde.getDropTargetContext();
                        Component component = dtc.getComponent();
                        if (component instanceof JComponent) {
                            Container parent = piece.getParent();
                            if (parent != null) {
                                parent.remove(piece);
                            }
                            ((JComponent) component).add(piece);
                            success = true;
                            dtde.acceptDrop(DnDConstants.ACTION_MOVE);
                            invalidate();
                            repaint();
                        } else {
                            success = false;
                            dtde.rejectDrop();
                        }
                    } else {
                        success = false;
                        dtde.rejectDrop();
                    }
                } catch (Exception exp) {
                    success = false;
                    dtde.rejectDrop();
                    exp.printStackTrace();
                }
            } else {
                success = false;
                dtde.rejectDrop();
            }
            dtde.dropComplete(success);
        }
    }
    
}

胶水

在 Drag'n'Drop 中,有两个特殊的类将拖动粘合到放置位置...

DataFlavor

DataFlavor 负责提供一种方法,通过这种方法,断开连接的元素不仅可以确定传输的内容,还可以确定如何重构该数据...

为了简单起见,我只是PieceLabel.class

import java.awt.datatransfer.DataFlavor;

public class PieceDataFlavor extends DataFlavor {
    public static final PieceDataFlavor SHARED_INSTANCE = new PieceDataFlavor();

    public PieceDataFlavor() {
        super(PieceLabel.class, null);
    }
    
}

可转让

Transferable 是一个包装类,它允许将数据从一个位置移动到另一个位置,例如剪贴板。

此示例相当简单,但您可以想象 Transferable 可能包含多个 DataFlavor,具体取决于您想要的 DataFlavor更改数据的类型(或获取方式)。

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

public class PieceTransferable implements Transferable {
    private final DataFlavor[] flavors = new DataFlavor[]{PieceDataFlavor.SHARED_INSTANCE};
    private final PieceLabel piece;

    public PieceTransferable(PieceLabel piece) {
        this.piece = piece;
    }

    @Override
    public DataFlavor[] getTransferDataFlavors() {
        return flavors;
    }

    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {
        // Okay, for this example, this is over kill, but makes it easier
        // to add new flavor support by subclassing
        boolean supported = false;
        for (DataFlavor mine : getTransferDataFlavors()) {
            if (mine.equals(flavor)) {
                supported = true;
                break;
            }
        }
        return supported;
    }

    public PieceLabel getPanel() {
        return piece;
    }

    @Override
    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
        Object data = null;
        if (isDataFlavorSupported(flavor)) {
            data = getPanel();
        } else {
            throw new UnsupportedFlavorException(flavor);
        }
        return data;
    }
    
}

把它们放在一起

因为组件是独立的,所以将它们组合在一起实际上非常容易......它们基本上可以照顾自己......

What a drag

import java.awt.Color;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JPanel;

public class BoardPane extends JPanel {

    public BoardPane() {
        setLayout(new GridLayout(8, 8));
        int index = 0;
        for (int row = 0; row < 8; row++) {
            for (int col = 0; col < 8; col++) {
                Cell cell = new Cell();
                if (index % 2 == 0) {
                    cell.setBackground(Color.WHITE);
                } else {
                    cell.setBackground(Color.BLACK);
                }
                add(cell);
                index++;
            }
            index++;
        }
        try {
            PieceLabel label = new PieceLabel();
            BufferedImage image = ImageIO.read(getClass().getResource("/Piece01.png"));
            label.setIcon(new ImageIcon(image));
            setCellPiece(label, 0, 0);
        } catch (IOException ex) {
            Logger.getLogger(BoardDrag.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void setCellPiece(PieceLabel label, int row, int col) {
        int index = (row * 8) + col;
        Cell cell = (Cell) getComponent(index);
        cell.removeAll();
        cell.add(label);
    }
    
}

有什么问题?

没错,你并不能免费获得一切。

您必须实现确定移动是否有效所需的逻辑。该逻辑应该以主动拒绝拖动的方式实现。例如,这可能需要您向 Transferable 添加更多信息,以便确定起始单元格。

我个人希望实现某种“规则”引擎,您的 DnD API 可以使用它,使其变得可插拔

关于java - JLayeredPane布局管理器自由移动对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26621830/

相关文章:

java - Spring 3.0 中 Post 函数不绑定(bind)

java - Spring Data Mongodb 的性能问题

java - GridBagLayout 添加后不要使组件 "jump"

java - 从不同的类调用 JPanel 时遇到问题

java - 如何正确引用一个类(JPanel)中的刷新方法,以便可以从另一个类中的方法调用它?

java - 如何在 JNI 中访问从 C++ 返回 java.lang.String 的 Java 方法的返回值?

java.lang.OutOfMemory错误: Java heap space on using ArrayLists

定义paint()时Java组件不显示

java - 设置 JPanel 相对于 Graphics 2D 绘制网格的大小的大小

Java赛车游戏: JPanel component moves along when I repaint the car object