我现在才学习 JavaFX,所以对这个答案持保留态度......欢迎任何更正。我对此很感兴趣,所以做了一些研究。
失效监听器
这个问题的答案部分是InvalidationListener
. 您可以在此处详细阅读文档,但本质是 aChangeLister
会立即传播更改,而InvalidationListener
a 会注意到值无效但将计算推迟到需要时。基于“z / (x - y)”计算的两种情况的示例:
首先,琐碎的东西:
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableNumberValue;
import javafx.beans.value.ObservableValue;
public class LazyExample
{
public static void main(String[] args) {
changeListenerCase();
System.out.println("\n=====================================\n");
invalidationListenerCase();
}
...
}
2 种情况(更改和失效侦听器)将设置 3 个变量,x
, y
, z
,计算表达式z / (x - y)
和适当的侦听器。然后他们调用一个manipulate()
方法来改变值。记录所有步骤:
public static void changeListenerCase() {
SimpleDoubleProperty x = new SimpleDoubleProperty(1);
SimpleDoubleProperty y = new SimpleDoubleProperty(2);
SimpleDoubleProperty z = new SimpleDoubleProperty(3);
NumberBinding nb = makeComputed(x,y,z);
nb.addListener(new ChangeListener<Number>() {
@Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println("ChangeListener: " + oldValue + " -> " + newValue);
}
});
// prints 3 times, each after modification
manipulate(x,y,z);
System.out.println("The result after changes with a change listener is: " + nb.doubleValue());
}
public static void invalidationListenerCase() {
SimpleDoubleProperty x = new SimpleDoubleProperty(1);
SimpleDoubleProperty y = new SimpleDoubleProperty(2);
SimpleDoubleProperty z = new SimpleDoubleProperty(3);
NumberBinding nb = makeComputed(x,y,z);
nb.addListener(new InvalidationListener() {
@Override public void invalidated(Observable observable) {
System.out.println("Invalidated");
}
});
// will print only once, when the result is first invalidated
// note that the result is NOT calculated until it is actually requested
manipulate(x,y,z);
System.out.println("The result after changes with an invalidation listener is: " + nb.doubleValue());
}
以及常用的方法:
private static NumberBinding makeComputed(final ObservableNumberValue x, final ObservableNumberValue y, final ObservableNumberValue z) {
return new DoubleBinding() {
{
bind(x,y,z);
}
@Override protected double computeValue() {
System.out.println("...CALCULATING...");
return z.doubleValue() / (x.doubleValue()-y.doubleValue());
}
};
}
private static void manipulate(SimpleDoubleProperty x, SimpleDoubleProperty y, SimpleDoubleProperty z) {
System.out.println("Changing z...");
z.set(13);
System.out.println("Changing y...");
y.set(1);
System.out.println("Changing x...");
x.set(2);
}
输出是:
...CALCULATING...
Changing z...
...CALCULATING...
ChangeListener: -3.0 -> -13.0
Changing y...
...CALCULATING...
ChangeListener: -13.0 -> Infinity
Changing x...
...CALCULATING...
ChangeListener: Infinity -> 13.0
The result after changes with a change listener is: 13.0
=====================================
...CALCULATING...
Changing z...
Invalidated
Changing y...
Changing x...
...CALCULATING...
The result after changes with an invalidation listener is: 13.0
所以在第一种情况下有过多的计算和一个infinity
案例。在第二种情况下,数据在第一次更改时被标记为无效,然后仅在需要时重新计算。
脉搏
绑定图形属性怎么样,例如某物的宽度和高度(如您的示例中)?JavaFX 的基础结构似乎不会立即将更改应用于图形属性,而是根据称为Pulse的信号。脉冲是异步调度的,在执行时,将根据节点属性的当前状态更新 UI。动画中的每一帧和 UI 属性的每次更改都会安排一个脉冲运行。
我不知道在您的示例情况下会发生什么,初始宽度 = 1 像素和高度 = 10 6像素,代码设置宽度 = 10 6像素(一步,调度脉冲),然后高度 = 1 像素(第二步)。如果第一步尚未处理,第二步是否会发出另一个脉冲?从 JavaFX 的角度来看,合理的做法是让管道只处理 1 个脉冲事件,但我需要一些参考。但是,即使处理了两个事件,第一个事件也应该处理整个状态变化(宽度和高度),因此变化发生在一个视觉步骤中。
开发人员必须考虑我相信的架构。假设有一个单独的任务(伪代码):
width = lengthyComputation();
Platform.runLater(node.setWidth(width));
height = anotherLengthyComputation();
Platform.runLater(node.setHeight(height));
我猜如果第一个脉冲事件有机会运行,那么用户会看到宽度的变化 - 暂停 - 高度的变化。最好把它写成(同样,总是在后台任务中)(伪代码):
width = lengthyComputation();
height = anotherLengthyComputation();
Platform.runLater(node.setWidth(width));
Platform.runLater(node.setHeight(height));
更新(来自 john16384 的评论):据此,不可能直接听脉冲。但是,可以扩展某些方法,javafx.scene.Parent
每个脉冲运行一次并达到相同的效果。因此layoutChildren()
,如果不需要对子树进行更改,或者扩展computePrefHeight(double width)
/ computePrefWidth(double height)
,如果子树将被修改,则您可以扩展。