javafx - 如何使窗格停留在javafx中连接draggablenode的线上
问题描述
我正在设计带有可拖动节点的图形结构的 UI。在图中,我有一个名为关系的组件(它是一个窗格),它显示了两个节点之间的链接。我希望关系保持并沿着线中部的线移动。
目前的UI设计如下图
预期的就像:
解决方案
修改线的结束坐标时,需要刷新节点的位置。为了避免每次布局传递多次触发计算,我建议从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();
}
推荐阅读
- angular - 如何在ionic-Angular中将数据传递到另一个页面
- sql - 我需要创建一个矩阵来显示一年中每个月的订单数量
- sql - 存储过程不在列中返回值
- node.js - Node.js 高阶函数
- amazon-web-services - 我的负载均衡器无法与我的 Fargate 实例通信
- plugins - 为什么我的插件没有给项目命名?
- excel - 减小 ActiveSheet 粘贴大小。大数据显示内存不足
- java - 修改 yaml 文件中的模式的脚本(或程序)
- java - 如何使用 Android LayoutInflater Factory 注入和覆盖布局属性?
- android - 试图在 android studio (Kotlin) 中用另一个 Fragment 替换一个 Fragment