我正在使用 JavaFX (IntelliJ) 创建一个媒体播放器。我将 MediaView 放在 BorderPane 中心的 Pane(mediaPane) 内。我希望 mediaView 始终位于窗格的中心,同时保持其尺寸与窗格绑定。
这是我的 FXML 文件。
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.media.MediaView?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="557.0" prefWidth="846.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.jmediaplayer.MediaPlayer">
<children>
<BorderPane fx:id="background" layoutX="193.0" layoutY="111.0" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<bottom>
<VBox fx:id="progressParent" prefHeight="129.0" prefWidth="846.0" style="-fx-background-color: #36454F;" BorderPane.alignment="CENTER">
<children>
<Slider fx:id="progressSlider" />
<HBox fx:id="controlBar" alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="10.0">
<children>
<ComboBox fx:id="speedBox" onAction="#changeSpeed" prefHeight="56.0" prefWidth="150.0" promptText="speed" />
<Button fx:id="mediaPreviousButton" mnemonicParsing="false" onAction="#mediaPrevious" prefHeight="53.0" prefWidth="100.0" text="Previous" />
<Button fx:id="mediaResetButton" layoutX="72.0" layoutY="10.0" mnemonicParsing="false" onAction="#mediaReset" prefHeight="53.0" prefWidth="100.0" text="Reset" />
<Button fx:id="mediaActionButton" layoutX="134.0" layoutY="10.0" mnemonicParsing="false" onAction="#mediaAction" prefHeight="53.0" prefWidth="100.0" text="Play" />
<Button fx:id="mediaNextbutton" layoutX="196.0" layoutY="10.0" mnemonicParsing="false" onAction="#mediaNext" prefHeight="53.0" prefWidth="100.0" text="Next" />
<Slider fx:id="volumeBar" prefHeight="16.0" prefWidth="176.0" value="50.0" />
</children>
</HBox>
</children>
</VBox>
</bottom>
<top>
<VBox fx:id="titleParent" prefHeight="100.0" prefWidth="846.0" style="-fx-background-color: #36454F;" BorderPane.alignment="CENTER">
<children>
<Label fx:id="appTitle" alignment="CENTER" prefHeight="51.0" prefWidth="853.0" style="-fx-text-fill: #fff;" text="JMediaPlayer">
<font>
<Font name="System Bold Italic" size="42.0" />
</font>
</Label>
<HBox fx:id="mediaSelect" alignment="CENTER" prefHeight="46.0" prefWidth="846.0" spacing="10.0">
<children>
<Label fx:id="mediaTitle" prefHeight="16.0" prefWidth="705.0" style="-fx-text-fill: #fff;" text="Title">
<font>
<Font size="20.0" />
</font>
</Label>
<Button fx:id="openFileButton" mnemonicParsing="false" onAction="#selectFile" text="Open" />
</children>
</HBox>
</children>
</VBox>
</top>
<center>
<VBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER" VBox.vgrow="ALWAYS">
<children>
<StackPane alignment="CENTER" VBox.vgrow="ALWAYS">
<children>
<Pane fx:id="mediaPane" prefHeight="328.0" style="-fx-border-color: red; -fx-border-width: 2;" VBox.vgrow="ALWAYS">
<children>
<MediaView fx:id="mediaView" fitHeight="320.0" fitWidth="400.0" onMouseClicked="#mediaViewClicked" />
</children>
</Pane>
</children>
</StackPane>
</children>
</VBox>
</center>
</BorderPane>
</children>
</AnchorPane>
这是我的控制器类
package com.example.jmediaplayer;
import static com.example.jmediaplayer.constants.Constants.*;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.media.Media;
import javafx.scene.media.MediaView;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.util.Duration;
import java.io.File;
import java.net.URL;
import java.util.Objects;
import java.util.ResourceBundle;
public class MediaPlayer implements Initializable {
@FXML
private BorderPane background;
@FXML
private Pane mediaPane;
@FXML
private Label mediaTitle, appTitle;
@FXML
private Button mediaActionButton, mediaNextbutton, mediaPreviousButton, mediaResetButton, openFileButton;
@FXML
private Slider progressSlider;
@FXML
private ComboBox<String> speedBox;
@FXML
private Slider volumeBar;
@FXML
private VBox progressParent, titleParent;
@FXML
private HBox controlBar, mediaSelect;
@FXML
private MediaView mediaView;
private Media media;
private javafx.scene.media.MediaPlayer mediaPlayer;
private boolean isPlaying = false, isSeeking = false;
private File file;
private final String[] speeds = {"0.25x", "0.5x", "1x", "1.25x", "1.5x", "1.75x", "2x"};
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
setLayout();
// mediaActionButton.setGraphic(new FontIcon());
configureProgressSlider();
for (String speed : speeds) {
speedBox.getItems().add(speed);
}
speedBox.setOnAction(this::changeSpeed);
mediaView.setFocusTraversable(true);
volumeBar.valueProperty().addListener((observableValue, number, t1) -> mediaPlayer.setVolume(volumeBar.getValue() * 0.01));
}
private void configureProgressSlider() {
progressSlider.setMin(0);
progressSlider.setMax(1);
progressSlider.setValueChanging(true);
progressSlider.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> isSeeking = true);
progressSlider.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> isSeeking = false);
progressSlider.valueProperty().addListener((observableValue, number, newValue) -> {
if (isSeeking) {
double newTime = newValue.doubleValue() * media.getDuration().toMillis();
mediaPlayer.seek(Duration.millis(newTime));
}
});
}
void setLayout() {
progressSlider.prefWidthProperty().bind(progressParent.widthProperty());
controlBar.prefWidthProperty().bind(progressParent.widthProperty());
appTitle.prefWidthProperty().bind(titleParent.widthProperty());
mediaSelect.prefWidthProperty().bind(titleParent.widthProperty());
mediaPane.prefWidthProperty().bind(background.widthProperty());
mediaPane.prefHeightProperty().bind(background.heightProperty());
// mediaView.setLayoutX((background.getWidth() - mediaView.getFitWidth()) / 2);
// mediaView.setLayoutY((mediaPane.getHeight() - mediaView.getFitHeight()) / 2);
// imagePane.prefWidthProperty().bind(background.widthProperty());
mediaView.fitWidthProperty().bind(mediaPane.widthProperty());
mediaView.fitHeightProperty().bind(mediaPane.heightProperty());
}
@FXML
void changeSpeed(ActionEvent event) {
if (speedBox.getValue() == null) {
mediaPlayer.setRate(1);
} else {
mediaPlayer.setRate(Double.parseDouble(speedBox.getValue().substring(0, speedBox.getValue().length() - 1)));
}
}
@FXML
void mediaAction(ActionEvent event) {
if (isPlaying) {
mediaPlayer.pause();
mediaActionButton.setText("Play");
} else {
changeSpeed(null);
mediaPlayer.setVolume(volumeBar.getValue() * 0.01);
mediaPlayer.play();
mediaActionButton.setText("Pause");
}
isPlaying = !isPlaying;
}
@FXML
void mediaNext(ActionEvent event) {
}
@FXML
void mediaPrevious(ActionEvent event) {
}
@FXML
void mediaReset(ActionEvent event) {
if (mediaPlayer != null) {
mediaPlayer.stop();
}
}
@FXML
void selectFile(ActionEvent event) {
FileChooser fileChooser = new FileChooser();
ExtensionFilter extensionFilter1 = new ExtensionFilter("MP4 files (*.mp4)", "*.mp4");
ExtensionFilter extensionFilter2 = new ExtensionFilter("MP3 files (*.mp3)", "*.mp3");
fileChooser.getExtensionFilters().add(extensionFilter1);
fileChooser.getExtensionFilters().add(extensionFilter2);
fileChooser.setSelectedExtensionFilter(extensionFilter1);
file = fileChooser.showOpenDialog(null);
setupMedia(file);
}
void setupMedia(File file) {
if (file != null) {
mediaReset(null);
String extension = getFileExtension(file);
mediaTitle.setText(file.getName());
media = new Media(file.toURI().toString());
mediaPlayer = new javafx.scene.media.MediaPlayer(media);
mediaPlayer.currentTimeProperty().addListener((observableValue, duration, newTime) -> {
double value = newTime.toMillis() / media.getDuration().toMillis();
progressSlider.setValue(value);
});
if ("mp3".equalsIgnoreCase(extension)) {
mediaView.setVisible(false);
// imageView.setVisible(true);
Image bg_for_mp3 = new Image(Objects.requireNonNull(getClass().getResource(IMAGE_PATH)).toExternalForm());
// imageView.setImage(bg_for_mp3);
} else if ("mp4".equalsIgnoreCase(extension)) {
// imageView.setVisible(false);
mediaView.setVisible(true);
mediaView.setMediaPlayer(mediaPlayer);
}
if (isPlaying) {
mediaAction(null);
}
progressSlider.setValue(0);
}
}
@FXML
void mediaViewClicked(MouseEvent event) {
// System.out.println("Set Layout X : " + (background.getWidth() - mediaView.getFitWidth()) / 2);
// System.out.println("Set Layout Y : " + (mediaPane.getHeight() - mediaView.getFitHeight()) / 2);
Platform.runLater(() -> {
System.out.println(background.getWidth() == mediaView.getFitWidth());
System.out.println(mediaPane.getWidth() == mediaView.getFitWidth());
});
}
String getFileExtension(File file) {
int index = file.getName().lastIndexOf('.');
if (index > 0) {
return file.getName().substring(index+1);
}
return "";
}
}
我尝试使用绑定属性将mediaPane与背景(BorderPane)绑定并将mediaView与mediaPane绑定,例如
mediaPane.prefWidthProperty().bind(background.widthProperty());
mediaPane.prefHeightProperty().bind(background.heightProperty());
mediaView.fitWidthProperty().bind(mediaPane.widthProperty());
mediaView.fitHeightProperty().bind(mediaPane.heightProperty());
它完成了使 mediaView 响应的工作,但它仍然只停留在 mediaPane 的左上角位置,因为它的layoutX和layoutY设置为0。 因此,我尝试手动将layoutX和layoutY设置为
mediaView.setLayoutX((background.getWidth() - mediaView.getFitWidth()) / 2);
mediaView.setLayoutY((mediaPane.getHeight() - mediaView.getFitHeight()) / 2);
但它没有做任何事情。我还尝试将
background.getWidth()
、mediaView.getFitWidth()
和 mediaPane.getWidth()
的值打印在一起,似乎它们的值始终相等,因此 (background.getWidth() - mediaView.getFitWidth()) / 2
的值始终变为 0
。
然后我尝试删除 Pane 并将 mediaView 直接添加到 BorderPane 的中心,但是绑定 heightProperty 变得很困难,因为我的菜单位于顶部,控件位于底部。
我找不到任何其他解决方案。
创建自动调整大小
MediaView
,与创建自动调整大小ImageView
类似。
尝试运行以下应用程序并调整窗口大小以查看效果。
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.layout.Region;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Stage;
public class MediaApp extends Application {
public static final String MEDIA_URL = "https://download.samplelib.com/mp4/sample-5s.mp4";
@Override
public void start(Stage stage) {
MediaPlayer mediaPlayer = new MediaPlayer(
new Media(MEDIA_URL)
);
mediaPlayer.setCycleCount(MediaPlayer.INDEFINITE);
MediaView mediaView = new MediaView(mediaPlayer);
MediaViewPane mediaViewPane = new MediaViewPane(mediaView);
mediaViewPane.setPrefSize(600, 400);
stage.setScene(new Scene(mediaViewPane));
stage.show();
}
public static void main(String[] args) {
launch();
}
class MediaViewPane extends Region {
private ObjectProperty<MediaView> MediaViewProperty = new SimpleObjectProperty<>();
public ObjectProperty<MediaView> MediaViewProperty() {
return MediaViewProperty;
}
public MediaView getMediaView() {
return MediaViewProperty.get();
}
public void setMediaView(MediaView MediaView) {
this.MediaViewProperty.set(MediaView);
}
public MediaViewPane() {
this(new MediaView());
}
@Override
protected void layoutChildren() {
MediaView mediaView = MediaViewProperty.get();
if (mediaView != null) {
mediaView.setFitWidth(getWidth());
mediaView.setFitHeight(getHeight());
layoutInArea(mediaView, 0, 0, getWidth(), getHeight(), 0, HPos.CENTER, VPos.CENTER);
}
super.layoutChildren();
}
public MediaViewPane(MediaView MediaView) {
MediaViewProperty.addListener((arg0, oldMediaView, newMediaView) -> {
if (oldMediaView != null) {
getChildren().remove(oldMediaView);
}
if (newMediaView != null) {
getChildren().add(newMediaView);
}
});
this.MediaViewProperty.set(MediaView);
}
}
}
如果
MediaView
是 setPreserveRatio
(默认),
true
将自动成为 信箱。