当前位置: 首页 > 知识库问答 >
问题:

布局更改时的动画

郑西岭
2023-03-14

我错过了什么?

package javafxapplication1;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.animation.Transition;
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;

/**
 * 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, ListChangeListener<Node> {

  private Map<Node, Transition> nodesInTransition;

  public LayoutAnimator() {
    this.nodesInTransition = 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 ov, Object oldValue, Object newValue) {
    final Double oldValueDouble = (Double) oldValue;
    final Double newValueDouble = (Double) newValue;
    final Double changeValueDouble = newValueDouble - oldValueDouble;
    DoubleProperty doubleProperty = (DoubleProperty) ov;

    Node node = (Node) doubleProperty.getBean();
    final TranslateTransition t;
    if ((TranslateTransition) nodesInTransition.get(node) == null) {
      t = new TranslateTransition(Duration.millis(150), node);
    } else {
      t = (TranslateTransition) nodesInTransition.get(node);
    }

    if (doubleProperty.getName().equals("layoutX")) {
      Double orig = node.getTranslateX();
      if (Double.compare(t.getFromX(), Double.NaN) == 0) {
        t.setFromX(orig - changeValueDouble);
        t.setToX(orig);
      }
    }
    if (doubleProperty.getName().equals("layoutY")) {
      Double orig = node.getTranslateY();
      if (Double.compare(t.getFromY(), Double.NaN) == 0) {
        t.setFromY(orig - changeValueDouble);
        t.setToY(orig);
      }
    }
    t.play();

  }

  @Override
  public void onChanged(ListChangeListener.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);
        }
      }
    }
  }
}

为了便于阅读,这里提供了一个要点,并附带了一个测试用例:https://Gist.github.com/teyc/5668517

共有1个答案

夹谷岳
2023-03-14

这是一个非常好的主意。我喜欢这个。您应该考虑将新的布局管理器贡献给JFXTRAS。

为什么最初发布的解决方案不起作用

您的问题是试图记录TransLateX/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设置为一秒钟,以便眼睛有足够的时间看到实际发生了什么。

放慢动画速度帮助我看到,监听器生成的实际动画似乎要等到节点被转发出去后的一帧才会发生,出现了一个短暂的故障,节点会暂时出现在它的目标位置,然后回到它的原始位置,然后慢慢地动画到目标位置。

潜在的进一步改进

对于动画的定时可能会有一些改进,这样,如果一个动画被部分播放并出现了转播,也许你不想为新的转播从一开始播放一个完整的动画。或者,您可能希望所有的动画节点以恒定的、相同的速度移动,而不是持续一段时间。但从视觉上看,这似乎并不重要,所以我不会真的担心它。

测试系统

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值不受影响。

我从您的问题中提取了要点,以实现上面提到的自定义转换增强。

如果您感兴趣,可以查看选项卡头、TitledPanes和Accordions的openjfx代码,看看这些控件的外观如何处理子节点布局更改的动画。

 类似资料:
  • 问题内容: 我有一个具有 自动高度的UITableView自定义UITableViewCell设计 。 该行为是在my的第一次加载中UIViewController,UITableView显示带有部分文本的标签。然后,当我滚动 到底部然后滚动到顶部时,得到了所需的高度和文本外观。 这是先滚动后再滚动的外观(这是 我的实现的正确外观,但我删除了一些Lorem文本): 编辑1: Code: 在UI B

  • 我的活动布局如下: 当我将布局中的animateLayoutChanges设置为true时,每当NestedScrollView内部发生布局更改时,生成的布局就会与AppBarLayout的内容重叠。 我尝试了这个评论中的建议,但没有任何帮助。 有没有人遇到类似/相同的问题并找到解决办法?

  • 我在学习教程中的动画自动布局 http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was 一切都很顺利。 当我尝试在我的应用程序中使用这个概念,尝试从下到上设置设置屏幕(UIView)的动画时,当设置屏幕只是一个空的UIView时,效果非常好, 但如果我将UILabel

  • 许多有关自动布局约束动画的教程建议更新约束的常量属性,然后在动画块中调用。 我的处境有点棘手。我有一个包含3个子视图的视图。此超级视图的高度不是固定的-它是按其子视图的高度之和计算的。在某些情况下,我要求这3个子视图中的一个子视图切换其高度(它在0和30之间变化,即我希望平滑地隐藏和显示它)。代码类似于此: 不幸的是,这并不像我预期的那样有效。我可以看到子视图高度的平滑变化,但在为子视图高度约束设

  • 我知道有几个类似的主题,但我读了并尝试了其中的大部分,但仍然不知道如何做到这一点。 我写了一个组件在Joomla 2.5和它的工作到目前为止。我有不同的视图,我可以使用controller.php.其中一个视图显示了我的数据库(关于团队的数据)中的一个表。 现在我想有另一个相同视图的布局,它将数据基表显示为表单,以便可以更改内容。 这是文件结构: -团队/ ---tmpl/ - - - defau

  • 也许我错了...但是据我所知,所有的Jitsi会议元素(主页和房间页面)都是使用已经缩小的脚本构建的,其中一些还连接到jitsi服务器内的文件。 有可能改变Jitsi会议的布局吗HTML(家/房间)基于我的自定义布局?怎样?