如何在不使用任何布局的情况下使文本居中?

How to center text without using any layout?

import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextBoundsType;
import javafx.stage.Stage;

public class HorizontalTextAlignment extends javafx.application.Application {

    @Override
    public void start(Stage stage) {
        var rectangle = new Rectangle(600, 100, Color.TURQUOISE);
        var text = new Text(rectangle.getWidth() / 2, rectangle.getHeight() / 2, "TEXT");
        text.setBoundsType(TextBoundsType.VISUAL);
        text.setFont(new Font(100));
        text.setTextAlignment(TextAlignment.CENTER);
        text.setTextOrigin(VPos.CENTER);
        stage.setScene(new Scene(new Pane(rectangle, text)));
        stage.show();
    }

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

我需要在不使用任何布局的情况下将文本居中。您可能会注意到,虽然垂直对齐有效,但水平对齐无效。当然,这可以通过简单的计算解决。

// You need to change the horizontal position of the text for this to work.
var text = new Text(rectangle.getLayoutX(), rectangle.getHeight() / 2, "TEXT");
...
var rectangleWidth = rectangle.getWidth();
var textWidth = text.getLayoutBounds().getWidth();
text.setLayoutX((rectangleWidth - textWidth) / 2);

问题是,虽然在图片中不太容易看出它不在正中间

如果只更改初始化,这个很好看。

var text = new Text(rectangle.getLayoutX(), rectangle.getHeight() / 2, "TEXT");

但是,这不能通过简单地从水平位置减去所需的值(在本例中为 -2)来解决,因为当我更改文本时。

需要减去9,这就是问题所在。我永远无法提前知道必须从水平位置扣除多少。

请问我该如何解决?

谢谢

如果不是被迫使用那些特定的 Nodes,我会使用 Label 而不是 TextStackPane 而不是 Pane:

Rectangle rectangle = new Rectangle(600, 100, Color.TURQUOISE);

Label label = new Label("TEXT");
label.setAlignment(Pos.CENTER);
label.setFont(new Font(100));

StackPane.setAlignment(label, Pos.CENTER);
StackPane.setAlignment(rectangle, Pos.CENTER);

StackPane pane = new StackPane(rectangle, label);

pane.prefWidthProperty().bind(rectangle.widthProperty());
pane.prefHeightProperty().bind(rectangle.heightProperty());

stage.setScene(new Scene(pane));
stage.show();

更新

鉴于您不能使用 StackPane,这里有一个完全开箱即用的解决方案。

您可以创建一个新的 ShapeRectangle 中减去 Text。然后你将有两个 Rectangles,一个文本透明,另一个用于背景(这将为文本提供颜色)。

这些方法的好处是您以两个具有相同边界的 Shapes 结尾,并且更容易在 Scene.

中进行布局
import javafx.application.Application;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextBoundsType;
import javafx.stage.Stage;

public class App extends Application {

    @Override
    public void start(Stage stage) {

        Rectangle rectangle = new Rectangle(600, 100);

        Text text = new Text("TEXT");

        text.setTextAlignment(TextAlignment.CENTER);
        text.setBoundsType(TextBoundsType.VISUAL);
        text.setFont(new Font(100));
        text.setTextOrigin(VPos.CENTER);

        text.setX(rectangle.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2);
        text.setY(rectangle.getHeight() / 2);

        Shape shape = Shape.subtract(rectangle, text);

        shape.setFill(Color.TURQUOISE);

        shape.layoutXProperty().bind(rectangle.layoutXProperty());
        shape.layoutYProperty().bind(rectangle.layoutYProperty());
        shape.translateXProperty().bind(rectangle.translateXProperty());
        shape.translateYProperty().bind(rectangle.translateYProperty());

        stage.setScene(new Scene(new Pane(rectangle, shape)));
        stage.show();
    }

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

使用 StackPane(例如 中所示)可以更轻松地完成所有这些操作。使用适当的布局应该始终是您的第一选择。但是,我的其余答案显示了如何手动完成。


xy 属性定义文本节点的 原点 。 x-coordinate 确定文本左侧的起始位置。但是,y-coordinate 可以通过 textOrigin 属性.

进行一些自定义
                   X (always)
                   |
Y (VPos.TOP)-------╔════════════════════════════════╗
                   ║                                ║
Y (VPos.CENTER)----║          Text Node             ║
                   ║                                ║
Y (VPos.BOTTOM)----╚════════════════════════════════╝

注意:还有 VPos.BASELINE(默认值),但我不知道如何形象化。

如您所见,当您将 x 属性 设置为 rectangle.getWidth() / 2 时,您正在对齐文本节点的 左侧 与矩形的水平中心。如果要将文本节点的水平中心与矩形的水平中心对齐,则必须考虑文本节点的宽度。这是一个例子:

import javafx.application.Application;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextBoundsType;
import javafx.stage.Stage;

public class App extends Application {

  @Override
  public void start(Stage stage) {
    var rectangle = new Rectangle(600, 100, Color.TURQUOISE);

    var text = new Text("TEXT");
    text.setBoundsType(TextBoundsType.VISUAL);
    text.setFont(new Font(100));
    text.setTextAlignment(TextAlignment.CENTER);
    text.setTextOrigin(VPos.CENTER);

    // WARNING: Assumes Rectangle is at (0, 0)
    text.setX(rectangle.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2);
    text.setY(rectangle.getHeight() / 2);

    stage.setScene(new Scene(new Pane(rectangle, text)));
    stage.show();
  }
}

请注意,我在设置其他所有内容(例如字体、文本等)后设置了 x 属性。这是已知结果文本宽度所必需的。当然,这不是响应式的;如果矩形或文本的尺寸发生变化,您必须手动重新计算新的 x 值。当我们忽略布局时,理想的解决方案是使用绑定。类似于:

// WARNING: Assumes Rectangle is at (0, 0)
text.xProperty()
    .bind(
        Bindings.createDoubleBinding(
            () -> rectangle.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2,
            rectangle.widthProperty(),
            text.layoutBoundsProperty()));

不幸的是,更改 x 属性 会更改文本的布局边界,以上会导致 WhosebugError。文本的bounds-in-local(this is a characteristic of shapes)也是如此。如果您不介意通过其 layoutXlayoutY 属性定位文本,则有一个解决方案:

// WARNING: Assumes Rectangle is at (0, 0)

// leave text.x = 0 and text.y = 0
text.layoutXProperty()
    .bind(
        Bindings.createDoubleBinding(
            () -> rectangle.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2,
            rectangle.widthProperty(),
            text.layoutBoundsProperty()));
// assumes textOrigin = VPos.CENTER
text.layoutYProperty().bind(rectangle.heightProperty().divide(2));

请注意 layoutXlayoutY 属性来自 Node。布局使用它们来定位它们的 children。但是由于您没有使用自动定位其 children 的布局,您可以安全地手动设置它们。还有 translateXtranslateY 属性。这些添加到布局属性中。例如,ignoring other things,最终的x-position将是layoutX + translateX(与最终​​的y-position类似);对于 Text 节点,最终的 x-position 将是 x + layoutX + translateX(同样,对于最终的 y-position)。

您可能已经注意到,我的所有示例都警告不要假定矩形位于其 parent 的原点。要解决该问题,您只需将矩形 x 和 y 位置添加到文本的布局位置即可。

您可以使用 Label with its alignment 集来使文本居中,而不是文本对象。然后你只需要强制 Label 的最小宽度与父级的宽度相匹配(这大致是模仿某些布局所做的):

@Override
public void start(Stage stage) {
    var rectangle = new Rectangle(600, 100, Color.TURQUOISE);
    var text = new Label("TEXT");
    text.setFont(new Font(100));
    text.setAlignment(Pos.CENTER);
    Pane pane = new Pane(rectangle, text);
    text.minWidthProperty().bind(pane.widthProperty());
    text.minHeightProperty().bind(pane.heightProperty());
    stage.setScene(new Scene(pane));
    stage.show();
}