如何使用动画使 JavaFX 滑块旋钮仅移动到刻度线?
How to use animation to make a JavaFX slider knob move to tick marks only?
继续讨论这个问题,
我添加了一个“播放”按钮和一个时间轴,这样当用户单击“播放”按钮时,滑块的旋钮会重复前进到下一个刻度线,直到它到达滑块的末端或用户单击“停止”按钮。不幸的是,动画运行的时间越长,旋钮在刻度线之间移动的越多(是的,setSnapToTicks
是正确的)。此外,滑块的值不是我所期望的。理想情况下,它应该是“-60、-59、-58 等”。但时间轴一次移动滑块旋钮的一小部分(例如 -55.188833333333335)。很长一段时间以来,我一直在努力解决这个问题。这是我的代码:
public class SliderTest extends Application {
StringConverter<Double> stringConverter = new StringConverter<>() {
@Override
public String toString( Double object ) {
long seconds = object.longValue();
long minutes = TimeUnit.SECONDS.toMinutes( seconds );
long remainingseconds = Math.abs( seconds - TimeUnit.MINUTES.toSeconds( minutes ) );
return String.format( "%02d", minutes ) + ":" + String.format( "%02d", remainingseconds );
}
@Override
public Double fromString( String string ) {
return null;
}
};
@Override
public void start( Stage primaryStage ) throws Exception {
try {
Pane pane = new Pane();
pane.setStyle( "-fx-background-color: black;" );
Slider slider = new Slider( -60, 0, -60 );
slider.setBlockIncrement( 60 );
slider.setMajorTickUnit( 60 );
slider.setMinorTickCount( 60 );
slider.setShowTickLabels( true );
slider.setShowTickMarks( true );
slider.setSnapToTicks( true );
slider.setLabelFormatter( stringConverter );
slider.setPrefWidth( 1303 );
Button playStopButton = new Button( "Play" );
HBox buttonHBox = new HBox( playStopButton );
buttonHBox.setAlignment( Pos.CENTER );
Text sliderTime = new Text( "Time" );
sliderTime.setFont( new Font( 12 ) );
sliderTime.setFill( Color.WHITE );
sliderTime.setText( stringConverter.toString( slider.getValue() ) );
HBox hbox = new HBox( slider, sliderTime, buttonHBox );
hbox.setPrefWidth( 1428 );
hbox.setSpacing( 10 );
hbox.setAlignment( Pos.TOP_CENTER );
pane.getChildren().add( hbox );
Scene scene = new Scene( pane );
scene.getStylesheets().add( getClass().getResource( "/css/slider.css" ).toExternalForm() );
primaryStage.setScene( scene );
primaryStage.show();
Timeline timeline = new Timeline(
new KeyFrame( Duration.ZERO, new KeyValue( slider.valueProperty(), slider.getValue() ) ),
new KeyFrame( Duration.seconds( -slider.getValue() ), new KeyValue( slider.valueProperty(), 0 ) ) );
slider.valueProperty().addListener( ( observable, oldValue, newValue ) -> {
slider.setValue( newValue.intValue() );
System.out.println( slider.getValue() + " | " + newValue.doubleValue() );
// Set the text to the slider's new value.
sliderTime.setText( stringConverter.toString( slider.getValue() ) );
if ( newValue.doubleValue() == 0 ) {
// Reset the timeline and the text for the sliderButton.
timeline.stop();
timeline.getKeyFrames().clear();
playStopButton.setText( "Stop" );
}
} );
slider.setOnMousePressed( event -> {
// On mouse down, reset the timeline.
timeline.stop();
timeline.getKeyFrames().clear();
} );
slider.setOnMouseReleased( event -> {
// Set the new start position of the timeline.
timeline.getKeyFrames().add(
new KeyFrame( Duration.ZERO, new KeyValue( slider.valueProperty(), slider.getValue() ) ) );
timeline.getKeyFrames().add( new KeyFrame( Duration.seconds( -slider.getValue() ),
new KeyValue( slider.valueProperty(), 0 ) ) );
// Play the animation only if it was playing before.
if ( playStopButton.getText().equals( "Pause" ) ) {
timeline.play();
}
// Change the image for the playStopButton to stop when the thumb is moved to zero.
else if ( playStopButton.getText().equals( "Stop" ) && ( slider.getValue() < 0 ) ) {
playStopButton.setText( "Play" );
}
} );
playStopButton.setOnMouseClicked( event -> {
// Ignore mouse clicks on the playStopButton when the slider is at time zero.
if ( slider.getValue() != 0 ) {
if ( ( timeline.getStatus() == Status.STOPPED ) || ( timeline.getStatus() == Status.PAUSED ) ) {
playStopButton.setText( "Pause" );
timeline.play();
}
else if ( timeline.getStatus() == Status.RUNNING ) {
playStopButton.setText( "Play" );
timeline.pause();
}
}
} );
} catch ( Exception e ) {
e.printStackTrace();
}
}
public static void main( String[] args ) {
launch( args );
}
部分问题出在我对 Slider
的设置中。问题的另一部分是我对 Timeline
的定义。基本上,我将它留到 Java 来定义滑块按钮移动到的所有值,而不管刻度线在哪里,这不是我想要的(尽管它确实可以实现流畅的动画)。但在进行以下更改后,滑块的按钮每秒都会前进到下一个刻度线,这正是我想要的。与之前滑块按钮始终位于两个刻度线之间的情况相比。
slider.setMajorTickUnit( 1 );
Timeline timeline = new Timeline(
new KeyFrame( Duration.seconds( 1 ), event -> {
slider.setValue( slider.getValue() + 1 );
} ) );
timeline.setCycleCount( Math.abs( Double.valueOf( slider.getValue() ).intValue() ) );
继续讨论这个问题,
setSnapToTicks
是正确的)。此外,滑块的值不是我所期望的。理想情况下,它应该是“-60、-59、-58 等”。但时间轴一次移动滑块旋钮的一小部分(例如 -55.188833333333335)。很长一段时间以来,我一直在努力解决这个问题。这是我的代码:
public class SliderTest extends Application {
StringConverter<Double> stringConverter = new StringConverter<>() {
@Override
public String toString( Double object ) {
long seconds = object.longValue();
long minutes = TimeUnit.SECONDS.toMinutes( seconds );
long remainingseconds = Math.abs( seconds - TimeUnit.MINUTES.toSeconds( minutes ) );
return String.format( "%02d", minutes ) + ":" + String.format( "%02d", remainingseconds );
}
@Override
public Double fromString( String string ) {
return null;
}
};
@Override
public void start( Stage primaryStage ) throws Exception {
try {
Pane pane = new Pane();
pane.setStyle( "-fx-background-color: black;" );
Slider slider = new Slider( -60, 0, -60 );
slider.setBlockIncrement( 60 );
slider.setMajorTickUnit( 60 );
slider.setMinorTickCount( 60 );
slider.setShowTickLabels( true );
slider.setShowTickMarks( true );
slider.setSnapToTicks( true );
slider.setLabelFormatter( stringConverter );
slider.setPrefWidth( 1303 );
Button playStopButton = new Button( "Play" );
HBox buttonHBox = new HBox( playStopButton );
buttonHBox.setAlignment( Pos.CENTER );
Text sliderTime = new Text( "Time" );
sliderTime.setFont( new Font( 12 ) );
sliderTime.setFill( Color.WHITE );
sliderTime.setText( stringConverter.toString( slider.getValue() ) );
HBox hbox = new HBox( slider, sliderTime, buttonHBox );
hbox.setPrefWidth( 1428 );
hbox.setSpacing( 10 );
hbox.setAlignment( Pos.TOP_CENTER );
pane.getChildren().add( hbox );
Scene scene = new Scene( pane );
scene.getStylesheets().add( getClass().getResource( "/css/slider.css" ).toExternalForm() );
primaryStage.setScene( scene );
primaryStage.show();
Timeline timeline = new Timeline(
new KeyFrame( Duration.ZERO, new KeyValue( slider.valueProperty(), slider.getValue() ) ),
new KeyFrame( Duration.seconds( -slider.getValue() ), new KeyValue( slider.valueProperty(), 0 ) ) );
slider.valueProperty().addListener( ( observable, oldValue, newValue ) -> {
slider.setValue( newValue.intValue() );
System.out.println( slider.getValue() + " | " + newValue.doubleValue() );
// Set the text to the slider's new value.
sliderTime.setText( stringConverter.toString( slider.getValue() ) );
if ( newValue.doubleValue() == 0 ) {
// Reset the timeline and the text for the sliderButton.
timeline.stop();
timeline.getKeyFrames().clear();
playStopButton.setText( "Stop" );
}
} );
slider.setOnMousePressed( event -> {
// On mouse down, reset the timeline.
timeline.stop();
timeline.getKeyFrames().clear();
} );
slider.setOnMouseReleased( event -> {
// Set the new start position of the timeline.
timeline.getKeyFrames().add(
new KeyFrame( Duration.ZERO, new KeyValue( slider.valueProperty(), slider.getValue() ) ) );
timeline.getKeyFrames().add( new KeyFrame( Duration.seconds( -slider.getValue() ),
new KeyValue( slider.valueProperty(), 0 ) ) );
// Play the animation only if it was playing before.
if ( playStopButton.getText().equals( "Pause" ) ) {
timeline.play();
}
// Change the image for the playStopButton to stop when the thumb is moved to zero.
else if ( playStopButton.getText().equals( "Stop" ) && ( slider.getValue() < 0 ) ) {
playStopButton.setText( "Play" );
}
} );
playStopButton.setOnMouseClicked( event -> {
// Ignore mouse clicks on the playStopButton when the slider is at time zero.
if ( slider.getValue() != 0 ) {
if ( ( timeline.getStatus() == Status.STOPPED ) || ( timeline.getStatus() == Status.PAUSED ) ) {
playStopButton.setText( "Pause" );
timeline.play();
}
else if ( timeline.getStatus() == Status.RUNNING ) {
playStopButton.setText( "Play" );
timeline.pause();
}
}
} );
} catch ( Exception e ) {
e.printStackTrace();
}
}
public static void main( String[] args ) {
launch( args );
}
部分问题出在我对 Slider
的设置中。问题的另一部分是我对 Timeline
的定义。基本上,我将它留到 Java 来定义滑块按钮移动到的所有值,而不管刻度线在哪里,这不是我想要的(尽管它确实可以实现流畅的动画)。但在进行以下更改后,滑块的按钮每秒都会前进到下一个刻度线,这正是我想要的。与之前滑块按钮始终位于两个刻度线之间的情况相比。
slider.setMajorTickUnit( 1 );
Timeline timeline = new Timeline(
new KeyFrame( Duration.seconds( 1 ), event -> {
slider.setValue( slider.getValue() + 1 );
} ) );
timeline.setCycleCount( Math.abs( Double.valueOf( slider.getValue() ).intValue() ) );