JavaFX Marquee Animation

2019-07-08 22:48发布

You maybe have seen some apps that when the String in a tab can't be fully shown then it is animated and is going back and front so the user can see what String the tab contains.Android is doing that is settings when your phone display is not enough to show the the whole label.

Below is a code to achieve it in JavaFX using Service,but it is not a good way.

The Question is: Here is how i can do it using Animation or another build in JavaFX class?

Code:

import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import tools.InfoTool;

public class MoveTitleService extends Service<Void>{
    private String title;
    volatile boolean doAnimation;
    private int counter;
    public Label movingText = new Label("A reallyyy big teeeeexxxxxxxxxxxxxxxxxxxxxxxt");


    /**
     *Constructor
    */
    public MoveTitleService() {
         movingText.setFont(Font.font("null",FontWeight.BOLD,14));
         movingText.setTextFill(Color.WHITE);   

         setOnSucceeded( s ->{
             movingText.setText("");
         });
    }

    //Start the Service
    public void startTheService(String title) {
        this.title = title;
        doAnimation = true;

        restart();
    }


    //Stop the Service
    public void stopService(){
        doAnimation=false;
    }

    @Override
    protected Task<Void> createTask() {
        return new Task<Void>() {
            @Override
            protected Void call() throws Exception {

                while (doAnimation) {
                    //System.out.println("MoveTitleService is Running...");

                    // One letter at a time
                    for (int m = 0; m <= title.length(); m++) {
                        counter=m;

                        Platform.runLater( () ->{
                            movingText.setText(title.substring(0, counter) + addSpaces(title.length() - counter));      
                        });

                        if(!doAnimation) break;
                        Thread.sleep(150);
                    }

                    // Disappearing to back
                    for (int m = 0; m < title.length(); m++) {
                        counter=m;

                        Platform.runLater( () ->{
                            movingText.setText(title.substring(counter));
                        });

                        if(!doAnimation) break;
                        Thread.sleep(150);
                    }

                    // Appearing to front
                    for (int m = 1; m <= title.length(); m++) {
                        counter=m;

                        Platform.runLater( () ->{
                            movingText.setText(title.substring(title.length() - counter));
                        });

                        if(!doAnimation) break;
                        Thread.sleep(150);
                    }

                    if(!doAnimation) break;
                    for(int i=0; i<3000/150; i++)
                        Thread.sleep(150);
                    Thread.sleep(3000);

                }

                return null;
            }

            private String addSpaces(int spaces) {
                String z = "";
                for (int i = 0; i <= spaces; i++)
                    z += " ";

                return z;
            }

        };
    }

}

1条回答
欢心
2楼-- · 2019-07-08 23:36

You could use a Timeline for this:

// min distance to Pane bounds
private static final double OFFSET = 25;

@Override
public void start(Stage primaryStage) {
    Text text = new Text("A reallyyy big teeeeexxxxxxxxxxxxxxxxxxxxxxxt!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    text.setLayoutY(25);
    text.setManaged(false);
    text.setLayoutX(OFFSET);

    Pane pane = new Pane(text);
    pane.setMinHeight(50);

    Timeline timeline = new Timeline();

    KeyFrame updateFrame = new KeyFrame(Duration.seconds(1 / 60d), new EventHandler<ActionEvent>() {

        private boolean rightMovement;

        @Override
        public void handle(ActionEvent event) {
            double tW = text.getLayoutBounds().getWidth();
            double pW = pane.getWidth();
            double layoutX = text.getLayoutX();

            if (2 * OFFSET + tW <= pW && layoutX >= OFFSET) {
                // stop, if the pane is large enough and the position is correct
                text.setLayoutX(OFFSET);
                timeline.stop();
            } else {
                if ((rightMovement && layoutX >= OFFSET) || (!rightMovement && layoutX + tW + OFFSET <= pW)) {
                    // invert movement, if bounds are reached
                    rightMovement = !rightMovement;
                }

                // update position
                if (rightMovement) {
                    layoutX += 1;
                } else {
                    layoutX -= 1;
                }
                text.setLayoutX(layoutX);
            }
        }
    });

    timeline.getKeyFrames().add(updateFrame);
    timeline.setCycleCount(Animation.INDEFINITE);

    // listen to bound changes of the elements to start/stop the animation
    InvalidationListener listener = o -> {
        double textWidth = text.getLayoutBounds().getWidth();
        double paneWidth = pane.getWidth();
        if (textWidth + 2 * OFFSET > paneWidth
                && timeline.getStatus() != Animation.Status.RUNNING) {
            timeline.play();
        }
    };

    text.layoutBoundsProperty().addListener(listener);
    pane.widthProperty().addListener(listener);

    Scene scene = new Scene(pane);

    primaryStage.setScene(scene);
    primaryStage.show();
}

Note that repeadedly updating the position yourself needs to be done, since Animations cannot be adjusted while running, so for any resizing to take effect during the animation, you need repeated updates...

查看更多
登录 后发表回答