提问者:小点点

JavaFX:如何让我的标题和菜单相互居中[带MVCE]


我是JavaFX的新手,我正在尝试制作一个可以是任何大小的菜单。

我已经尝试了几个小时的所有可能的布局,但是我不能完成一个简单的设计。

我的背景是一个黑色矩形。我希望标题居中在屏幕顶部,我的菜单居中在标题下方。另外,我希望舞台大小固定在矩形大小上,这样我们就不会在背景上看到白色。

这是我的mvce:

package mvce_poneymon_menu;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Mvce_poneymon_menu extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        MenuView menuView = new MenuView(600, 600);

    Group root = new Group();
    Scene scene = new Scene(root);

    stage.setTitle("Poneymon");
    stage.setScene(scene);

    root.getChildren().add(menuView);
    menuView.requestFocus();

    stage.show();
}

public static void main(String[] args) {
    launch(args);
}
}

MenuView.java:

package mvce_poneymon_menu;

import javafx.animation.TranslateTransition;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.util.Duration;


public class MenuView extends StackPane {
    static final Font FONT = Font.font("", FontWeight.BOLD, 50);

    int width;
    int height;

    VBox menuBox;
    int currentItem = 0;

    public MenuView(int w, int h) {
        width = w;
        height = h;

        createContent();
        setOnKeyPressedEvent();
    }

    private void createContent() {
        GridPane grid = new GridPane();

        MenuItem exitItem = new MenuItem("Exit");
        exitItem.setOnActivate(() -> System.exit(0));

        menuBox = new VBox(10,
                    new MenuItem("Start a game"),
                    new MenuItem("Parameters"),
                    exitItem);
        menuBox.setAlignment(Pos.CENTER);
        menuBox.setTranslateX(360);

        getMenuItem(0).setActive(true);

        HBox title = (HBox)createTitle("Poneymon");
        grid.add(title, 0, 0);
        grid.add(menuBox, 0, 1);

        Rectangle bg = new Rectangle(width, height);
        grid.setTranslateY(25);
        this.getChildren().addAll(bg, grid);
    }

    private Node createTitle(String title) {
        HBox letters = new HBox(0);
        letters.setAlignment(Pos.CENTER);

        for (int i = 0; i < title.length(); i++) {
            Text letter = new Text(title.charAt(i) + "");
            letter.setFont(FONT);
            letter.setFill(Color.WHITE);
            letters.getChildren().add(letter);

            TranslateTransition tt = new TranslateTransition(Duration.seconds(2), letter);
            tt.setDelay(Duration.millis(i * 50));
            tt.setToY(-25);
            tt.setAutoReverse(true);
            tt.setCycleCount(TranslateTransition.INDEFINITE);
            tt.play();
        }

        return letters;
    }

    private MenuItem getMenuItem(int index) {
        return (MenuItem)menuBox.getChildren().get(index);
    }

    private void setOnKeyPressedEvent() {
        this.setOnKeyPressed(new EventHandler<KeyEvent>() {
            public void handle(KeyEvent e) {
                if (e.getCode() == KeyCode.UP) {
                    if (currentItem > 0) {
                        getMenuItem(currentItem).setActive(false);
                        getMenuItem(--currentItem).setActive(true);
                    }
                }

                if (e.getCode() == KeyCode.DOWN) {
                    if (currentItem < menuBox.getChildren().size() - 1) {
                        getMenuItem(currentItem).setActive(false);
                        getMenuItem(++currentItem).setActive(true);
                    }
                }

                if (e.getCode() == KeyCode.ENTER) {
                    getMenuItem(currentItem).activate();
                }
            }
        });
    }
}

MenuItem.java:

package mvce_poneymon_menu;

import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;


public class MenuItem extends HBox {
    static final Font FONT = Font.font("", FontWeight.BOLD, 30);

    private TriCircle c1 = new TriCircle();
    private TriCircle c2 = new TriCircle();
    private Text text;
    private Runnable script;

    private static class TriCircle extends Parent {
        public TriCircle() {
            Shape shape1 = Shape.subtract(new Circle(5), new Circle(2));
            shape1.setFill(Color.WHITE);

            Shape shape2 = Shape.subtract(new Circle(5), new Circle(2));
            shape2.setFill(Color.WHITE);
            shape2. setTranslateX(5);

            Shape shape3 = Shape.subtract(new Circle(5), new Circle(2));
            shape3.setFill(Color.WHITE);
            shape3.setTranslateX(2.5);
            shape3.setTranslateY(-5);

            getChildren().addAll(shape1, shape2, shape3);

            setEffect(new GaussianBlur(2));
        }
    }

    public MenuItem(String name) {
        super(15);
        setAlignment(Pos.CENTER);

        text = new Text(name);
        text.setFont(FONT);
        text.setEffect(new GaussianBlur(2));

        getChildren().addAll(c1, text, c2);
        setActive(false);
        setOnActivate(() -> System.out.println(name + " activated"));
    }

    public void setActive(boolean b) {
        c1.setVisible(b);
        c2.setVisible(b);
        text.setFill(b ? Color.WHITE : Color.GREY);
    }

    public void setOnActivate(Runnable r) {
        script = r;
    }

    public void activate() {
        if (script != null) {
            script.run();
        }
    }
}

我肯定这很简单,但我想不通: c


共1个答案

匿名用户

我的背景是一个黑色的矩形。[…]另外,我希望舞台大小固定为Rectgle大小,这样我们就不会在背景上看到白色。

简单地为StackPane分配背景会简单得多。这将允许您调整MenuView的大小,并保持背景的大小与MenuView的大小相同,而无需额外的逻辑。

应使用setResizable阶段防止调整窗口大小。

我希望标题居中在屏幕顶部,我的菜单居中在标题下方。

您使用了不健康的转换属性。(在这种情况下,我指的是translateXtranslateY。)父布局不考虑这些属性;在布局期间,节点被定位在没有任何转换的相同节点所在的位置,渲染算法会考虑这些转换。

恕我直言,以下结构将更适合预期的结果:

MenuView (root)
  |- VBox (place menu items below title)
      |- HBox (title container) 
          |- ...
      |- MenuItem
      |- MenuItem
      |- MenuItem

要获得标题容器的正确大小,我建议在内容周围使用填充。

还有其他几件事我要改变:

  • Shape shape1 = Shape.subtract(new Circle(5), new Circle(2));
    shape1.setFill(Color.WHITE);
    

    我建议将此更改为带有笔画的圆圈,而不是交叉形状。

    @Override
    public void start(Stage stage) {
        MenuView menuView = new MenuView(600, 600);
        Scene scene = new Scene(menuView);
    
        stage.setTitle("Poneymon");
        stage.setScene(scene);
        menuView.requestFocus();
    
        stage.setResizable(false); // prevent resizing of stage
        stage.show();
    }
    
    public class MenuView extends StackPane {
    
        static final Font FONT = Font.font("", FontWeight.BOLD, 50);
        int currentItem = 0;
    
        public MenuView(int w, int h) {
            setPrefSize(w, h);
    
            createContent();
            setOnKeyPressedEvent();
        }
    
        private List<MenuItem> menuItems;
    
        private void createContent() {
    
            MenuItem exitItem = new MenuItem("Exit");
            exitItem.setOnActivate(() -> Platform.exit());
    
            menuItems = Arrays.asList(
                    new MenuItem("Start a game"),
                    new MenuItem("Parameters"),
                    exitItem);
    
            VBox container = new VBox(10, createTitle("Poneymon"));
            container.getChildren().addAll(menuItems);
            container.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
    
            getMenuItem(0).setActive(true);
    
            setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
    
            getChildren().add(container);
        }
    
        private Node createTitle(String title) {
            final double movement = 25;
            HBox letters = new HBox();
            letters.setAlignment(Pos.CENTER);
    
            // add space on top equla to the upwards movement of the letters
            letters.setPadding(new Insets(movement, 0, 0, 0));
    
            for (int i = 0; i < title.length(); i++) {
                Text letter = new Text(title.charAt(i) + "");
                letter.setFont(FONT);
                letter.setFill(Color.WHITE);
                letters.getChildren().add(letter);
    
                TranslateTransition tt = new TranslateTransition(Duration.seconds(2), letter);
                tt.setDelay(Duration.millis(i * 50));
                tt.setToY(-movement);
                tt.setAutoReverse(true);
                tt.setCycleCount(TranslateTransition.INDEFINITE);
                tt.play();
            }
    
            return letters;
        }
    
        private MenuItem getMenuItem(int index) {
            return menuItems.get(index);
        }
    
        private void setOnKeyPressedEvent() {
            this.setOnKeyPressed(new EventHandler<KeyEvent>() {
                public void handle(KeyEvent e) {
                    if (e.getCode() == KeyCode.UP) {
                        if (currentItem > 0) {
                            getMenuItem(currentItem).setActive(false);
                            getMenuItem(--currentItem).setActive(true);
                        }
                    }
    
                    if (e.getCode() == KeyCode.DOWN) {
                        if (currentItem < menuItems.size() - 1) {
                            getMenuItem(currentItem).setActive(false);
                            getMenuItem(++currentItem).setActive(true);
                        }
                    }
    
                    if (e.getCode() == KeyCode.ENTER) {
                        getMenuItem(currentItem).activate();
                    }
                }
            });
        }
    }
    
    public class MenuItem extends HBox {
    
        static final Font FONT = Font.font("", FontWeight.BOLD, 30);
    
        private Group c1 = createTriCircle();
        private Group c2 = createTriCircle();
        private Text text;
        private Runnable script;
    
        private static Circle createCircle(double centerX, double centerY) {
            final double innerRadius = 2;
            final double outerRadius = 5;
            Circle circle = new Circle(centerX, centerY, (innerRadius + outerRadius) / 2, null);
            circle.setStroke(Color.WHITE);
            circle.setStrokeWidth(outerRadius - innerRadius);
            return circle;
        }
    
        private static Group createTriCircle() {
            return new Group(
                    createCircle(0, 0),
                    createCircle(5, 0),
                    createCircle(2.5, -5));
        }
    
        public MenuItem(String name) {
            super(15);
            setAlignment(Pos.CENTER);
    
            text = new Text(name);
            text.setFont(FONT);
    
            setEffect(new GaussianBlur(2));
    
            getChildren().addAll(c1, text, c2);
            setActive(false);
            setOnActivate(() -> System.out.println(name + " activated"));
        }
    
        public void setActive(boolean b) {
            c1.setVisible(b);
            c2.setVisible(b);
            text.setFill(b ? Color.WHITE : Color.GREY);
        }
    
        public void setOnActivate(Runnable r) {
            script = r;
        }
    
        public void activate() {
            if (script != null) {
                script.run();
            }
        }
    }
    

    要调整标题和菜单项之间的距离,您可以使用VBox. setMargin