英文:
JavaFX GridPane spacing issue
问题
以下是翻译好的代码部分:
package com.example.javafxdemo.shuffle;
import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.*;
public class ShuffleGridPaneExample_Animation_BUGGY_BUT_CLOSE_1_DEBUG extends Application {
private final int gridSize = 7; // 网格中的列数
private final int squareSize = 80; // 每个正方形的大小
private final GridPane gridPane = new GridPane();
private final List<Square> squares = new ArrayList<>();
@Override
public void start(Stage primaryStage) {
createGridPane();
Button shuffleButton = new Button("Shuffle"); // "Shuffle"按钮
shuffleButton.setOnAction(event -> shuffleSquares());
GridPane root = new GridPane();
root.setAlignment(Pos.CENTER);
root.add(gridPane, 0, 0);
root.add(shuffleButton, 0, 1);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Shuffle GridPane Example"); // 标题
primaryStage.show();
}
private void createGridPane() {
Random random = new Random();
for (int i = 0; i < gridSize; i++) {
Square square = createSquare(generateRandomLetter(random), i);
squares.add(square);
gridPane.add(square, i, 0);
}
}
static HashSet<Integer> generatedLetters = new LinkedHashSet<>();
private char generateRandomLetter(Random random) {
while (true) {
int letterIndex = random.nextInt(26); // 生成0到25之间的随机索引
if (generatedLetters.contains(letterIndex)) {
continue;
}
return (char) ('A' + letterIndex); // 将索引转换为对应的字母(A-Z)
}
}
private Square createSquare(char letter, int columnIndex) {
Square square = new Square(squareSize, squareSize, letter, columnIndex);
GraphicsContext gc = square.getGraphicsContext2D();
gc.setFill(generateRandomColor());
gc.fillRect(0, 0, squareSize, squareSize);
gc.setFill(Color.BLACK);
gc.setFont(Font.font("Arial", FontWeight.BOLD, 40));
gc.fillText(String.valueOf(letter), squareSize / 2 - 10, squareSize / 2 + 10);
return square;
}
private void shuffleSquares() {
Collections.shuffle(squares);
gridPane.getChildren().clear();
int columnIndex = 0;
for (Square square : squares) {
gridPane.add(square, columnIndex, 0);
columnIndex++;
}
animateTileMovement();
}
private void animateTileMovement() {
ParallelTransition parallelTransition = new ParallelTransition();
for (int i = 0; i < gridSize; i++) {
Square square = squares.get(i);
int targetIndex = gridPane.getColumnIndex(square);
if (square.getOriginalIndex() != targetIndex) {
double startX = square.getLayoutX();
double targetX = targetIndex * squareSize;
TranslateTransition translateTransition = new TranslateTransition(Duration.seconds(3), square);
translateTransition.setFromX(startX);
translateTransition.setToX(targetX);
// 添加飞行效果
double startY = square.getLayoutY() - squareSize;
translateTransition.setFromY(startY);
double targetY = square.getLayoutY();
translateTransition.setToY(targetY);
System.out.println(i + "'" + square.letter + "' (" + startX + ", " + startY + ") (" + targetX + ", " + targetY + ")");
parallelTransition.getChildren().add(translateTransition);
}
}
parallelTransition.play();
}
public static void main(String[] args) {
launch(args);
}
private static class Square extends Canvas {
private final int originalIndex;
private final char letter;
public Square(double width, double height, char letter, int originalIndex) {
super(width, height);
this.originalIndex = originalIndex;
this.letter = letter;
}
public int getOriginalIndex() {
return originalIndex;
}
}
private Color generateRandomColor() {
Random random = new Random();
double r = random.nextDouble();
double g = random.nextDouble();
double b = random.nextDouble();
return new Color(r, g, b, 1.0);
}
}
希望这对你有所帮助!如果有任何其他疑问,请随时提出。
英文:
I have some code in which I am trying to display seven unique "squares" of characters.
There is a "Shuffle" button - upon clicking the characters should change sequence.
There is also a "flying" effect added.
The shuffle and flying effects are working, but the spacing of the squares in the GridPane gets messed up upon shuffling. Great if someone can help.
Here is the code:
package com.example.javafxdemo.shuffle;
import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.*;
public class ShuffleGridPaneExample_Animation_BUGGY_BUT_CLOSE_1_DEBUG extends Application {
private final int gridSize = 7; // Number of columns in the grid
private final int squareSize = 80; // Size of each square
private final GridPane gridPane = new GridPane();
private final List<Square> squares = new ArrayList<>();
@Override
public void start(Stage primaryStage) {
createGridPane();
Button shuffleButton = new Button("Shuffle");
shuffleButton.setOnAction(event -> shuffleSquares());
GridPane root = new GridPane();
root.setAlignment(Pos.CENTER);
root.add(gridPane, 0, 0);
root.add(shuffleButton, 0, 1);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Shuffle GridPane Example");
primaryStage.show();
}
private void createGridPane() {
Random random = new Random();
for (int i = 0; i < gridSize; i++) {
Square square = createSquare(generateRandomLetter(random), i);
squares.add(square);
gridPane.add(square, i, 0);
}
}
static HashSet<Integer> generatedLetters = new LinkedHashSet<>();
private char generateRandomLetter(Random random) {
while (true) {
int letterIndex = random.nextInt(26); // Generate a random index between 0 and 25
if (generatedLetters.contains(letterIndex)) {
continue;
}
return (char) ('A' + letterIndex); // Convert the index to the corresponding letter (A-Z)
}
}
private Square createSquare(char letter, int columnIndex) {
Square square = new Square(squareSize, squareSize, letter, columnIndex);
GraphicsContext gc = square.getGraphicsContext2D();
gc.setFill(generateRandomColor());
gc.fillRect(0, 0, squareSize, squareSize);
gc.setFill(Color.BLACK);
gc.setFont(Font.font("Arial", FontWeight.BOLD, 40));
gc.fillText(String.valueOf(letter), squareSize / 2 - 10, squareSize / 2 + 10);
return square;
}
private void shuffleSquares() {
Collections.shuffle(squares);
gridPane.getChildren().clear();
int columnIndex = 0;
for (Square square : squares) {
gridPane.add(square, columnIndex, 0);
columnIndex++;
}
animateTileMovement();
}
private void animateTileMovement() {
ParallelTransition parallelTransition = new ParallelTransition();
for (int i = 0; i < gridSize; i++) {
Square square = squares.get(i);
int targetIndex = gridPane.getColumnIndex(square);
if (square.getOriginalIndex() != targetIndex) {
double startX = square.getLayoutX();
double targetX = targetIndex * squareSize;
TranslateTransition translateTransition = new TranslateTransition(Duration.seconds(3), square);
translateTransition.setFromX(startX);
translateTransition.setToX(targetX);
// Add the flying effect
double startY = square.getLayoutY() - squareSize;
translateTransition.setFromY(startY);
double targetY = square.getLayoutY();
translateTransition.setToY(targetY);
System.out.println(i + "'" + square.letter + "' (" + startX + ", " + startY + ") (" + targetX + ", " + targetY + ")");
parallelTransition.getChildren().add(translateTransition);
}
}
parallelTransition.play();
}
public static void main(String[] args) {
launch(args);
}
private static class Square extends Canvas {
private final int originalIndex;
private final char letter;
public Square(double width, double height, char letter, int originalIndex) {
super(width, height);
this.originalIndex = originalIndex;
this.letter = letter;
}
public int getOriginalIndex() {
return originalIndex;
}
}
private Color generateRandomColor() {
Random random = new Random();
double r = random.nextDouble();
double g = random.nextDouble();
double b = random.nextDouble();
return new Color(r, g, b, 1.0);
}
}
答案1
得分: 2
以下是您提供的内容的中文翻译:
问题
在处理动画和布局时的一个技巧是,平移和布局相结合以定位一个节点。在动画之后,平移应该为零,否则节点的位置就不正确。这就是您情况中出现问题的原因。
另外,完成动画后,您没有更新您的“原始索引”。因此,下次进行动画时,动画将从网格的第一个位置开始,而不是当前位置(这似乎是错误的)。
如果物品不改变位置,它就不会“飞入”(即根本不会动画化),这似乎有点奇怪,但我猜这是您想要的效果。
您的起始位置的动画计算需要考虑到您已经重新排列了布局,因此您需要将其从原始布局位置转换到当前位置(您当前的计算对于这样做并不正确)。
更改
在方块类中,将originalIndex
设置为非final,并添加一个setter:
private int originalIndex;
public void setOriginalIndex(int idx) {
originalIndex = idx;
}
调整场景大小,以便清楚地了解动画的执行情况,并查看发生了什么。这对开发和调试很有帮助。不过,在最终实现中,您可以删除调整大小,以便物品“飞入”(或者在网格面板上添加裁剪,以允许“飞入”效果,而不是依赖于大小调整并使物品“离屏”)。
Scene scene = new Scene(root, squareSize * 7, squareSize * 4);
重写动画函数以考虑我之前提到的要点:
private void animateTileMovement() {
ParallelTransition parallelTransition = new ParallelTransition();
for (int i = 0; i < gridSize; i++) {
Square square = squares.get(i);
int targetIndex = GridPane.getColumnIndex(square);
if (square.getOriginalIndex() != targetIndex) {
double startX = (targetIndex - square.getOriginalIndex()) * - squareSize;
double targetX = 0;
TranslateTransition translateTransition = new TranslateTransition(Duration.seconds(3), square);
translateTransition.setFromX(startX);
translateTransition.setToX(targetX);
// 添加飞行效果
double startY = - squareSize;
translateTransition.setFromY(startY);
double targetY = 0;
translateTransition.setToY(targetY);
System.out.println(i + "'" + square.letter + "' (" + startX + ", " + startY + ") (" + targetX + ", " + targetY + ")");
parallelTransition.getChildren().add(translateTransition);
}
}
parallelTransition.setOnFinished(e -> {
for (Square square : squares) {
int targetIndex = GridPane.getColumnIndex(square);
System.out.println(square + ": " + square.originalIndex + " -> " + targetIndex + " & " + square.getTranslateX() + "," + square.getTranslateY());
square.setOriginalIndex(GridPane.getColumnIndex(square));
}
});
parallelTransition.play();
}
我还在动画完成时添加了更多的调试输出,以便您可以清楚地看到方块来自哪里以及它们去哪里,它们的最终平移值为零(即布局,而不是用于动画的中间平移,反映它们的最终位置)。
完整示例
所有其他代码都是相同的,但我在这里添加了更新后的代码的全文,以便通过复制/粘贴轻松复制:
import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.*;
public class ShuffleGridPaneExample_Animation extends Application {
private final int gridSize = 7; // 网格中的列数
private final int squareSize = 80; // 每个方块的大小
private final GridPane gridPane = new GridPane();
private final List<Square> squares = new ArrayList<>();
@Override
public void start(Stage primaryStage) {
createGridPane();
Button shuffleButton = new Button("Shuffle");
shuffleButton.setOnAction(event -> shuffleSquares());
GridPane root = new GridPane();
root.setAlignment(Pos.CENTER);
root.add(gridPane, 0, 0);
root.add(shuffleButton, 0, 1);
Scene scene = new Scene(root, squareSize * 7, squareSize * 4);
primaryStage.setScene(scene);
primaryStage.setTitle("Shuffle GridPane Example");
primaryStage.show();
}
private void createGridPane() {
Random random = new Random();
for (int i = 0; i < gridSize; i++) {
Square square = createSquare(generateRandomLetter(random), i);
squares.add(square);
gridPane.add(square, i, 0);
}
}
static HashSet<Integer> generatedLetters = new LinkedHashSet<>();
private char generateRandomLetter(Random random) {
while (true) {
int letterIndex = random.nextInt(26); // 生成0到25之间的随机索引
if (generatedLetters.contains(letterIndex)) {
continue;
}
return (char) ('A' + letterIndex); // 将索引转换为相应的字母(A-Z)
}
}
private Square createSquare(char letter, int columnIndex) {
Square square = new Square(squareSize, squareSize, letter, columnIndex);
GraphicsContext gc = square.getGraphicsContext2D();
gc.setFill(generateRandomColor());
gc.fillRect(0, 0, squareSize, squareSize);
gc.setFill(Color.BLACK);
gc.setFont(Font.font("Arial", FontWeight.BOLD, 40));
gc.fillText(String.valueOf(letter), squareSize / 2 - 10, squareSize / 2 + 10);
return square;
}
private void shuffleSquares() {
Collections.shuffle(squares);
gridPane.getChildren().clear();
int columnIndex = 0;
for (Square square : squares) {
gridPane.add(square, columnIndex, 0);
columnIndex++;
}
<details>
<summary>英文:</summary>
**Issues**
One trick with animations and layouts is that translation and layout add together to position a node. After the animation, the translation should be zero, otherwise, the node is not positioned correctly. This is what is going wrong in your case.
Also, you don't update your "original index" after you complete animating. So the next time you animate, the animation starts from the very first position that your grid was in, not the current position (which seems wrong).
If the item does not change position it does not "fly in" (i.e. it is not animated at all), which seems a bit weird, but I guess it is what you wanted.
Your animation calculation for the start position needs to consider that you have already rearranged the layout, so you need to translate from the original layout position to the current one (your calculations aren't currently correct for doing that).
**Changes**
In the square class make the `originalIndex` not final and add a setter for it:
private int originalIndex;
public void setOriginalIndex(int idx) {
originalIndex = idx;
}
Size the scene so it is clear what the animation is doing and you can see what is happening. This is good for development and debugging purposes. Still, in your final implementation, you can remove the sizing so stuff "flies in" (or add a clip to the gridPane to allow for the "fly in" effect rather than relying on sizing and having stuff "off-screen").
Scene scene = new Scene(root, squareSize * 7, squareSize * 4);
Rewrite the animation function to take into account the points I mentioned earlier:
private void animateTileMovement() {
ParallelTransition parallelTransition = new ParallelTransition();
for (int i = 0; i < gridSize; i++) {
Square square = squares.get(i);
int targetIndex = GridPane.getColumnIndex(square);
if (square.getOriginalIndex() != targetIndex) {
double startX = (targetIndex - square.getOriginalIndex()) * - squareSize;
double targetX = 0;
TranslateTransition translateTransition = new TranslateTransition(Duration.seconds(3), square);
translateTransition.setFromX(startX);
translateTransition.setToX(targetX);
// Add the flying effect
double startY = - squareSize;
translateTransition.setFromY(startY);
double targetY = 0;
translateTransition.setToY(targetY);
System.out.println(i + "'" + square.letter + "' (" + startX + ", " + startY + ") (" + targetX + ", " + targetY + ")");
parallelTransition.getChildren().add(translateTransition);
}
}
parallelTransition.setOnFinished(e -> {
for (Square square : squares) {
int targetIndex = GridPane.getColumnIndex(square);
System.out.println(square + ": " + square.originalIndex + " -> " + targetIndex + " & " + square.getTranslateX() + "," + square.getTranslateY());
square.setOriginalIndex(GridPane.getColumnIndex(square));
}
});
parallelTransition.play();
}
I also added a bit more debugging output on completion of the animation so that you can clearly see where squares came from and went to and that their final translation value is zero (i.e. the layout, not the intermediate translation for animation, reflects their final position).
**Complete example**
All the other code is the same, but I added the full text of the updated code here for easy replication via copy/paste:
import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.*;
public class ShuffleGridPaneExample_Animation extends Application {
private final int gridSize = 7; // Number of columns in the grid
private final int squareSize = 80; // Size of each square
private final GridPane gridPane = new GridPane();
private final List<Square> squares = new ArrayList<>();
@Override
public void start(Stage primaryStage) {
createGridPane();
Button shuffleButton = new Button("Shuffle");
shuffleButton.setOnAction(event -> shuffleSquares());
GridPane root = new GridPane();
root.setAlignment(Pos.CENTER);
root.add(gridPane, 0, 0);
root.add(shuffleButton, 0, 1);
Scene scene = new Scene(root, squareSize * 7, squareSize * 4);
primaryStage.setScene(scene);
primaryStage.setTitle("Shuffle GridPane Example");
primaryStage.show();
}
private void createGridPane() {
Random random = new Random();
for (int i = 0; i < gridSize; i++) {
Square square = createSquare(generateRandomLetter(random), i);
squares.add(square);
gridPane.add(square, i, 0);
}
}
static HashSet<Integer> generatedLetters = new LinkedHashSet<>();
private char generateRandomLetter(Random random) {
while (true) {
int letterIndex = random.nextInt(26); // Generate a random index between 0 and 25
if (generatedLetters.contains(letterIndex)) {
continue;
}
return (char) ('A' + letterIndex); // Convert the index to the corresponding letter (A-Z)
}
}
private Square createSquare(char letter, int columnIndex) {
Square square = new Square(squareSize, squareSize, letter, columnIndex);
GraphicsContext gc = square.getGraphicsContext2D();
gc.setFill(generateRandomColor());
gc.fillRect(0, 0, squareSize, squareSize);
gc.setFill(Color.BLACK);
gc.setFont(Font.font("Arial", FontWeight.BOLD, 40));
gc.fillText(String.valueOf(letter), squareSize / 2 - 10, squareSize / 2 + 10);
return square;
}
private void shuffleSquares() {
Collections.shuffle(squares);
gridPane.getChildren().clear();
int columnIndex = 0;
for (Square square : squares) {
gridPane.add(square, columnIndex, 0);
columnIndex++;
}
animateTileMovement();
}
private void animateTileMovement() {
ParallelTransition parallelTransition = new ParallelTransition();
for (int i = 0; i < gridSize; i++) {
Square square = squares.get(i);
int targetIndex = GridPane.getColumnIndex(square);
if (square.getOriginalIndex() != targetIndex) {
double startX = (targetIndex - square.getOriginalIndex()) * - squareSize;
double targetX = 0;
TranslateTransition translateTransition = new TranslateTransition(Duration.seconds(3), square);
translateTransition.setFromX(startX);
translateTransition.setToX(targetX);
// Add the flying effect
double startY = - squareSize;
translateTransition.setFromY(startY);
double targetY = 0;
translateTransition.setToY(targetY);
System.out.println(i + "'" + square.letter + "' (" + startX + ", " + startY + ") (" + targetX + ", " + targetY + ")");
parallelTransition.getChildren().add(translateTransition);
}
}
parallelTransition.setOnFinished(e -> {
for (Square square : squares) {
int targetIndex = GridPane.getColumnIndex(square);
System.out.println(square + ": " + square.originalIndex + " -> " + targetIndex + " & " + square.getTranslateX() + "," + square.getTranslateY());
square.setOriginalIndex(GridPane.getColumnIndex(square));
}
});
parallelTransition.play();
}
public static void main(String[] args) {
launch(args);
}
private static class Square extends Canvas {
private int originalIndex;
private final char letter;
public Square(double width, double height, char letter, int originalIndex) {
super(width, height);
this.originalIndex = originalIndex;
this.letter = letter;
}
public int getOriginalIndex() {
return originalIndex;
}
public void setOriginalIndex(int idx) {
originalIndex = idx;
}
}
private Color generateRandomColor() {
Random random = new Random();
double r = random.nextDouble();
double g = random.nextDouble();
double b = random.nextDouble();
return new Color(r, g, b, 1.0);
}
}
**References**
* https://stackoverflow.com/questions/59269930/is-there-a-way-to-animate-elements-in-gridpane
* https://stackoverflow.com/questions/16828234/animation-upon-layout-changes
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论