如何在JavaFX中制作线性渐变动画?

4

我正在尝试在JavaFX(使用JavaFX 8)上动画显示标签的文本填充。 我的目标是使渐变的第一个颜色每半秒钟从黄色变为红色。 我尝试过以下代码:

Timeline timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
timeline.setAutoReverse(true);

LinearGradient fill1 = new LinearGradient(50,50,200,200,false, CycleMethod.NO_CYCLE, new Stop(0.1f, Color.YELLOW), new Stop(1.0f, Color.BLACK));
LinearGradient fill2 = new LinearGradient(50,50,200,200,false, CycleMethod.NO_CYCLE, new Stop(0.1f, Color.RED), new Stop(1.0f, Color.BLACK));
KeyValue keyValue1 = new KeyValue(labelInstrucoes.textFillProperty(), fill1, Interpolator.EASE_OUT);
KeyValue keyValue2 = new KeyValue(labelInstrucoes.textFillProperty(), fill2, Interpolator.EASE_OUT);

KeyFrame keyframe1 = new KeyFrame(Duration.millis(0), keyValue1);
KeyFrame keyframe2 = new KeyFrame(Duration.millis(500), keyValue2);

timeline.getKeyFrames().addAll(keyframe1, keyframe2);

timeline.play();

但是它没有起作用。然而,如果我使用一个简单的颜色,比如这样:
KeyValue keyValue1 = new KeyValue(labelInstrucoes.textFillProperty(), Color.YELLOW, Interpolator.EASE_OUT);
KeyValue keyValue2 = new KeyValue(labelInstrucoes.textFillProperty(), Color.RED, Interpolator.EASE_OUT);

它有效。那么,如何使渐变动画化?

据我所知,JavaFX在渐变上应用插值器存在限制。可以使用onFinished()作为解决方法。 - Uluk Biy
2个回答

9
你可以使用css技巧来实现此功能,使用查找的颜色。
在外部CSS文件中,为线性渐变的开始定义一个查找颜色,并使用引用查找颜色的线性渐变定义文本填充:
animated-gradient.css:
.animated-gradient {
    -gradient-base: red ;
    -fx-text-fill: linear-gradient(to right, -gradient-base, black);
}

然后,"animate"一个颜色属性,并更新内联样式以在属性更改时更改查找到的颜色的值:
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;



public class AnimatedGradient extends Application {
    @Override
    public void start(Stage primaryStage) {
        StackPane root = new StackPane();
        Label label = new Label("Animated gradient");
        root.getChildren().add(label);
        label.getStyleClass().add("animated-gradient");
        Scene scene = new Scene(root,400,400);
        scene.getStylesheets().add(getClass().getResource("animated-gradient.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.show();

        ObjectProperty<Color> baseColor = new SimpleObjectProperty<>();

        KeyValue keyValue1 = new KeyValue(baseColor, Color.RED);
        KeyValue keyValue2 = new KeyValue(baseColor, Color.YELLOW);
        KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO, keyValue1);
        KeyFrame keyFrame2 = new KeyFrame(Duration.millis(500), keyValue2);
        Timeline timeline = new Timeline(keyFrame1, keyFrame2);

        baseColor.addListener((obs, oldColor, newColor) -> {
            label.setStyle(String.format("-gradient-base: #%02x%02x%02x; ", 
                    (int)(newColor.getRed()*255),
                    (int)(newColor.getGreen()*255),
                    (int)(newColor.getBlue()*255)));
        });

        timeline.setAutoReverse(true);
        timeline.setCycleCount(Animation.INDEFINITE);
        timeline.play();

    }

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

您可以使用Bindings.format(...)来代替监听器,两者没有太大区别。

诀窍是动画属性,监听它并在属性更改时更改主题(可以使用CSS或不使用)。这是一个强大的IoC。喜欢它!谢谢! - user470370

3

以下是不需要css的解决方案,可以用于动画控件。 下面我贴出包含两个不同元素的应用程序代码。您可以运行它并查看它如何工作。

1.此区域具有动画背景

public class FilledRegion extends Region {
private final Random rand;
private ObjectProperty<Color> externalColor = new SimpleObjectProperty();
private ObjectProperty<Color> internalColor = new SimpleObjectProperty();
private Color oldExternalColor;
private Color oldInternalColor;
private Background bg;
private Timeline timeline;
private int duration;

public FilledRegion() {
    rand = new Random();
    this.setMinWidth(75);
    oldExternalColor = getRandomColor(AnimatedGradients.baseExternalColor, AnimatedGradients.externalDelta);
    oldInternalColor = getRandomColor(AnimatedGradients.baseInternalColor, AnimatedGradients.internalDelta);
    externalColor.set( oldExternalColor );
    internalColor.set( oldInternalColor );
    setBackground();
    internalColor.addListener((obs, oldColor, newColor) -> {
        setBackground();
    });
}

public void startAnimation() {
    timeline = new Timeline();
    createTimelineContent();
    timeline.setOnFinished(ActionEvent -> {
        createTimelineContent();
        timeline.play();
    });
    timeline.play();
}

private void createTimelineContent() {
    timeline.getKeyFrames().clear();
    duration = getRandomDuration();
    KeyFrame kf1 =
        new KeyFrame(Duration.ZERO,
            new KeyValue( externalColor, oldExternalColor ),
            new KeyValue( internalColor, oldInternalColor ));
    oldExternalColor = getRandomColor(AnimatedGradients.baseExternalColor, AnimatedGradients.externalDelta);
    oldInternalColor = getRandomColor(AnimatedGradients.baseInternalColor, AnimatedGradients.internalDelta);
    KeyFrame kf2 =
        new KeyFrame(new Duration(duration),
            new KeyValue( externalColor, oldExternalColor ),
            new KeyValue( internalColor, oldInternalColor ));
    timeline.getKeyFrames().addAll( kf1, kf2 );
}

private void setBackground() {
    bg = new Background(
        new BackgroundFill(
            new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE,
                new Stop[] { new Stop(0, externalColor.get()), new Stop(0.5, internalColor.get()), new Stop(1, externalColor.get())}
            ),
            new CornerRadii(0),
            new Insets(0, 0, 0, 0)
        )
    );
    this.setBackground(bg);
}

private Color getRandomColor(Color color, double delta) {
    int index = (int)( (color.getRed()+getRandomCoefficient(delta))*255 );
    int r = ( index > 255 ) ? 255 : ( (index < 0) ? 0 : index );
    index = (int)( (color.getGreen()+getRandomCoefficient(delta))*255 );
    int g = ( index > 255 ) ? 255 : ( (index < 0) ? 0 : index );
    index = (int)( (color.getBlue()+getRandomCoefficient(delta))*255 );
    int b = ( index > 255 ) ? 255 : ( (index < 0) ? 0 : index );
    return Color.rgb(r, g, b);
}

private double getRandomCoefficient(double delta) {
    return ( rand.nextDouble()*2 - 1 ) * delta;
}

private int getRandomDuration() {
    return (int)(( rand.nextDouble()*2 - 1 ) * AnimatedGradients.durationDelta * AnimatedGradients.baseDuration) + AnimatedGradients.baseDuration;
}}

2. 这是一个动画图标的示例

public class AnimatedIcon extends WritableImage {
private ObjectProperty<Color> topColor = new SimpleObjectProperty();
private ObjectProperty<Color> bottomColor = new SimpleObjectProperty();
private Color oldTopColor;
private Color oldBottomColor;
private Timeline timeline;
private static final int DURATION = 5000;
private static final Random rand = new Random();

private static final int ICON_WIDTH = 32;
private static final int ICON_HEIGHT = 32;

private Stage primaryStage;

public AnimatedIcon(Stage primaryStage) {
    super(ICON_WIDTH, ICON_HEIGHT);
    this.primaryStage = primaryStage;
    oldTopColor = Color.rgb(0, 45, 0);
    oldBottomColor = Color.rgb(12, 128, 12);
    topColor.set(oldTopColor);
    bottomColor.set(oldBottomColor);
    createGraphics();
    bottomColor.addListener((obs, oldColor, newColor) -> {
        createGraphics();
    });
}

private void createGraphics() {
    PixelWriter pixelWriter = this.getPixelWriter();
    for (int y = 0; y < ICON_HEIGHT; y++) {
        for (int x = 0; x < ICON_WIDTH; x++) {
            double position = (double)x/(double)ICON_WIDTH;
            int r = (int)( ( topColor.get().getRed() - (topColor.get().getRed() - bottomColor.get().getRed())*position ) * 255 );
            int g = (int)( ( topColor.get().getGreen() - (topColor.get().getGreen() - bottomColor.get().getGreen())*position ) * 255 );
            int b = (int)( ( topColor.get().getBlue() - (topColor.get().getBlue() - bottomColor.get().getBlue())*position ) * 255 );
            double o = topColor.get().getOpacity() - (topColor.get().getOpacity() - bottomColor.get().getOpacity())*position;
            pixelWriter.setColor(x,y,Color.rgb(r,g,b,o));
        }
    }
    int index = primaryStage.getIcons().indexOf(this);
    if (index == 0) {
        primaryStage.getIcons().set(index, this);
    } else {
        primaryStage.getIcons().add(this);            
    }
}

public void startAnimation() {
    timeline = new Timeline();
    createTimelineContent();
    timeline.setOnFinished(ActionEvent -> {
        createTimelineContent();
        timeline.play();
    });
    timeline.play();
}

private void createTimelineContent() {
    timeline.getKeyFrames().clear();
    KeyFrame kf1 =
        new KeyFrame(Duration.ZERO,
            new KeyValue( topColor, oldTopColor ),
            new KeyValue( bottomColor, oldBottomColor ));
    oldTopColor = Color.rgb(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256), rand.nextDouble());
    oldBottomColor = Color.rgb(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256), 1);
    KeyFrame kf2 =
        new KeyFrame(new Duration(DURATION),
            new KeyValue( topColor, oldTopColor ),
            new KeyValue( bottomColor, oldBottomColor ));
    timeline.getKeyFrames().addAll( kf1, kf2 );
}}

这是位于上方的元素的主类

public class AnimatedGradients extends Application {
protected static int baseDuration = 10000;
protected static Color baseExternalColor = Color.rgb(0, 0, 0);
protected static Color baseInternalColor = Color.rgb(127, 127, 127);
protected static double durationDelta = 0.25;
protected static double externalDelta = 0.1;
protected static double internalDelta = 1;

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

@Override
public void start(Stage primaryStage) {
    HBox root = new HBox();
    root.setSpacing(1);
    root.setAlignment(Pos.CENTER);
    Scene scene = new Scene(root, 761, 500, Color.BLACK);        
    primaryStage.setScene(scene);
    AnimatedIcon ai = new AnimatedIcon(primaryStage);
    primaryStage.getIcons().add(ai);
    ai.startAnimation();
    primaryStage.setTitle("Animated Gradients");
    for (int i = 0; i < 10; i++) {
        FilledRegion gr = new FilledRegion();
        root.getChildren().add(gr);
        gr.startAnimation();
    }
    primaryStage.show();

}}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接