这真是个好主意。我喜欢这个。您应该考虑将新的布局管理器贡献给jfxtras http://jfxtras.org.
为什么您最初发布的解决方案不起作用
您的问题是尝试记录 translateX/Y 值的原始值并以该原始值结束翻译转换的逻辑。
当 TranslateTransition 正在进行时,它会修改 translateX/Y 值。使用当前代码,如果您在动画完成之前快速调整屏幕大小,则可以将 TranslateTransition 的 toX/Y 属性设置为当前的 translateX/Y 值。这使得动画的最终静止位置成为某个先前的中间点,而不是节点所需的最终静止位置(所需的位置只是节点的 translateX/Y 值为 0 的点)。
如何修复它
解决方法很简单 - TranslateTransition 的 toX/Y 应始终为零,因此转换最终总是将节点翻译到当前布局位置应有的位置,且没有翻译增量。
示例解决方案代码片段
这是核心更新的转换代码:
TranslateTransition t;
switch (doubleProperty.getName()) {
case "layoutX":
t = nodeXTransitions.get(node);
if (t == null) {
t = new TranslateTransition(Duration.millis(150), node);
t.setToX(0);
nodeXTransitions.put(node, t);
}
t.setFromX(node.getTranslateX() - delta);
node.setTranslateX(node.getTranslateX() - delta);
break;
default: // "layoutY"
t = nodeYTransitions.get(node);
if (t == null) {
t = new TranslateTransition(Duration.millis(150), node);
t.setToY(0);
nodeYTransitions.put(node, t);
}
t.setFromY(node.getTranslateY() - delta);
node.setTranslateY(node.getTranslateY() - delta);
}
t.playFromStart();
示例解决方案输出
添加更多矩形并调整屏幕大小以强制使用新布局后,更新后的动画制作器的输出是(使用您在要点上提供的测试工具):
关于调试动画并修复示例代码的其他问题
在调试动画时,我发现放慢动画速度会更容易。为了弄清楚如何修复发布的程序,我将 TranslateTransition 设置为一秒,以便让眼睛有足够的时间来查看实际发生的情况。
放慢动画速度帮助我看到,监听器生成的实际动画似乎不会发生,直到节点重新布局后的一帧,从而产生一个短暂的故障,节点将暂时出现在其目标位置,然后继续回到原来的位置,然后慢慢动画到目标位置。
初始定位故障的修复方法是在开始播放动画之前将侦听器中的节点平移回原始位置。
您的代码使用nodesInTransition 映射来跟踪当前转换的节点,但它不会在映射中放置任何内容。我将其重命名为nodeXTransitions 和nodeYTransitions(因为我使用单独的x 和y 转换,而不是对两者使用单个转换,因为使用单独的转换似乎更容易)。我在创建节点转换时将其放入地图中,以便在创建新转换时可以停止旧转换。这似乎并不是绝对必要的,因为如果没有地图逻辑,一切似乎都可以正常工作(也许 JavaFX 已经在其动画框架内隐式地做了类似的事情),但这似乎是一个安全的事情,所以我保留了它。
在我进行了上面详细说明的更改后,一切似乎都正常。
潜在的进一步改进
也许可以对动画的计时进行一些改进,这样如果动画部分播放并且发生重新布局,您可能不想从头开始播放整个动画以进行新的重新布局。或者,您可能希望所有动画节点以恒定、相同的速度移动,而不是持续一段时间。但从视觉上看,这似乎并不重要,所以我不会真正担心它。
测试系统
我在 Java8b91 和 OS X 10.8 上进行了测试。
更新解决方案的完整代码
更新的布局动画器的完整代码是:
import javafx.animation.TranslateTransition;
import javafx.beans.property.DoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.util.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Animates an object when its position is changed. For instance, when
* additional items are added to a Region, and the layout has changed, then the
* layout animator makes the transition by sliding each item into its final
* place.
*/
public class LayoutAnimator implements ChangeListener<Number>, ListChangeListener<Node> {
private Map<Node, TranslateTransition> nodeXTransitions = new HashMap<>();
private Map<Node, TranslateTransition> nodeYTransitions = new HashMap<>();
/**
* Animates all the children of a Region.
* <code>
* VBox myVbox = new VBox();
* LayoutAnimator animator = new LayoutAnimator();
* animator.observe(myVbox.getChildren());
* </code>
*
* @param nodes
*/
public void observe(ObservableList<Node> nodes) {
for (Node node : nodes) {
this.observe(node);
}
nodes.addListener(this);
}
public void unobserve(ObservableList<Node> nodes) {
nodes.removeListener(this);
}
public void observe(Node n) {
n.layoutXProperty().addListener(this);
n.layoutYProperty().addListener(this);
}
public void unobserve(Node n) {
n.layoutXProperty().removeListener(this);
n.layoutYProperty().removeListener(this);
}
@Override
public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) {
final double delta = newValue.doubleValue() - oldValue.doubleValue();
final DoubleProperty doubleProperty = (DoubleProperty) ov;
final Node node = (Node) doubleProperty.getBean();
TranslateTransition t;
switch (doubleProperty.getName()) {
case "layoutX":
t = nodeXTransitions.get(node);
if (t == null) {
t = new TranslateTransition(Duration.millis(150), node);
t.setToX(0);
nodeXTransitions.put(node, t);
}
t.setFromX(node.getTranslateX() - delta);
node.setTranslateX(node.getTranslateX() - delta);
break;
default: // "layoutY"
t = nodeYTransitions.get(node);
if (t == null) {
t = new TranslateTransition(Duration.millis(150), node);
t.setToY(0);
nodeYTransitions.put(node, t);
}
t.setFromY(node.getTranslateY() - delta);
node.setTranslateY(node.getTranslateY() - delta);
}
t.playFromStart();
}
@Override
public void onChanged(Change change) {
while (change.next()) {
if (change.wasAdded()) {
for (Node node : (List<Node>) change.getAddedSubList()) {
this.observe(node);
}
} else if (change.wasRemoved()) {
for (Node node : (List<Node>) change.getRemoved()) {
this.unobserve(node);
}
}
}
}
}
关于独立于用户 TranslateX/Y 设置进行布局动画更改
当前提出的解决方案的一个缺点是,如果自定义布局管理器的用户将translateX/Y设置直接应用于布局节点,那么当布局管理器转发所有内容时,他们将丢失这些值(作为translateX) /Y 最终被设置回 0)。
为了保留用户的translateX/Y,可以更新解决方案以使用自定义过渡而不是平移过渡。自定义转换可以应用于翻译 http://docs.oracle.com/javafx/2/api/javafx/scene/transform/Translate.html到节点的转换 http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#getTransforms%28%29。然后,只有每个节点上的附加变换会受到布局动画的内部工作方式的影响 - 用户的原始 translateX/Y 值不会受到影响。
我从你的问题中分出了要点来实现上面提到的自定义转换增强 https://gist.github.com/jewelsea/5683558.
如果你有兴趣的话,你可以看看openjfx代码 https://wiki.openjdk.java.net/display/OpenJFX/Main对于选项卡标题、TitledPanes 和 Accordions,并查看这些控件的外观如何处理其子节点的布局更改动画。