gpt4 book ai didi

java - 如何让 JavaFX 控件在初始化后立即实现它的 localBounds?

转载 作者:塔克拉玛干 更新时间:2023-11-02 08:45:31 25 4
gpt4 key购买 nike

我已经创建了我自己的 Marquee 类/控件,它在加载的前几秒后工作正常。但是,一旦应用程序首次加载,选取框就会认为它的本地边界是 maxWidth: 0.0 minWidth: 2.0。下面是 Marquee 类代码和我用来将它加载到我的测试应用程序中的代码。

选框类:

public class Marquee extends HBox {
/**
* Add a node to the Marquee control
* @param node - a node to add
*/
public void add(Node node) {
getChildren().add(node);
}

/**
* Add a list of nodes to the Marquee control
* @param observable list - an observable list to add
*/
public void addAll(ObservableList<Node> list) {
getChildren().addAll(list);
}

/**
* Default Constructor: Initializes the Marquee Object with default settings:
* Empty Array List of Nodes, initial delay, Direction.LEFT, Duration.seconds(10), Interpolator.LINEAR, 10)
*/
public Marquee()
{
this(FXCollections.observableArrayList(new ArrayList<Node>()), Duration.seconds(3), Direction.LEFT, Duration.seconds(10), Interpolator.LINEAR, 10.0);
}

/**
* Constructor: Initializes the Marquee Object with default settings
* @param observable list
*/
public Marquee(ObservableList<Node> nodes)
{
this(nodes, Duration.seconds(3), Direction.LEFT, Duration.seconds(10), Interpolator.LINEAR, 10.0);
}

/**
* Constructor: Initializes the Marquee Object with default settings
* @param observable list
* @param duration - usually in seconds i.e. Duration.seconds(10)
*/
public Marquee(ObservableList<Node> nodes, Duration duration) {
this(nodes, Duration.seconds(3), Direction.LEFT, duration, Interpolator.LINEAR, 10.0);
}

/**
* Constructor: Initializes the Marquee Object with default settings
* @param observable list
* @param direction - an enum, i.e Direction.LEFT or Direction.RIGHT
* @param duration - usually in seconds i.e. Duration.seconds(10)
*/
public Marquee(ObservableList<Node> nodes, Direction direction, Duration duration) {
this(nodes, Duration.seconds(3), direction, duration, Interpolator.LINEAR, 10.0);
}

/**
* Constructor: Initializes the Marquee Object with default settings
* @param observable list
* @param duration - usually in seconds i.e. Duration.seconds(10)
* @param interpolator - effects the translation behavior, i.e
* Interpolator.EASE_BOTH, or EASE_LINEAR
*/
public Marquee(ObservableList<Node> nodes, Duration duration, Interpolator interpolator)
{
this(nodes, Duration.seconds(3), Direction.LEFT, duration, interpolator, 10.0);
}

/**
* Constructor: Initializes the Marquee Object with default settings:
* @param observable list
* @param initialDelay - the amount of time before the marquee will begin scrolling
* after the application has loaded
* @param direction - an enum, i.e Direction.LEFT or Direction.RIGHT
* @param duration - usually in seconds i.e. Duration.seconds(10)
* @param interpolator - effects the translation behavior, i.e
* Interpolator.EASE_BOTH, or EASE_LINEAR
*/
public Marquee(ObservableList<Node> list, Duration initialDelay, Direction direction, Duration duration, Interpolator interpolator) {

this(list, initialDelay, direction, duration, interpolator, 10.0);
}

/**
* Preferred Constructor: Initializes the Marquee Object with your preferred settings
*
* @param observable list
* @param initialDelay - the amount of time before the marquee will begin scrolling
* after the application has loaded
* @param direction - an enum, i.e Direction.LEFT or Direction.RIGHT
* @param duration - usually in seconds i.e. Duration.seconds(10)
* @param interpolator - effects the translation behavior, i.e
* Interpolator.EASE_BOTH, or EASE_LINEAR
* @param nodeSpacing - a double value that determines how far apart
* each element in the marquee will be placed from one another
*/
public Marquee(ObservableList<Node> list, Duration initialDelay, Direction direction, Duration duration, Interpolator interpolator, double nodeSpacing) {
super();
getChildren().addAll(list);
setSpacing(nodeSpacing);
delay = initialDelay;
this.direction = direction;
this.duration = duration;
this.interpolator = interpolator;
}

public enum Direction {
LEFT, RIGHT
};

private Direction direction;
private TranslateTransition animation;
private Duration duration;

/**
* This begins the animation of the Marquee. By default this method
* calculates the width of the Marquee's parent and uses that as its
* start point. When the nodes inside the Marquee have reached the outer
* bounds of its parent the Marquee will stop and reset. Note: If the
* application is resized, the animation will need to be stopped and
* restarted. The Marquee will recalculate its translation requirements each
* cycle so if the user resizes it's parent, the Marquee will conform.
*
* @param duration
* the amount of time the translation should take
*/
public void animate() {

animation = new TranslateTransition(duration, this);
double maxWidth = getBoundsInLocal().getMaxX();
double minWidth = getBoundsInLocal().getMinX() - getContentsWidth();

switch (direction) {
case LEFT:
animation.setToX(minWidth);
animation.setFromX(maxWidth);
break;
case RIGHT:
animation.setToX(maxWidth);
animation.setFromX(minWidth);
break;
default:
animation.setToX(minWidth);
animation.setFromX(maxWidth);
break;
}

animation.setCycleCount(1);
animation.setInterpolator(getInterpolator());
animation.setDelay(delay);
animation.playFromStart();

animation.setOnFinished(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event)
{
stopAnimation();
recycleAnimation();
}

});
}

private Duration delay;
public Duration getDelay() {
return delay;
}

public void setDelay(Duration delay) {
this.delay = delay;
}

private Interpolator interpolator;

/**
* How the Marquee transitions its content into and out of FOV.
* Options are:
* DISCRETE (DO NOT USE), EASE_IN, EASE_OUT, EASE_BOTH, and LINEAR (DEFAULT).
* Any change to the Interpolator will take affect after the current cycle
* ends.
* Suggested Usage: setInterpolator(Interpolator.LINEAR)
*/
public void setInterpolator(Interpolator interpolator)
{
this.interpolator = interpolator;
}

/**
* The Interpolator of the Marquee.
* @return Interpolator
*/
public Interpolator getInterpolator()
{
return interpolator;
}

public void recycleAnimation()
{
setDelay(Duration.ZERO);
animate();
}

/**
* Stop animation of Marquee
*/
public void stopAnimation() {
animation.stop();
}

/**
* Set the default spacing between nodes in the Marquee Default is set to
* 5.0
*/
public void setNodeSpacing(double value) {
setSpacing(value);
}

/**
* Get the current spacing between nodes in the Marquee
*
* @return double
*/
public double getNodeSpacing() {
return getSpacing();
}

private int getContentsWidth()
{
int width = 0;

for(Node node : getChildrenUnmodifiable())
{
width += node.boundsInLocalProperty().get().getWidth();
}

return width;
}

}

和我的主类

public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {

BorderPane root = new BorderPane();

ObservableList<Node> labels = FXCollections.observableArrayList();
labels.add(new Label("Test Label 1"));
labels.add(new Label("Test Label 2"));

Marquee marqueeLeft = new Marquee(labels, Duration.ZERO, Direction.LEFT, Duration.seconds(10), Interpolator.EASE_BOTH, 10.0);
root.setTop(marqueeLeft);

final ObservableList<Node> labels2 = FXCollections.observableArrayList();
labels2.add(new Label("Test Label 3"));
labels2.add(new Label("Test Label 4"));
final Marquee marqueeRight = new Marquee(labels2, Duration.ZERO, Direction.RIGHT, Duration.seconds(10), Interpolator.EASE_BOTH, 10.0);
root.setBottom(marqueeRight);
marqueeLeft.animate();
marqueeRight.animate();

Button button = new Button();
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Workin");
marqueeRight.add(new Label("Test Add Label"));
}
});

root.setCenter(button);

Scene scene = new Scene(root,600,300);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();


} catch(Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
launch(args);
}
}

我尝试过等到显示舞台并尝试获取 parentBounds、localBounds 之后再加载选框。它总是希望从 0.0、2.0 开始。

如有任何建议,我们将不胜感激。

最佳答案

在您的 Marquee 类中,要为您调用 getContentWidth() 的节点设置动画,它只会获取每个节点的宽度:

private int getContentsWidth(){
int width = 0;
for(Node node : getChildrenUnmodifiable()){
width += node.boundsInLocalProperty().get().getWidth();
}
return width;
}

然后实例化选取框,并开始动画:

Marquee marqueeLeft = new Marquee(labels, Duration.ZERO, Direction.LEFT, Duration.seconds(10), Interpolator.EASE_BOTH, 10.0);
marqueeLeft.animate();

然后您展示舞台。

您的方法的问题在于 getWidth():它将始终返回 0 直到您显示舞台并布置节点。之后,它们将具有非零宽度。

现在,您必须等待第一个动画周期。当动画结束时,它会再次调用 animate():

public void recycleAnimation(){
setDelay(Duration.ZERO);
animate();
}

第二次,所有的节点都在舞台上,并且有一个有效的宽度,跑马灯开始移动。

解决方法:移动动画调用即可:

Scene scene = new Scene(root,600,300);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();

marqueeLeft.animate();
marqueeRight.animate();

现在,在示例中,宽度将为 126 像素,选取框将按照您的预期开始移动。

编辑

如果在显示舞台之后创建选取框:

Scene scene = new Scene(root,600,300);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();

Marquee marqueeLeft = new Marquee(labels, Duration.ZERO, Direction.LEFT, Duration.seconds(10), Interpolator.EASE_BOTH, 10.0);
root.setTop(marqueeLeft);
marqueeLeft.animate();

这也行不通,因为对 animate() 的调用是在节点添加到场景后立即发生的,而 JavaFX 应用程序线程不会有时间更新值。您可以阅读有关 JavaFX 体系结构的信息 here .

解决方案:让场景图有时间完成它的任务:

root.setTop(marqueeLeft);
root.setBottom(marqueeRight);

Platform.runLater(new Runnable() {

@Override
public void run() {
marqueeLeft.animate();
marqueeRight.animate();
}
});

关于java - 如何让 JavaFX 控件在初始化后立即实现它的 localBounds?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27971180/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com