如何使用自定义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
相关产品推荐
相关产品推荐

