我需要在容器之间移动节点(mosty剪切的ImageViews)。在父母改变期间"我必须在视觉上保持组件的位置,所以来自用户'透视这种重组应该是透明的。
我的场景架构是这样的:
我必须移动的节点被剪切,翻译了一些应用于它们的效果,并且最初位于Board
窗格下。 contents
的{{1}}组会进行缩放,剪裁和翻译。
我想将它们移到MoverLayer。它工作正常,因为desk
绑定到moverLayer
:
Board
所以我可以简单地在它们之间移动节点:
moverLayer.translateXProperty().bind(board.translateXProperty());
moverLayer.translateYProperty().bind(board.translateYProperty());
moverLayer.scaleXProperty().bind(board.scaleXProperty());
moverLayer.scaleYProperty().bind(board.scaleYProperty());
moverLayer.layoutXProperty().bind(board.layoutXProperty());
moverLayer.layoutYProperty().bind(board.layoutYProperty());
但是,在public void start(MouseEvent me) {
board.getContainer().getChildren().remove(node);
desk.getMoverLayer().getChildren().add(node);
}
public void finish(MouseEvent me) {
desk.getMoverLayer().getChildren().remove(node);
board.getContainer().getChildren().add(node);
}
contents
和tray
之间移动节点时,它开始变得复杂。我尝试使用不同的坐标(本地,父母,场景,屏幕),但不知何故,它总是放错地方。看来,当MoverLayer
的比例为1.0时,它可以将desk.contents
和translateX
的坐标映射到屏幕坐标,切换父级,然后将屏幕坐标映射到本地并用作翻译。但是,如果缩放比例不同,则坐标会有所不同(并且节点会移动)。我还尝试以递归方式将坐标映射到公共父(translateY
),但两者都没有。
我的一般性问题是,计算相对于不同父母的坐标的最佳实践是什么?
这是MCVE代码。对不起,我根本无法让它变得更简单。
desk
答案 0 :(得分:2)
好。经过大量的调试,计算和尝试,我找到了解决方案。
这是我为执行父交换而编写的实用程序函数:
/**
* Change the parent of a node.
*
* <p>
* The node should have a common ancestor with the new parent.
* </p>
*
* @param item
* The node to move.
* @param newParent
* The new parent.
*/
@SuppressWarnings("unchecked")
public static void changeParent(Node item, Parent newParent) {
try {
// HAve to use reflection, because the getChildren method is protected in common ancestor of all
// parent nodes.
// Checking old parent for public getChildren() method
Parent oldParent = item.getParent();
if ((oldParent.getClass().getMethod("getChildren").getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
throw new IllegalArgumentException("Old parent has no public getChildren method.");
}
// Checking new parent for public getChildren() method
if ((newParent.getClass().getMethod("getChildren").getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
throw new IllegalArgumentException("New parent has no public getChildren method.");
}
// Finding common ancestor for the two parents
Parent commonAncestor = findCommonAncestor(oldParent, newParent);
if (commonAncestor == null) {
throw new IllegalArgumentException("Item has no common ancestor with the new parent.");
}
// Bounds of the item
Bounds itemBoundsInParent = item.getBoundsInParent();
// Mapping coordinates to common ancestor
Bounds boundsInParentBeforeMove = localToParentRecursive(oldParent, commonAncestor, itemBoundsInParent);
// Swapping parent
((Collection<Node>) oldParent.getClass().getMethod("getChildren").invoke(oldParent)).remove(item);
((Collection<Node>) newParent.getClass().getMethod("getChildren").invoke(newParent)).add(item);
// Mapping coordinates back from common ancestor
Bounds boundsInParentAfterMove = parentToLocalRecursive(newParent, commonAncestor, boundsInParentBeforeMove);
// Setting new translation
item.setTranslateX(
item.getTranslateX() + (boundsInParentAfterMove.getMinX() - itemBoundsInParent.getMinX()));
item.setTranslateY(
item.getTranslateY() + (boundsInParentAfterMove.getMinY() - itemBoundsInParent.getMinY()));
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalStateException("Error while switching parent.", e);
}
}
/**
* Finds the topmost common ancestor of two nodes.
*
* @param firstNode
* The first node to check.
* @param secondNode
* The second node to check.
* @return The common ancestor or null if the two node is on different
* parental tree.
*/
public static Parent findCommonAncestor(Node firstNode, Node secondNode) {
// Builds up the set of all ancestor of the first node.
Set<Node> parentalChain = new HashSet<>();
Node cn = firstNode;
while (cn != null) {
parentalChain.add(cn);
cn = cn.getParent();
}
// Iterates down through the second ancestor for common node.
cn = secondNode;
while (cn != null) {
if (parentalChain.contains(cn)) {
return (Parent) cn;
}
cn = cn.getParent();
}
return null;
}
/**
* Transitively converts the coordinates from the node to an ancestor's
* coordinate system.
*
* @param node
* The node the starting coordinates are local to.
* @param ancestor
* The ancestor to map the coordinates to.
* @param x
* The X of the point to be converted.
* @param y
* The Y of the point to be converted.
* @return The converted coordinates.
*/
public static Point2D localToParentRecursive(Node node, Parent ancestor, double x, double y) {
Point2D p = new Point2D(x, y);
Node cn = node;
while (cn != null) {
if (cn == ancestor) {
return p;
}
p = cn.localToParent(p);
cn = cn.getParent();
}
throw new IllegalStateException("The node is not a descedent of the parent.");
}
/**
* Transitively converts the coordinates of a bound from the node to an
* ancestor's coordinate system.
*
* @param node
* The node the starting coordinates are local to.
* @param ancestor
* The ancestor to map the coordinates to.
* @param bounds
* The bounds to be converted.
* @return The converted bounds.
*/
public static Bounds localToParentRecursive(Node node, Parent ancestor, Bounds bounds) {
Point2D p = localToParentRecursive(node, ancestor, bounds.getMinX(), bounds.getMinY());
return new BoundingBox(p.getX(), p.getY(), bounds.getWidth(), bounds.getHeight());
}
/**
* Transitively converts the coordinates from an ancestor's coordinate
* system to the nodes local.
*
* @param node
* The node the resulting coordinates should be local to.
* @param ancestor
* The ancestor the starting coordinates are local to.
* @param x
* The X of the point to be converted.
* @param y
* The Y of the point to be converted.
* @return The converted coordinates.
*/
public static Point2D parentToLocalRecursive(Node n, Parent parent, double x, double y) {
List<Node> parentalChain = new ArrayList<>();
Node cn = n;
while (cn != null) {
if (cn == parent) {
break;
}
parentalChain.add(cn);
cn = cn.getParent();
}
if (cn == null) {
throw new IllegalStateException("The node is not a descedent of the parent.");
}
Point2D p = new Point2D(x, y);
for (int i = parentalChain.size() - 1; i >= 0; i--) {
p = parentalChain.get(i).parentToLocal(p);
}
return p;
}
/**
* Transitively converts the coordinates of the bounds from an ancestor's
* coordinate system to the nodes local.
*
* @param node
* The node the resulting coordinates should be local to.
* @param ancestor
* The ancestor the starting coordinates are local to.
* @param bounds
* The bounds to be converted.
* @return The converted coordinates.
*/
public static Bounds parentToLocalRecursive(Node n, Parent parent, Bounds bounds) {
Point2D p = parentToLocalRecursive(n, parent, bounds.getMinX(), bounds.getMinY());
return new BoundingBox(p.getX(), p.getY(), bounds.getWidth(), bounds.getHeight());
}
上述解决方案运行良好,但我想知道这是否是执行任务的最简单方法。为了一般性,我不得不使用一些反思:getChildren()
类的Parent
方法受到保护,只有它的下属才会公开,所以我不能直接通过{ {1}}。
使用上述实用程序很简单:致电Parent
。
这个实用程序还提供了查找两个节点的共同祖先的函数,并通过节点祖先链递归转换坐标。