1

I wrote a JavaFX application.
I want to update Label width every 10ms, where the label represents a value.
Generaly, I succeeded to do this, but with a problem: the label blinks with small values, in opposite of big values.
In my previous works, I noticed that a Progressbar control has a smooth resizing animation.
So, I want to make this works like a Progressbar does (smoothly).


This is an example code explaining the problem:

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;


public class MainApplication extends Application {

    private static final String LABEL_STYLE = "-fx-font-weight: bold; -fx-border-style: solid; -fx-pref-height: 38; -fx-background-color: ";
    private static final double MAX_WIDTH = 760;
    private static final double MAX_VALUE = 60000;
    private static final double VALUE_PER_PIXEL_RATIO = MAX_VALUE / MAX_WIDTH;
    private static final int ANIMATION_DURATION = 9000;
    private static final int KEYFRAME_DURATION = 10;
    private static final int ANIMATION_CYCLES = ANIMATION_DURATION / KEYFRAME_DURATION;

    private final Label label_1 = new Label();
    private final Label label_2 = new Label();
    private final Label label_3 = new Label();



    @Override
    public void start(Stage primaryStage) {

        label_1.setStyle(LABEL_STYLE + " #4363d8; ");
        label_2.setStyle(LABEL_STYLE + " #ffe119; ");
        label_3.setStyle(LABEL_STYLE + " #3cb44b; ");

        VBox root = new VBox();
        root.setSpacing(10);
        root.setPadding(new Insets(40, 30, 30, 30));
        root.getChildren().addAll(label_1, label_2, label_3);
        root.getChildren().addAll(new Label("Label 1 : Good (Smooth)"),
                new Label("Label 2 : Acceptable"),
                new Label("Label 3 : Weak (Blinks)"));


        Scene scene = new Scene(root);

        primaryStage.setScene(scene);
        primaryStage.setWidth(800);
        primaryStage.setHeight(350);
        primaryStage.show();

        playAnimation();

    }


    private void playAnimation() {

        MyData myData_1 = new MyData(58000, ANIMATION_CYCLES, VALUE_PER_PIXEL_RATIO);
        MyData myData_2 = new MyData(20000, ANIMATION_CYCLES, VALUE_PER_PIXEL_RATIO);
        MyData myData_3 = new MyData(4000, ANIMATION_CYCLES, VALUE_PER_PIXEL_RATIO);

        final Timeline timeline = new Timeline();
        timeline.setCycleCount(ANIMATION_CYCLES);

        timeline.getKeyFrames().add(new KeyFrame(Duration.millis(KEYFRAME_DURATION), new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent actionEvent) {

                label_1.setPrefWidth(myData_1.getCurrentWidth());
                label_2.setPrefWidth(myData_2.getCurrentWidth());
                label_3.setPrefWidth(myData_3.getCurrentWidth());
            }

        }));

        timeline.play();

        // Replay animation
        timeline.setOnFinished(event -> playAnimation());

    }

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

class MyData  {

    private double currentValue;
    private double currentWidth;
    private double incrementStep;
    private static double VALUE_PER_PIXEL_RATIO;

    public MyData(int maxValue, double cycles, double ratio) {
        super();
        this.incrementStep = maxValue / cycles;
        VALUE_PER_PIXEL_RATIO = ratio;
    }

    public double getCurrentWidth() {
        currentWidth = currentValue / VALUE_PER_PIXEL_RATIO;
        currentValue += incrementStep;
        return currentWidth;
    }
}

Thanks.

Doros Barmajia
  • 502
  • 1
  • 5
  • 13

1 Answers1

1

It doesn’t blink for me, using JavaFX 15 on Debian Linux, with an Intel integrated graphics chip.

You are doing a lot of work to animate the Labels yourself. Rather than doing the animation yourself, you can let the Timeline do the interpolation:

private void animate(Label label,
                     double value) {

    Timeline timeline = new Timeline(
        1000 / 10,  // every 10 ms
        new KeyFrame(Duration.millis(58000 / value * animationDuration),
            new KeyValue(label.prefWidthProperty(), 800 - 60)));

    timeline.setCycleCount(Timeline.INDEFINITE);
    timeline.play();
}

private void playAnimation() {
    animate(label_1, 58000);
    animate(label_2, 20000);
    animate(label_3, 4000);
}

I’m not sure forcing 100 frames per second is actually useful. JavaFX seems to be pretty good at creating smooth animation without specifying the frame rate:

private void animate(Label label,
                     double value) {

    Timeline timeline = new Timeline(
        new KeyFrame(Duration.millis(58000 / value * animationDuration),
            new KeyValue(label.prefWidthProperty(), 800 - 60)));

    timeline.setCycleCount(Timeline.INDEFINITE);
    timeline.play();
}

private void playAnimation() {
    animate(label_1, 58000);
    animate(label_2, 20000);
    animate(label_3, 4000);
}
VGR
  • 40,506
  • 4
  • 48
  • 63
  • Thanks for your answer. What I mean by blinks is the little visible jumps (The green label growup with litle visible jumps (blinks) not like the blue label). Your code give me the same result like my code, even I have the same environment like you (Intel i5, 8GB RAM, 1GB GPU, JavaFx 15, Linux Mint). – Doros Barmajia Nov 26 '20 at 00:15
  • I forget to say that I use EventHandler() instead of KeyValue, because I have other tasks to do inside Timeline. – Doros Barmajia Nov 26 '20 at 00:19
  • I see the “blink” effect you are talking about; the animation is not smooth for the third bar. I don’t know what’s causing it. – VGR Nov 26 '20 at 03:32
  • I noticed that only whith small jumps. Thanks. – Doros Barmajia Nov 26 '20 at 11:38