这是一个显示我的问题的示例代码:
public class TestApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
VBox box = new VBox();
Button button = new Button("Add a Label at top.");
button.setOnAction(event -> {
box.getChildren().addFirst(new Label("Label"));
// Here I want to compute something about coordinate
Platform.runLater(() -> System.out.println(button.getBoundsInParent().getMinY()));
});
box.getChildren().add(button);
box.setPrefSize(500, 500);
Scene scene = new Scene(box);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
点击按钮后,窗口的布局如下:
然后我想要得到button.getBoundsInParent().getMinY()
。
我认为这minY
等于标签的高度(这也是我想要得到的)。
但是结果却是-1.399999976158142(不知道为什么不是0,不过和我这次的问题没关系)。
我想问的是:
为什么只能获取到添加标签前的坐标,如何才能获取到 位置的正确坐标System.out.println
。
提前感谢您的帮助!🙏
更新:
我已阅读评论。我想要实现的功能是滚动以使节点可见。
button
在我的程序中,和之间有很多层容器ScrollPane
。所以我尝试编写一个静态方法来处理这个任务。
它如下所示:
public class TestApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
VBox box = new VBox();
Button button = new Button("Add a Label at top.");
button.setOnAction(event -> {
box.getChildren().addFirst(new Label("Label"));
Platform.runLater(() -> scrollVerticalToVisible(button));
});
box.getChildren().add(button);
box.setPrefSize(500, 500);
Scene scene = new Scene(new ScrollPane(box));
primaryStage.setScene(scene);
primaryStage.show();
}
public static void scrollVerticalToVisible(Node node) {
Bounds bounds = node.getBoundsInParent();
Node container = node;
while (container != null && !(container instanceof ScrollPane)) {
bounds = container.localToParent(bounds);
container = container.getParent();
}
if (container == null) {
return;
}
ScrollPane scrollPane = (ScrollPane) container;
double height = scrollPane.getContent().getBoundsInLocal().getHeight();
// Same Issue, I cannot get correct y
double y = bounds.getMaxY() - scrollPane.getViewportBounds().getMinY();
double viewHeight = scrollPane.getViewportBounds().getHeight();
System.out.println(y + " " + viewHeight + " " + height);
scrollPane.setVvalue((y - viewHeight) / (height - viewHeight));
}
public static void main(String[] args) {
launch(args);
}
}
在您尝试修复此问题之前,我会确保您没有更好的 API 支持解决方案。正如@SedJ601 在评论中指出的那样,
ListView
这里使用 a 可能是一个更好的控件。但是,这里有两个问题:
没有很好的方法可以解决第一个问题。“正确”的方法是向现有滚动窗格类添加功能,以允许它将节点滚动到视图中。这至少需要对现有滚动窗格皮肤进行子类化并覆盖其布局方法以进行适当的计算。(通过这样做,您可以确保在布局过程中的适当时间完成计算。)不幸的是,JavaFX 皮肤不容易进行子类化,采用这种方法需要大量工作和大量复制现有库代码。
您可以使用一些技巧来执行此操作,即通过调用来强制布局传递
请注意,这会强制进行额外的布局传递,从性能角度来看这不是很好,但在实践中可能不会造成任何问题。
计算时,假设内容的高度为
height
,视口的高度为viewHeight
。那么“可滚动像素”的数量就是它们之间的差值height-viewHeight
。如果当前垂直滚动值为(假设其在到
v
范围内),则滚动出视口顶部的内容像素数为。0
1
v * (height - viewHeight)
因此,当前可见的内容部分从 延伸
v * (height - viewHeight)
到v * (height - viewHeight) + viewHeight
。所以滚动算法应该是这样的:
v * (height - viewHeight)
,则调整v
使v * (height - viewHeight)
等于最小 y 值,即设置v
为ymin / (height - viewHeight)
v * (height - viewHeight) + viewHeight
,则进行调整v
,使得最大y值为v * (height - viewHeight) + viewHeight
,即设置v
为(ymax - viewHeight)/(height - viewHeight)
。vvalue
最后请注意,滚动窗格的范围从0
到并不能保证先验性1
;它实际上的范围是从scrollPane.getVmin()
到scrollPane.getVMax()
。我们可以通过计算以下内容来使上述计算有效v
:然后,一旦我们计算
newV
出任何必要的调整,就可以最后补充一点:
一个获取一个节点在另一个节点的坐标系中的边界的有用技巧是将边界从原始节点平移到场景,然后从场景平移到所需的坐标系。(只要两个节点位于同一场景中,此方法就有效。)
所以你可以
即获取所需节点的本地边界,使用 转换到场景坐标系
node.localToScene(...)
,然后使用 转换到滚动窗格的内容节点的坐标系scrollPane.getContent().sceneToLocal(...)
。这是一个工作版本,测试用例稍微复杂一些。我添加了第二个按钮,在按钮下方添加标签,并使布局稍微复杂一些(两个按钮都在
VBox
主面板内VBox
)。我还在滚动窗格外添加了一个按钮,该按钮会将两个按钮滚动到视图中。(请注意,我们也会进行检查
if (viewHeight > height) return;
,即,如果整个内容可见,则不执行任何操作。)