如何应用JavaFX开发用户界面
概述
JavaFX是用于构建富互联网应用程序的Java库。使用JavaFX开发的应用程序可以在各种设备上运行,如台式计算机,手机,物联网
设备,平板电脑等。这一章主要是介绍如何应用JavaFX使用编程声明方式开发用户界面。
编程与声明创建用户界面
以节点为中心的UI的简介
package sample;import java.util.List;import javafx.application.Application;import javafx.beans.property.SimpleStringProperty;import javafx.beans.property.StringProperty;import javafx.geometry.Rectangle2D;import javafx.geometry.VPos;import javafx.scene.Group;import javafx.scene.Scene;import javafx.scene.control.Button;import javafx.scene.control.CheckBox;import javafx.scene.control.Label;import javafx.scene.control.TextField;import javafx.scene.input.MouseEvent;import javafx.scene.layout.HBox;import javafx.scene.layout.VBox;import javafx.scene.paint.Color;import javafx.scene.shape.Rectangle;import javafx.scene.text.Text;import javafx.stage.Screen;import javafx.stage.Stage;import javafx.stage.StageStyle;import javafx.stage.WindowEvent;public class StageCoachMain extends Application { StringProperty title = new SimpleStringProperty(); Text textStageX; Text textStageY; Text textStageW; Text textStageH; Text textStageF; CheckBox checkBoxResizable; CheckBox checkBoxFullScreen; double dragAnchorX; double dragAnchorY; public static void main(String[] args) { Application.launch(args); } @Override public void start(Stage stage) { //设置舞台 StageStyle stageStyle = StageStyle.DECORATED; List<String> unnamedParams = getParameters().getUnnamed(); if (unnamedParams.size() > 0) { String stageStyleParam = unnamedParams.get(0); if (stageStyleParam.equalsIgnoreCase("transparent")) { stageStyle = StageStyle.TRANSPARENT; } else if (stageStyleParam.equalsIgnoreCase("undecorated")) { stageStyle = StageStyle.UNDECORATED; } else if (stageStyleParam.equalsIgnoreCase("utility")) { stageStyle = StageStyle.UTILITY; } } final Stage stageRef = stage; Group rootGroup; TextField titleTextField; Button toBackButton = new Button("toBack()"); toBackButton.setOnAction(e -> stageRef.toBack()); Button toFrontButton = new Button("toFront()"); toFrontButton.setOnAction(e -> stageRef.toFront()); Button closeButton = new Button("close()"); //关闭舞台并检测何时关闭 closeButton.setOnAction(e -> stageRef.close()); //绘制圆弧矩形 Rectangle blue = new Rectangle(250, 350, Color.SKYBLUE); blue.setArcHeight(50); blue.setArcWidth(50); textStageX = new Text(); textStageX.setTextOrigin(VPos.TOP); textStageY = new Text(); textStageY.setTextOrigin(VPos.TOP); textStageH = new Text(); textStageH.setTextOrigin(VPos.TOP); textStageW = new Text(); textStageW.setTextOrigin(VPos.TOP); textStageF = new Text(); textStageF.setTextOrigin(VPos.TOP); checkBoxResizable = new CheckBox("resizable"); checkBoxResizable.setDisable(stageStyle == StageStyle.TRANSPARENT || stageStyle == StageStyle.UNDECORATED); checkBoxFullScreen = new CheckBox("fullScreen"); titleTextField = new TextField("Stage Coach"); Label titleLabel = new Label("title"); //使用UI布局容器 HBox titleBox = new HBox(titleLabel, titleTextField); VBox contentBox = new VBox( textStageX, textStageY, textStageW, textStageH, textStageF, checkBoxResizable, checkBoxFullScreen, titleBox, toBackButton, toFrontButton, closeButton); contentBox.setLayoutX(30); contentBox.setLayoutY(20); contentBox.setSpacing(10); rootGroup = new Group(blue, contentBox); //用节点填充场景 Scene scene = new Scene(rootGroup, 270, 370); //设置场景中背景 scene.setFill(Color.TRANSPARENT);//when mouse button is pressed, save the initial position of screen rootGroup.setOnMousePressed((MouseEvent me) -> { dragAnchorX = me.getScreenX() - stageRef.getX(); dragAnchorY = me.getScreenY() - stageRef.getY(); });//when screen is dragged, translate it accordingly rootGroup.setOnMouseDragged((MouseEvent me) -> { stageRef.setX(me.getScreenX() - dragAnchorX); stageRef.setY(me.getScreenY() - dragAnchorY); }); textStageX.textProperty().bind(new SimpleStringProperty("x: ") .concat(stageRef.xProperty().asString())); textStageY.textProperty().bind(new SimpleStringProperty("y: ") .concat(stageRef.yProperty().asString())); textStageW.textProperty().bind(new SimpleStringProperty("width: ") .concat(stageRef.widthProperty().asString())); textStageH.textProperty().bind(new SimpleStringProperty("height: ") .concat(stageRef.heightProperty().asString())); textStageF.textProperty().bind(new SimpleStringProperty("focused: ") .concat(stageRef.focusedProperty().asString())); //控制舞台是否可以改变大小 stage.setResizable(true); checkBoxResizable.selectedProperty() .bindBidirectional(stage.resizableProperty()); // 让舞台全屏 checkBoxFullScreen.selectedProperty().addListener((ov, oldValue, newValue) -> { stageRef.setFullScreen(checkBoxFullScreen.selectedProperty().getValue()); }); title.bind(titleTextField.textProperty()); stage.setScene(scene); stage.titleProperty().bind(title); stage.initStyle(stageStyle); //关闭舞台并检测何时关闭 stage.setOnCloseRequest((WindowEvent we) -> { System.out.println("Stage is closing"); }); stage.show(); //使用用户界面布局容器 Rectangle2D primScreenBounds = Screen.getPrimary().getVisualBounds(); stage.setX((primScreenBounds.getWidth() - stage.getWidth()) / 2); stage.setY((primScreenBounds.getHeight() - stage.getHeight()) / 4); } }
确定舞台是否处于焦点位置
textStageF.textProperty().bind(new SimpleStringProperty("focused: ")
.concat(stageRef.focusedProperty().asString()));
控制舞台的z轴顺序
Button toBackButton = new Button("toBack()");
toBackButton.setOnAction(e -> stageRef.toBack());
Button toFrontButton = new Button("toFront()");
toFrontButton.setOnAction(e -> stageRef.toFront());
package sample;/** * @author: Administrator * @date: 2021/03/17 22:02 * @description: */import javafx.application.Application;import javafx.beans.property.DoubleProperty;import javafx.beans.property.SimpleDoubleProperty;import javafx.beans.property.SimpleStringProperty;import javafx.collections.FXCollections;import javafx.collections.ObservableList;import javafx.geometry.HPos;import javafx.geometry.Insets;import javafx.geometry.Orientation;import javafx.geometry.VPos;import javafx.scene.Cursor;import javafx.scene.Scene;import javafx.scene.control.ChoiceBox;import javafx.scene.control.Hyperlink;import javafx.scene.control.Label;import javafx.scene.control.RadioButton;import javafx.scene.control.Slider;import javafx.scene.control.ToggleGroup;import javafx.scene.layout.FlowPane;import javafx.scene.layout.HBox;import javafx.scene.paint.Color;import javafx.scene.text.Font;import javafx.scene.text.FontWeight;import javafx.scene.text.Text;import javafx.stage.Stage;public class OnTheSceneMain extends Application { DoubleProperty fillVals = new SimpleDoubleProperty(255.0); Scene sceneRef; ObservableList cursors = FXCollections.observableArrayList( Cursor.DEFAULT, Cursor.CROSSHAIR, Cursor.WAIT, Cursor.TEXT, Cursor.HAND, Cursor.MOVE, Cursor.N_RESIZE, Cursor.NE_RESIZE, Cursor.E_RESIZE, Cursor.SE_RESIZE, Cursor.S_RESIZE, Cursor.SW_RESIZE, Cursor.W_RESIZE, Cursor.NW_RESIZE, Cursor.NONE ); public static void main(String[] args) { Application.launch(args); } @Override public void start(Stage stage) { Slider sliderRef; ChoiceBox choiceBoxRef; Text textSceneX; Text textSceneY; Text textSceneW; Text textSceneH; Label labelStageX; Label labelStageY; Label labelStageW; Label labelStageH; final ToggleGroup toggleGrp = new ToggleGroup(); sliderRef = new Slider(0, 255, 255); sliderRef.setOrientation(Orientation.VERTICAL); choiceBoxRef = new ChoiceBox(cursors); HBox hbox = new HBox(sliderRef, choiceBoxRef); hbox.setSpacing(10); textSceneX = new Text(); textSceneX.getStyleClass().add("emphasized-text"); textSceneY = new Text(); textSceneY.getStyleClass().add("emphasized-text"); textSceneW = new Text(); textSceneW.getStyleClass().add("emphasized-text"); textSceneH = new Text(); textSceneH.getStyleClass().add("emphasized-text"); textSceneH.setId("sceneHeightText"); Hyperlink hyperlink = new Hyperlink("lookup"); hyperlink.setOnAction((javafx.event.ActionEvent e) -> { System.out.println("sceneRef:" + sceneRef); Text textRef = (Text) sceneRef.lookup("#sceneHeightText"); System.out.println(textRef.getText()); }); RadioButton radio1 = new RadioButton("onTheScene.css"); radio1.setSelected(true); radio1.setToggleGroup(toggleGrp); RadioButton radio2 = new RadioButton("changeOfScene.css"); radio2.setToggleGroup(toggleGrp); labelStageX = new Label(); labelStageX.setId("stageX"); labelStageY = new Label(); labelStageY.setId("stageY"); labelStageW = new Label(); labelStageH = new Label(); FlowPane sceneRoot = new FlowPane(Orientation.VERTICAL, 20, 10, hbox, textSceneX, textSceneY, textSceneW, textSceneH, hyperlink, radio1, radio2, labelStageX, labelStageY, labelStageW, labelStageH); sceneRoot.setPadding(new Insets(0, 20, 40, 0)); sceneRoot.setColumnHalignment(HPos.LEFT); sceneRoot.setLayoutX(20); sceneRoot.setLayoutY(40); sceneRef = new Scene(sceneRoot, 600, 250); sceneRef.getStylesheets().add("onTheScene.css"); stage.setScene(sceneRef); choiceBoxRef.getSelectionModel().selectFirst();// Setup various property binding textSceneX.textProperty().bind(new SimpleStringProperty("Scene x: ") .concat(sceneRef.xProperty().asString())); textSceneY.textProperty().bind(new SimpleStringProperty("Scene y: ") .concat(sceneRef.yProperty().asString())); textSceneW.textProperty().bind(new SimpleStringProperty("Scene width: ") .concat(sceneRef.widthProperty().asString())); textSceneH.textProperty().bind(new SimpleStringProperty("Scene height: ") .concat(sceneRef.heightProperty().asString())); labelStageX.textProperty().bind(new SimpleStringProperty("Stage x: ") .concat(sceneRef.getWindow().xProperty().asString())); labelStageY.textProperty().bind(new SimpleStringProperty("Stage y: ") .concat(sceneRef.getWindow().yProperty().asString())); labelStageW.textProperty().bind(new SimpleStringProperty("Stage width: ") .concat(sceneRef.getWindow().widthProperty().asString())); labelStageH.textProperty().bind(new SimpleStringProperty("Stage height: ") .concat(sceneRef.getWindow().heightProperty().asString())); sceneRef.cursorProperty().bind(choiceBoxRef.getSelectionModel() .selectedItemProperty()); fillVals.bind(sliderRef.valueProperty());// When fillVals changes, use that value as the RGB to fill the scene fillVals.addListener((ov, oldValue, newValue) -> { Double fillValue = fillVals.getValue() / 256.0; sceneRef.setFill(new Color(fillValue, fillValue, fillValue, 1.0)); });// When the selected radio button changes, set the appropriate style sheet toggleGrp.selectedToggleProperty().addListener((ov, oldValue, newValue) -> { String radioButtonText = ((RadioButton) toggleGrp.getSelectedToggle()) .getText(); sceneRef.getStylesheets().clear(); sceneRef.getStylesheets().addAll(radioButtonText); }); stage.setTitle("On the Scene"); stage.show();// Define an unmanaged node that will display Text Text addedTextRef = new Text(0, -30, ""); addedTextRef.setTextOrigin(VPos.TOP); addedTextRef.setFill(Color.BLUE); addedTextRef.setFont(Font.font("Sans Serif", FontWeight.BOLD, 16)); addedTextRef.setManaged(false);// Bind the text of the added Text node to the fill property of the Scene addedTextRef.textProperty().bind(new SimpleStringProperty("Scene fill: "). concat(sceneRef.fillProperty()));// Add to the Text node to the FlowPane ((FlowPane) sceneRef.getRoot()).getChildren().add(addedTextRef); } }
设置场景中的光标
sceneRef.cursorProperty().bind(choiceBoxRef.getSelectionModel() .selectedItemProperty());
通过ID找到场景中的节点
textSceneH = new Text(); textSceneH.getStyleClass().add("emphasized-text"); textSceneH.setId("sceneHeightText"); Hyperlink hyperlink = new Hyperlink("lookup"); hyperlink.setOnAction((javafx.event.ActionEvent e) -> { System.out.println("sceneRef:" + sceneRef); Text textRef = (Text) sceneRef.lookup("#sceneHeightText"); System.out.println(textRef.getText()); });
场景引用中根据ID获取文本对象,并获取对象的内容。
从场景访问舞台
labelStageX.textProperty().bind(new SimpleStringProperty("Stage x: ") .concat(sceneRef.getWindow().xProperty().asString())); labelStageY.textProperty().bind(new SimpleStringProperty("Stage y: ") .concat(sceneRef.getWindow().yProperty().asString()));
向场景内容序列中插入节点
// Define an unmanaged node that will display TextText addedTextRef = new Text(0, -30, ""); addedTextRef.setTextOrigin(VPos.TOP); addedTextRef.setFill(Color.BLUE); addedTextRef.setFont(Font.font("Sans Serif", FontWeight.BOLD, 16)); addedTextRef.setManaged(false);// Bind the text of the added Text node to the fill property of the SceneaddedTextRef.textProperty().bind(new SimpleStringProperty("Scene fill: "). concat(sceneRef.fillProperty()));// Add the Text node to the FlowPane((FlowPane) sceneRef.getRoot()).getChildren().add(addedTextRef);
场景中用CSS来修饰节点
sceneRef.getStylesheets().add("onTheScene.css"); ...code omitted...// When the selected radio button changes, set the appropriate stylesheettoggleGrp.selectedToggleProperty().addListener((ov, oldValue, newValue) -> { String radioButtonText = ((RadioButton) toggleGrp.getSelectedToggle()) .getText(); sceneRef.getStylesheets().clear(); sceneRef.getStylesheets().addAll("/"+radioButtonText); });
处理输入事件
查询鼠标、键盘、触摸和手势事件和处理程序
理解键盘事件
理解鼠标事件
理解触摸事件
理解手势事件
场景中的动画节点
package sample;/** * @author: Administrator * @date: 2021/03/17 22:14 * @description: */import javafx.animation.Animation;import javafx.animation.Interpolator;import javafx.animation.KeyFrame;import javafx.animation.KeyValue;import javafx.animation.Timeline;import javafx.application.Application;import javafx.beans.property.DoubleProperty;import javafx.beans.property.SimpleDoubleProperty;import javafx.scene.Group;import javafx.scene.Scene;import javafx.scene.control.Button;import javafx.scene.layout.HBox;import javafx.scene.paint.Color;import javafx.scene.shape.Line;import javafx.stage.Stage;import javafx.util.Duration;public class Metronome1Main extends Application { DoubleProperty startXVal = new SimpleDoubleProperty(100.0); Button startButton; Button pauseButton; Button resumeButton; Button stopButton; Line line; Timeline anim; public static void main(String[] args) { Application.launch(args); } @Override public void start(Stage stage) { anim = new Timeline( new KeyFrame(new Duration(0.0), new KeyValue(startXVal, 100.)), new KeyFrame(new Duration(1000.0), new KeyValue(startXVal, 300., Interpolator.LINEAR)) ); anim.setAutoReverse(true); anim.setCycleCount(Animation.INDEFINITE); line = new Line(0, 50, 200, 400); line.setStrokeWidth(4); line.setStroke(Color.BLUE); startButton = new Button("start"); startButton.setOnAction(e -> anim.playFromStart()); pauseButton = new Button("pause"); pauseButton.setOnAction(e -> anim.pause()); resumeButton = new Button("resume"); resumeButton.setOnAction(e -> anim.play()); stopButton = new Button("stop"); stopButton.setOnAction(e -> anim.stop()); HBox commands = new HBox(10, startButton, pauseButton, resumeButton, stopButton); commands.setLayoutX(60); commands.setLayoutY(420); Group group = new Group(line, commands); Scene scene = new Scene(group, 400, 500); line.startXProperty().bind(startXVal); startButton.disableProperty().bind(anim.statusProperty() .isNotEqualTo(Animation.Status.STOPPED)); pauseButton.disableProperty().bind(anim.statusProperty() .isNotEqualTo(Animation.Status.RUNNING)); resumeButton.disableProperty().bind(anim.statusProperty() .isNotEqualTo(Animation.Status.PAUSED)); stopButton.disableProperty().bind(anim.statusProperty() .isEqualTo(Animation.Status.STOPPED)); stage.setScene(scene); stage.setTitle("Metronome 1"); stage.show(); } }
为动画使用时间线
DoubleProperty startXVal = new SimpleDoubleProperty(100.0); ...code omitted... Timeline anim = new Timeline(new KeyFrame(new Duration(0.0), new KeyValue(startXVal, 100.)),new KeyFrame(new Duration(1000.0), new KeyValue(startXVal, 300., Interpolator.LINEAR)) ); anim.setAutoReverse(true); anim.setCycleCount(Animation.INDEFINITE); ...code omitted... line = new Line(0, 50, 200, 400); line.setStrokeWidth(4); line.setStroke(Color.BLUE); ...code omitted... line.startXProperty().bind(startXVal); line = LineBuilder.create() .startY(50) .endX(200) .endY(400) .strokeWidth(4) .stroke(Color.BLUE) .build();
为时间线插入KeyFrame
Timeline实例包含两个KeyValue实例,在1s时间内,startXVal从100线性变化到300。将这个属性绑定到line对象的startProperty属性上即可让直线运动起来。
Timeline anim = new Timeline(new KeyFrame(new Duration(0.0), new KeyValue(startXVal, 100.)),new KeyFrame(new Duration(1000.0), new KeyValue(startXVal, 300., Interpolator.LINEAR)) );
控制时间线
通过Timeline的playFromStart(),pause(),play(),stop()控制动画开始,暂停,播放,停止。
startButton = new Button("start"); startButton.setOnAction(e -> anim.playFromStart()); pauseButton = new Button("pause"); pauseButton.setOnAction(e -> anim.pause()); resumeButton = new Button("resume"); resumeButton.setOnAction(e -> anim.play()); stopButton = new Button("stop"); stopButton.setOnAction(e -> anim.stop());