Вам нужно обновить позицию узла, когда изменяются конечные координаты линии. Чтобы не запускать вычисления несколько раз за проход макета, я рекомендую делать это из метода layoutChildren
родительского объекта, но вы также можете сделать это из прослушивателя свойств startX
, endY
, .... Это приведет к некоторым ненужным вычислениям.
Что касается расчета положения узла: центр узла должен совпадать со средней точкой линии, поэтому вам нужно решить следующее уравнение для markTopLeft
:
markTopLeft + (markWidth, markHeight) / 2 = (lineStart + lineEnd) / 2
markTopLeft = (lineStart + lineEnd - (markWidth, markHeight)) / 2
Пример
Панель с возможностью пользовательских вычислений макета
public class PostProcessPane extends Pane {
private final Set<Node> modifiedChildren = new HashSet<>();
private final Set<Node> modifiedChildrenUnmodifiable = Collections.unmodifiableSet(modifiedChildren);
private final List<Consumer<Set<Node>>> postProcessors = new ArrayList<>();
public List<Consumer<Set<Node>>> getPostProcessors() {
return postProcessors;
}
private final ChangeListener listener = (o, oldValue, newValue) -> modifiedChildren.add((Node) ((ReadOnlyProperty) o).getBean());
private void initListener() {
getChildren().addListener((ListChangeListener.Change<? extends Node> c) -> {
while (c.next()) {
if (c.wasRemoved()) {
for (Node n : c.getRemoved()) {
n.boundsInParentProperty().removeListener(listener);
}
}
if (c.wasAdded()) {
for (Node n : c.getAddedSubList()) {
n.boundsInParentProperty().addListener(listener);
}
}
}
});
}
public PostProcessPane() {
initListener();
}
public PostProcessPane(Node... children) {
super(children);
initListener();
for (Node n : children) {
n.boundsInParentProperty().addListener(listener);
}
}
@Override
protected void layoutChildren() {
super.layoutChildren();
if (!modifiedChildren.isEmpty()) {
for (Consumer<Set<Node>> processor : postProcessors) {
processor.accept(modifiedChildrenUnmodifiable);
}
modifiedChildren.clear();
}
}
}
Использование
@Override
public void start(Stage primaryStage) throws Exception {
Rectangle r1 = new Rectangle(200, 50, Color.BLUE);
Rectangle r2 = new Rectangle(200, 50, Color.RED);
Rectangle mark = new Rectangle(200, 50, Color.YELLOW);
Line line = new Line();
r1.setX(20);
r2.setX(380);
r2.setY(450);
PostProcessPane root = new PostProcessPane(line, r1, r2, mark);
root.getPostProcessors().add(changedNodes -> {
if (changedNodes.contains(r1) || changedNodes.contains(r2) || changedNodes.contains(mark)) {
Bounds bounds1 = r1.getBoundsInParent();
Bounds bounds2 = r2.getBoundsInParent();
// refresh line ends
line.setStartX(bounds1.getMinX() + bounds1.getWidth() / 2);
line.setStartY(bounds1.getMaxY());
line.setEndX(bounds2.getMinX() + bounds2.getWidth() / 2);
line.setEndY(bounds2.getMinY());
// recalculate mark position
mark.setX((line.getStartX() + line.getEndX() - mark.getWidth()) / 2);
mark.setY((line.getStartY() + line.getEndY() - mark.getHeight()) / 2);
}
});
// add some movement for the nodes
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(r1.xProperty(), r1.getX()),
new KeyValue(r1.yProperty(), r1.getY()),
new KeyValue(r2.xProperty(), r2.getX())),
new KeyFrame(Duration.seconds(1),
new KeyValue(r2.xProperty(), r1.getX())),
new KeyFrame(Duration.seconds(2),
new KeyValue(r1.xProperty(), r2.getX()),
new KeyValue(r1.yProperty(), r2.getY() / 2))
);
timeline.setAutoReverse(true);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}