【发布时间】:2017-11-28 22:18:00
【问题描述】:
我有一块 14x14 的板,它有 JButton,每个 Jbutton 都有不同的颜色。当您单击其中一个按钮时,它会检查具有相同颜色的邻居并将其删除。当它删除它们时,板之间有一个空白空间,所以上面的按钮应该向下移动以填充空白空间。我尝试使用 GridLayout,但我不知道如何移动上面的按钮。
【问题讨论】:
我有一块 14x14 的板,它有 JButton,每个 Jbutton 都有不同的颜色。当您单击其中一个按钮时,它会检查具有相同颜色的邻居并将其删除。当它删除它们时,板之间有一个空白空间,所以上面的按钮应该向下移动以填充空白空间。我尝试使用 GridLayout,但我不知道如何移动上面的按钮。
【问题讨论】:
这实际上是你几乎无法使用布局管理器的情况。
LayoutManager 应该同时计算 所有 组件的布局。它由某些事件触发(例如,当父组件调整大小时)。然后它计算布局并相应地排列子组件。
在你的情况下,情况完全不同。没有布局管理器可以明智地表示 上部按钮下降时出现的“中间”状态。 虽然组件是动画的,但它们不能成为正确布局的一部分。
动画本身也可能有点棘手,但幸运的是可以通用解决。但是您仍然必须跟踪有关 where 每个组件(即每个按钮)当前位于网格中的信息。当一个按钮被移除时,您必须计算受其影响的按钮(即直接位于其上方的按钮)。这些必须是动画的。动画结束后,您必须为这些按钮分配新的网格坐标。
以下是展示一种基本方法的 MCVE。它只是删除了被点击的按钮,但它应该很容易概括为删除其他按钮,基于其他条件。
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class FallingButtons
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
int rows = 8;
int cols = 8;
GridPanel gridPanel = new GridPanel(rows, cols);
for (int r=0; r<rows; r++)
{
for (int c=0; c<cols; c++)
{
JButton button = new JButton(r+","+c);
gridPanel.addComponentInGrid(r, c, button);
button.addActionListener(e ->
{
Point coordinates = gridPanel.getCoordinatesInGrid(button);
if (coordinates != null)
{
gridPanel.removeComponentInGrid(
coordinates.x, coordinates.y);
}
});
}
}
f.getContentPane().add(gridPanel);
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class GridPanel extends JPanel
{
private final int rows;
private final int cols;
private final JComponent components[][];
GridPanel(int rows, int cols)
{
super(null);
this.rows = rows;
this.cols = cols;
this.components = new JComponent[rows][cols];
addComponentListener(new ComponentAdapter()
{
@Override
public void componentResized(ComponentEvent e)
{
layoutGrid();
}
});
}
private void layoutGrid()
{
int cellWidth = getWidth() / cols;
int cellHeight = getHeight() / rows;
for (int r=0; r<rows; r++)
{
for (int c=0; c<cols; c++)
{
JComponent component = components[r][c];
if (component != null)
{
component.setBounds(
c * cellWidth, r * cellHeight, cellWidth, cellHeight);
}
}
}
}
Point getCoordinatesInGrid(JComponent component)
{
for (int r=0; r<rows; r++)
{
for (int c=0; c<cols; c++)
{
if (components[r][c] == component)
{
return new Point(r, c);
}
}
}
return null;
}
void addComponentInGrid(int row, int col, JComponent component)
{
add(component);
components[row][col] = component;
layoutGrid();
}
JComponent getComponentInGrid(int row, int col)
{
return components[row][col];
}
void removeComponentInGrid(int row, int col)
{
remove(components[row][col]);
components[row][col] = null;
List<Runnable> animations = new ArrayList<Runnable>();
for (int r=row-1; r>=0; r--)
{
JComponent component = components[r][col];
if (component != null)
{
Runnable animation =
createAnimation(component, r, col, r + 1, col);
animations.add(animation);
}
}
for (Runnable animation : animations)
{
Thread t = new Thread(animation);
t.setDaemon(true);
t.start();
}
repaint();
}
private Runnable createAnimation(JComponent component,
int sourceRow, int sourceCol, int targetRow, int targetCol)
{
int cellWidth = getWidth() / cols;
int cellHeight = getHeight() / rows;
Rectangle sourceBounds = new Rectangle(
sourceCol * cellWidth, sourceRow * cellHeight,
cellWidth, cellHeight);
Rectangle targetBounds = new Rectangle(
targetCol * cellWidth, targetRow * cellHeight,
cellWidth, cellHeight);
Runnable movement = createAnimation(
component, sourceBounds, targetBounds);
return () ->
{
components[sourceRow][sourceCol] = null;
movement.run();
components[targetRow][targetCol] = component;
repaint();
};
}
private static Runnable createAnimation(JComponent component,
Rectangle sourceBounds, Rectangle targetBounds)
{
int delayMs = 10;
int steps = 20;
Runnable r = () ->
{
int x0 = sourceBounds.x;
int y0 = sourceBounds.y;
int w0 = sourceBounds.width;
int h0 = sourceBounds.height;
int x1 = targetBounds.x;
int y1 = targetBounds.y;
int w1 = targetBounds.width;
int h1 = targetBounds.height;
int dx = x1 - x0;
int dy = y1 - y0;
int dw = w1 - w0;
int dh = h1 - h0;
for (int i=0; i<steps; i++)
{
double alpha = (double)i / (steps - 1);
int x = (int)(x0 + dx * alpha);
int y = (int)(y0 + dy * alpha);
int w = (int)(w0 + dw * alpha);
int h = (int)(h0 + dh * alpha);
SwingUtilities.invokeLater(() ->
{
component.setBounds(x, y, w, h);
});
try
{
Thread.sleep(delayMs);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
return;
}
}
SwingUtilities.invokeLater(() ->
{
component.setBounds(x1, y1, w1, h1);
});
};
return r;
}
}
【讨论】:
您可以尝试使用二维 JButton 数组
JButton[][] buttons = new JButton[14][14];
for (int i=0; i < buttons.length; i++) {
for (int j=0; j < buttons[i].length; j++) {
buttons[i][j] = new JButton("Button [" + i + "][" + j + "]");
}
}
// Then do whatever,remove,change color,check next element in array
// and compare colors etc
buttons[2][3].setText("changed text");
【讨论】:
如果您希望以上按钮在移除组件时占用更多空间来填充空白空间,则使用GridLayout 是不可能的,但您可以添加一些空白组件,例如JLabels 来填充空间。
为此,您可以使用Container 的add (Component comp, int index) 方法在容器中的特定索引处添加组件。
此代码 sn-p 将用面板中的空白组件替换指定索引处的按钮(例如 45),该组件具有 GridLayout 集:
JPanel boardPanel = new JPanel (new GridLayout (14, 14));
// ... add your buttons ...
// This code could be invoked inside an ActionListener ...
boardPanel.remove (45);
boardPanel.add (new JLabel (""), 45);
boardPanel.revalidate ();
boardPanel.repaint ();
这样,其余组件不会移动,您只会看到一个空白区域替换您的按钮。
您可以实现更多:如果在 index = 0 处添加空标签,所有按钮都会向右移动(请记住,组件的数量不应改变,否则组件将调整大小,您可能会获得不良行为) ,依此类推,您可以通过简单地移除单个组件并将其添加到不同的索引来“移动”它。
另一种方法是存储代表模型逻辑的二维对象数组(您可以存储颜色和所有需要的东西),并通过覆盖 paintComponent 方法自行绘制它们。
有关自定义绘画方法的示例,请查看this MadProgrammer's answer,其中他展示了如何突出显示网格中的特定单元格(在这种情况下,他使用 List 来存储对象,但二维数组也可以以及)。
【讨论】: