You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用自定义CSS变量实现JavaFX节点颜色插值动画?

JavaFX CSS变量与插值动画的冲突解决

我需要使用CSS变量动态修改JavaFX节点的颜色并实现插值动画,但遇到了冲突:JavaFX仅在节点属性(如fill)显式绑定到可样式化对象属性,且节点已加入场景图时,才会解析CSS变量。但如果节点属性被绑定,Timeline就无法对其值进行插值操作。

有没有解决办法?比如能否在Timeline启动前手动触发CSS变量的解析?


最小复现示例

public class ExampleApp extends Application {

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

    @Override
    public void start(Stage stage) {
        var r = new AnimatedRect(200, 200);
        // 实际效果:矩形闪烁红色和蓝色
        // 预期效果:矩形闪烁绿色和橙色
        r.setStyle("-color1: green; -color2: orange;");

        var scene = new Scene(new BorderPane(r), 200, 200);
        stage.setScene(scene);
        stage.show();
    }

    static class AnimatedRect extends Rectangle {

        public AnimatedRect(double width, double height) {
            super(width, height);
            setFill(color1.get());

            // 如果把color1绑定到fill属性,CSS变量会生效,
            // 但Timeline会停止,因为无法修改绑定属性的值,
            // 双向绑定也解决不了这个问题
            // fillProperty().bind(color1);

            var timeline = new Timeline(
                new KeyFrame(Duration.millis(0),
                    new KeyValue(fillProperty(), color1.get(), LINEAR)
                ),
                new KeyFrame(Duration.millis(1000),
                    new KeyValue(fillProperty(), color2.get(), LINEAR)
                )
            );
            timeline.setCycleCount(Timeline.INDEFINITE);
            timeline.setAutoReverse(false);

            sceneProperty().addListener((obs, o, n) -> {
                if (n != null) {
                    timeline.play();
                } else {
                    timeline.stop();
                }
            });
        }

        final StyleableObjectProperty<Paint> color1 = new SimpleStyleableObjectProperty<>(
            StyleableProperties.COLOR1, AnimatedRect.this, "-color1", Color.RED
        );

        final StyleableObjectProperty<Paint> color2 = new SimpleStyleableObjectProperty<>(
            StyleableProperties.COLOR2, AnimatedRect.this, "-color2", Color.BLUE
        );

        static class StyleableProperties {

            private static final CssMetaData<AnimatedRect, Paint> COLOR1 = new CssMetaData<>(
                "-color1", PaintConverter.getInstance(), Color.RED
            ) {
                @Override
                public boolean isSettable(AnimatedRect c) {
                    return !c.color1.isBound();
                }

                @Override
                public StyleableProperty<Paint> getStyleableProperty(AnimatedRect c) {
                    return c.color1;
                }
            };

            private static final CssMetaData<AnimatedRect, Paint> COLOR2 = new CssMetaData<>(
                "-color2", PaintConverter.getInstance(), Color.BLUE
            ) {
                @Override
                public boolean isSettable(AnimatedRect c) {
                    return !c.color2.isBound();
                }

                @Override
                public StyleableProperty<Paint> getStyleableProperty(AnimatedRect c) {
                    return c.color2;
                }
            };

            private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;

            static {
                final List<CssMetaData<? extends Styleable, ?>> styleables =
                    new ArrayList<>(Rectangle.getClassCssMetaData());
                styleables.add(COLOR1);
                styleables.add(COLOR2);
                STYLEABLES = Collections.unmodifiableList(styleables);
            }
        }

        public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
            return StyleableProperties.STYLEABLES;
        }

        @Override
        public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
            return getClassCssMetaData();
        }
    }
}

更新:问题根源与解决方案

问题出在JavaFX的CSS解析时机:只有当节点加入场景图后,CSS变量才会被解析。之前的代码在CSS变量生效前就创建了Timeline,导致动画使用的是默认颜色值。

解决思路是监听color1和color2的变化,每当颜色值更新时,停止当前Timeline并重新创建新的Timeline(因为Timeline是不可变对象)。虽然同时更新两个颜色时动画会重启两次,但能保证动画使用最新的CSS变量值正常运行。

修改后的代码:

static class AnimatedRect extends Rectangle {

    SimpleObjectProperty<Timeline> timeline = new SimpleObjectProperty<>();

    public AnimatedRect(double width, double height) {
        super(width, height);
        setFill(color1.get());

        color1.addListener((obs, o, v) -> {
            if (timeline.get() != null) {
                timeline.get().stop();
            }
            timeline.set(createTimeline());
            timeline.get().play();
        });

        color2.addListener((obs, o, v) -> {
            if (timeline.get() != null) {
                timeline.get().stop();
            }

            timeline.set(createTimeline());
            timeline.get().play();
        });

        sceneProperty().addListener((obs, o, n) -> {
            if (n != null) {
                if (timeline.get() != null) {
                    timeline.get().play();
                }
            } else {
                if (timeline.get() != null) {
                    timeline.get().stop();
                }
            }
        });
    }

    Timeline createTimeline() {
        var timeline = new Timeline(
            new KeyFrame(Duration.millis(0),
                new KeyValue(fillProperty(), color1.getValue(), LINEAR)
            ),
            new KeyFrame(Duration.millis(1000),
                new KeyValue(fillProperty(), color2.getValue(), LINEAR)
            )
        );
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.setAutoReverse(false);

        return timeline;
    }

    // 其余代码保持不变
}

内容的提问来源于stack exchange,提问作者Maxim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 19:24:52