《Java黑皮书基础篇第10版》 第15章【习题】

  • Post author:
  • Post category:java




Java语言程序设计 习题第十五章



15.2章节习题

15.1 什么是事件源对象? 什么是事件对象? 描述事件源对象和事件对象之间的关系

事件源对象类似于一个JavaFX的节点,是一个可供操作的用户界面控件,如按钮、文本框、菜单等。

事件对象是从事件源产生的一个对象,用来执行一个命令,如单击按钮,输入文本框等。它包含了有关事件的相关信息,例如事件类型、事件源对象、事件发生的时间等。事件对象是一个具体的事件类的实例,每种类型的事件都有相应的事件类,例如

ActionEvent



MouseEvent



KeyEvent

等。

15.2 一个按钮可以触发一个MouseEvent事件吗? 一个按钮可以触发一个KeyEvent事件吗? 一个按钮可以触发一个 ActionEvent 事件吗?

Button是Node的子类,因此以上事件都可以触发



15.3章节习题

15.3 为什么一个处理器必须是一个恰当的处理器接口的实例?

处理器应该实现handle方法来处理一个事件,实现了接口,就强制实现了handle抽象方法

15.4 请说明如何注册一个处理器对象,以及如何实现一个处理器接口?

// 对于button来说,可以这样注册一个处理器对象
button.setOnAction(handler);
// 实现接口并覆写handle方法即可,例如
class EnlargeHandler implements EventHandler<ActionEvent> {
        @Override 
        public void handle(ActionEvent e) {
            circlePane.enlarge();
        }
}

15.5 EventHandler接口的处理器方法是什么?

handle方法

15.6 对一个按钮注册一个ActionEvent处理器的注册方法是什么?

button.setOnAction(handler)



15.4章节习题

15.7 一个内部类可以被除它所在的嵌套类之外的类所使用吗?

可以

15.8 修饰符 public、protected、private 以及 static 可以用于内部类吗?

可以



15.5章节习题

15.9 如果类A是类B中的一个内部类,A的类文件名字是什么? 如果类B包含两个匿名内部类A和C,这两个类的.class文件名是什么?

B$A.class

B$A.class, B$C.class

15.10 下面代码中的错误是什么?

public class Test extends Application {
  public void start(Stage stage) {
    Button btOK = new Button("OK");
    // 没有注册事件
  }

  private class Handler implements EventHandler<ActionEvent> {
    // 应该是ActionEvent,不是Action
    public void handle(Action e) {
      System.out.println(e.getSource());
    }
  }
}
public class Test extends Application {
  public void start(Stage stage) {
    Button btOK = new Button("OK");
		// 少了()
    btOK.setOnAction(new EventHandler<ActionEvent> {
      public void handle(ActionEvent e) {
        System.out.println(e.getSource());
      }
    // 少了);
    }  // Something missing here
  }
}



15.6章节习题

15.11 什么是 lambda 表达式? 使用 lambda 表达式进行事件处理的好处是什么? 一个 lambda 表达式的语法是什么?

编译器对待一个 lambda 表达式如同它是从一个匿名内部类创建的对象,可以简化代码

(type1 para1,, typen paramn) -> expression; 
(type1 para1,, typen paramn) -> {statements}; 
() -> expression;
() -> {statements}; 

15.12 什么是功能接口? 为什么对于一个 lambda 表达式而言,必须是一个功能接口?

只有一个抽象方法的接口

15.13 请给出以下代码的输出

package com.example.demo;

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        test.setAction1(() -> System.out.print("Action 1! "));
        test.setAction2(e -> System.out.print(e + " "));
        System.out.println(test.setAction3(e -> e * 2));
    }

    public void setAction1(T1 t) {
        t.m();
    }

    public void setAction2(T2 t) {
        t.m(4.5);
    }

    public double setAction3(T3 t) {
        return t.m(5.5);
    }
}

interface T1 {
    public void m();
}

interface T2 {
    public void m(Double d);
}

interface T3 {
    public double m(Double d);
}
Action 1! 4.5 11.0



15.8章节习题

15.14 对于一个鼠标事件而言,使用什么方法来得到鼠标点的位置?

e.getX()
e.getY()

15.15 对于一个鼠标按下、释放、单击、进入、退出、移动和拖动事件,使用什么方法来注册一个相应的处理器?

setOnMouseClicked(handler)
setOnMousePressed(handler)
setOnMouseReleased(handler)
setOnMouseEntered(handler)
setOnMouseExited(handler)
setOnMouseDragged(handler)
setOnMouseMoved(handler)



15.9章节习题

15.16 使用什么方法来针对键的按下、释放以及敲击事件注册处理器?这些方法定义在哪些类中?

// 这些方法定义在Node和Scene中
setOnKeyPressed(handler)
setOnKeyReleased(handler)
setOnKeyTyped(handler)

15.17 使用什么方法来从一个键的敲击事件中获得该键的字符?针对按下键和释放键的事件,使用什么方法来得到键的编码?

e.getCharacter()
e.getText()

15.18 如何设置一个节点获取焦点,从而它可以监听键盘事件?

// 实际上就是接收键盘输入
node.requestFocus();



15.10章节习题

15.19 如果在第29行和第33行将pane替换为scene或者primaryStage, 会出现什么情况

在本例中,scene和pane的尺寸大小是一样的,scene改变了,pane也会跟着改变

但是,stage的变化不会引起scene和pane的变化



15.11章节习题

15.20 如何将一个动画的循环次数设置为无限次?如何自动倒转一个动画?如何开始、暂停以及停止一个动画?

animation.setCycleCount(Timeline.INFINITY);
animation.setAutoReverse(true);
animation.start();
animation.pause();
animation.stop();

15.21 PathTransition, FadeTransition和Timeline是Animation的一个子类型吗?

是的

15.22 如何创建一个 PathTransition? 如何创建一个 FadeTransition? 如何创建一个 Timeline?

new操作符

15.23 如何创建一个关键帧?

new KeyFrame(Duration, handler)



15.12章节习题

15.24 程序是如何使球移动的?

每隔50毫秒,小球的圆心就会更换一次坐标

15.25 程序清单15-17中的代码是如何改变球的移动方向的?

通过改变dx的符号

15.26 当在弹球面板上按下鼠标时,程序将做什么?当在弹球面板上释放鼠标时,程序将做什么?

暂停和重启

15.27 在程序清单15-18中,如果第32行不存在,当你按下UP或者DOWN方向键的时候,会出现什么情况?

程序不会有反应

15.28 如果程序清单15-17中没有第23行,会出现什么情况?

程序只会执行一次,也就是50毫秒



编程练习题



*15.1 (选取4张卡牌)

请写一个程序,可以让用户通过单击Refresh按钮以显示从一副52张卡牌选取的4张卡牌,如图15-24a所示。(参见编程练习題14.3中关于如何获得4张随机卡牌中的提示)

package com.example.demo;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import java.util.ArrayList;

public class Test extends Application {
    private static final String PREFIX = "File:/Users/kevinwang/Downloads/Java语言程序设计/习题/JavaFX_Document/";
    private static final String SUFFIX = ".png";

    @Override
    public void start(Stage pStage) {
        // 52张牌太多了,找不到资源,因此功能简化一下,在5张牌里面随机选择3张
        // 用hBox储存随机的3张纸牌,用stackPane储存按钮,再把这两个面板一起放到BorderPane中
        // viewList储存全部5张纸牌,randomIndex储存3个随机整数,去决定哪3张图片被展示
        HBox hBox = new HBox();
        StackPane stackPane = new StackPane();
        BorderPane borderPane = new BorderPane();
        ArrayList<ImageView> viewList = new ArrayList<>();
        ArrayList<Integer> randomIndex = new ArrayList<>();

        // 创建纸牌
        for (int i = 0; i < 5; i++) {
            viewList.add(new ImageView(PREFIX + i + SUFFIX));
            viewList.get(i).fitWidthProperty().bind(hBox.widthProperty().multiply(0.33));
            viewList.get(i).setPreserveRatio(true);
        }

        // 创建按钮
        Button button = new Button("Refresh");

        // 添加图片到hBox
        addHBox(randomIndex, viewList, hBox);

        // 添加按钮到stackPane
        stackPane.getChildren().add(button);

        // 如果点击了按钮,就会清空hBox和randomIndex,重新执行一遍"添加图片到hBox"的过程
        button.setOnAction(e -> {
            hBox.getChildren().clear();
            randomIndex.clear();
            addHBox(randomIndex, viewList, hBox);
        });

        // 把按钮和纸牌图片添加到borderPane中
        borderPane.setCenter(hBox);
        borderPane.setBottom(stackPane);

        Scene scene = new Scene(borderPane, 200, 150);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }

    public void addHBox(ArrayList<Integer> randomIndex, ArrayList<ImageView> viewList, HBox hBox) {
        for (int i = 0; i < 3; i++) {
            int temp;
            // 下面的循环控制了randomIndex数组中不可以出现相同的元素。如果没有循环,直接创建随机数,会有如下效果
            // 随机数是3,4,5,那么程序正常允许;但是如果随机数是3,4,4,程序会报错,因为一个ImageView只能被写入一次面板,也就是一个子节点ImageView只能有一个父节点HBox
            do
                temp = (int) (Math.random() * 5);
            while (randomIndex.contains(temp));
            randomIndex.add(temp);
            hBox.getChildren().add(viewList.get(temp));
            hBox.setAlignment(Pos.CENTER);
        }
    }
}

输出结果:

在这里插入图片描述



15.2 (旋转一个四边形)

请写一个程序,在Rotate按钮被单击时,将一个四边形向右旋转15度,如图15-24b所示

package com.example.demo;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class Test extends Application {
    // 初始化旋转角度
    double angle = 0;

    @Override
    public void start(Stage pStage) {
        StackPane stackPaneRectangle = new StackPane();
        StackPane stackPaneButton = new StackPane();
        BorderPane borderPane = new BorderPane();

        // 创建长方形并添加到面板
        Rectangle rectangle = new Rectangle(40, 10);
        rectangle.setFill(Color.TRANSPARENT);
        rectangle.setStroke(Color.BLACK);
        stackPaneRectangle.getChildren().add(rectangle);
        // 创建按钮并添加到面板
        Button button = new Button("Rotate");
        stackPaneButton.getChildren().add(button);

        // 如果点击了按钮,就会旋转
        button.setOnAction(e -> {
            rotate(rectangle);
        });

        // 把Nodes添加到borderPane中
        borderPane.setCenter(stackPaneRectangle);
        borderPane.setBottom(stackPaneButton);

        Scene scene = new Scene(borderPane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }

    public void rotate(Rectangle  rectangle) {
        // 小球旋转的角度应该是累加的
        angle += 15;
        rectangle.setRotate(angle);
    }
}

输出结果:

在这里插入图片描述



*15.3 (移动小球)

编写一个程序,在面板上移动小球。应该定义一个面板类来显示小球,并提供向左、 向右 、向上和向下移动小球的方法,如图15-24c所示。请进行边界检査以防止球完全移到视线之外

package com.example.demo;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        // pane用来储存圆圈,hBox用来储存按钮,text警告会在即将越界时发生,以上三个Node都会被放置在borderPane中
        Pane pane = new Pane();
        HBox hBox = new HBox();
        Text text = new Text("Next movement will be out of bound!");
        text.setFill(Color.RED);
        BorderPane borderPane = new BorderPane();

        // 创建圆形并添加到面板
        Circle circle = new Circle(100, 50, 15);
        circle.setStroke(Color.BLACK);
        circle.setFill(Color.TRANSPARENT);
        pane.getChildren().add(circle);
        // 创建按钮并添加到面板
        Button left = new Button("Left");
        Button right = new Button("Right");
        Button up = new Button("Up");
        Button down = new Button("Down");
        hBox.getChildren().addAll(left, right, up, down);
        hBox.setAlignment(Pos.CENTER);

        // 如果点击了按钮,就会移动,如果移动即将越界,会在顶部弹出警告
        left.setOnAction(e -> {
            // 清除可能存在的警告消息
            borderPane.setTop(null);
            if (circle.getCenterX() - circle.getRadius() * 2 < 0) {
                // 弹出警告并退出方法
                borderPane.setTop(text);
                return;
            }
            circle.setCenterX(circle.getCenterX() - circle.getRadius());
        });

        right.setOnAction(e -> {
            borderPane.setTop(null);
            if (circle.getCenterX() + circle.getRadius() * 2 > 200) {
                borderPane.setTop(text);
                return;
            }
            circle.setCenterX(circle.getCenterX() + circle.getRadius());
        });

        up.setOnAction(e -> {
            borderPane.setTop(null);
            if (circle.getCenterY() - circle.getRadius() * 2 < 0) {
                borderPane.setTop(text);
                return;
            }
            circle.setCenterY(circle.getCenterY() - circle.getRadius());
        });

        down.setOnAction(e -> {
            borderPane.setTop(null);
            if (circle.getCenterY() + circle.getRadius() * 2 > 150) {
                borderPane.setTop(text);
                return;
            }
            circle.setCenterY(circle.getCenterY() + circle.getRadius());
        });

        // 把Nodes添加到borderPane中
        borderPane.setCenter(pane);
        borderPane.setBottom(hBox);

        Scene scene = new Scene(borderPane, 200, 150);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



*15.4 (创建一个简单的计算)

编写一个程序完成加法、减法、乘法和除法操作,参见图15-25a

package com.example.demo;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        // hBox1用来储存输入行,hBox2用来储存按钮,text警告会在除数为0时弹出,以上三个Node都会被放置在borderPane中
        HBox hBox1 = new HBox();
        HBox hBox2 = new HBox();
        Text text = new Text("Divisor cannot be 0!");
        text.setFill(Color.RED);
        BorderPane borderPane = new BorderPane();

        // 创建输入行并添加到面板
        Text num1 = new Text("Number 1");
        TextField num1Field = new TextField();
        Text num2 = new Text("Number 2");
        TextField num2Field = new TextField();
        Text result = new Text("Result");
        TextField resultField = new TextField();
        // 禁止输入
        resultField.setEditable(false);
        hBox1.getChildren().addAll(num1, num1Field, num2, num2Field, result, resultField);
        hBox1.setSpacing(10);
        hBox1.setAlignment(Pos.CENTER);

        // 添加按钮
        Button add = new Button("Add");
        Button sub = new Button("Subtract");
        Button multi = new Button("Multiply");
        Button div = new Button("Divide");
        hBox2.getChildren().addAll(add, sub, multi, div);
        hBox2.setSpacing(20);
        hBox2.setAlignment(Pos.CENTER);

        // 点击按钮计算
        add.setOnAction(e -> {
            // 移除可能存在的警告
            borderPane.getChildren().remove(text);
            double num1Value = Double.parseDouble(num1Field.getText());
            double num2Value = Double.parseDouble(num2Field.getText());
            double resultValue = num1Value + num2Value;
            resultField.setText("" + resultValue);
        });

        sub.setOnAction(e -> {
            // 移除可能存在的警告
            borderPane.getChildren().remove(text);
            double num1Value = Double.parseDouble(num1Field.getText());
            double num2Value = Double.parseDouble(num2Field.getText());
            double resultValue = num1Value - num2Value;
            resultField.setText("" + resultValue);
        });

        multi.setOnAction(e -> {
            // 移除可能存在的警告
            borderPane.getChildren().remove(text);
            double num1Value = Double.parseDouble(num1Field.getText());
            double num2Value = Double.parseDouble(num2Field.getText());
            double resultValue = num1Value * num2Value;
            resultField.setText("" + resultValue);
        });

        div.setOnAction(e -> {
            // 移除可能存在的警告
            borderPane.getChildren().remove(text);
            double num1Value = Double.parseDouble(num1Field.getText());
            double num2Value = Double.parseDouble(num2Field.getText());
            if (num2Value == 0) {
                // 如果除数为0,弹出警告,并且清除上一轮计算的结果信息,注意,弹出警告之后,进行下次其他计算前需要移除
                borderPane.setTop(text);
                resultField.clear();
                // 文字居中
                BorderPane.setAlignment(text, Pos.CENTER);
                return;
            }
            double resultValue = num1Value / num2Value;
            resultField.setText("" + resultValue);
        });

        // 把Nodes添加到borderPane中
        borderPane.setCenter(hBox1);
        borderPane.setBottom(hBox2);

        Scene scene = new Scene(borderPane, 300, 150);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



*15.5(创建一个投资值计算器)

编写一个程序,计算投资值在给定利率以及给定年数下的未来值。计算的公式如下所示:




未来值

=

投资值

×

(

1

+

月利率

)

年数

×

12

未来值 = 投资值 × (1 + 月利率)^{年数 × 12}






未来值




=








投资值




×








(


1




+








月利率



)











年数


×


12












使用文本域显示利率、投资值和年数。当用户单击Calculate按钮时在文本域显示未来值,如图15-25b所示

package com.example.demo;

import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

import java.text.DecimalFormat;

public class Test extends Application {
    // 创建输入文本框
    TextField amountField = new TextField();
    TextField yearsField = new TextField();
    TextField rateField = new TextField();
    TextField valueField = new TextField();

    @Override
    public void start(Stage pStage) {
        // gridPane用来储存按钮
        GridPane gridPane = new GridPane();
        gridPane.setVgap(5);
        gridPane.setHgap(5);

        // 添加输入行到面板
        gridPane.add(new Label("Investment Amount:"), 0, 0);
        gridPane.add(amountField, 1, 0);
        gridPane.add(new Label("Number of Years:"), 0, 1);
        gridPane.add(yearsField, 1, 1);
        gridPane.add(new Label("Annual Interest Rate:"), 0, 2);
        gridPane.add(rateField, 1, 2);
        gridPane.add(new Label("Future Value:"), 0, 3);
        gridPane.add(valueField, 1, 3);
        Button button = new Button("Calculate");
        gridPane.add(button, 1, 4);

        // 禁止输入
        valueField.setEditable(false);
        // 位置调整
        gridPane.setAlignment(Pos.CENTER);
        GridPane.setHalignment(button, HPos.RIGHT);

        // 点击按钮计算
        button.setOnAction(e -> {
            calculate();
        });

        Scene scene = new Scene(gridPane, 300, 150);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }

    public void calculate() {
        double amount = Double.parseDouble(amountField.getText());
        double years = Double.parseDouble(yearsField.getText());
        double rate = Double.parseDouble(rateField.getText());
        double futureValue = amount * Math.pow((1 + rate / 12), (years * 12));
        // 将数字格式化为字符串,禁用科学计数法
        DecimalFormat decimalFormat = new DecimalFormat();
        String formattedNumber = decimalFormat.format(futureValue);
        valueField.setText("$" + formattedNumber);
    }
}

输出结果:

在这里插入图片描述



**15.6(两个消息交替出现)

编写一个程序,当单击鼠标时面板上交替显示两个文本”Java is fun”和”Java is powerful”

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        StackPane pane = new StackPane();
        Text text = new Text("Java is fun");
        pane.getChildren().add(text);

        // setOnMousePressed方法在按下鼠标键时会完成指定动作,setOnMouseClicked方法必须按下并释放鼠标才会完成指定动作
        text.setOnMousePressed(e -> {
            if (text.getText().length() == 11)
                text.setText("Java is powerful");
            else
                text.setText("Java is fun");
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



15.7(使用鼠标改变颜色)

编写一个程序,显示一个圆的颜色,当按下鼠标键时颜色为黑色,释放鼠标时颜色为白色

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        StackPane pane = new StackPane();
        Circle circle = new Circle(20);
        pane.getChildren().add(circle);
        circle.setOnMousePressed(e -> {
            if (circle.getFill() == Color.BLACK) {
                circle.setFill(Color.TRANSPARENT);
                circle.setStroke(Color.BLACK);
            }
            else
                circle.setFill(Color.BLACK);
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



*15.8(显示鼠标的位罝)

编写两个程序,一个当单击鼠标时显示鼠标的位置(参见图15-26a),而另一个当按下鼠标时显示鼠标的位置,当释放鼠标时停止显示

第一个程序

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        Text text = new Text();
        Pane pane = new Pane();
        // e是一个MouseEvent对象,它代表鼠标事件的信息,包括鼠标的位置、点击类型、按键状态等
        pane.setOnMousePressed(e -> {
            pane.getChildren().remove(text);
            double x = e.getX();
            double y = e.getY();
            String info = x + ", " + y;
            text.setX(x);
            text.setY(y);
            text.setText(info);
            pane.getChildren().add(text);
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

第二个程序

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        Text text = new Text();
        Pane pane = new Pane();

        // e是一个MouseEvent对象,它代表鼠标事件的信息,包括鼠标的位置、点击类型、按键状态等
        pane.setOnMousePressed(e -> {
            if (e.getButton() == MouseButton.PRIMARY) {
                double x = e.getX();
                double y = e.getY();
                String info = x + ", " + y;
                text.setX(x);
                text.setY(y);
                text.setText(info);
                pane.getChildren().add(text);
            }
        });

        // 松开时消失
        pane.setOnMouseReleased(e -> {
            if (e.getButton() == MouseButton.PRIMARY) {
                pane.getChildren().remove(text);
            }
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



*15.9 (使用箭头键画线)

请编写一个程序,使用箭头键绘制线段。所画的线从面板的中心开始,当敲 击向右、向上、向左或向下的箭头键时,相应地向东、向北、向西或向南方向画线,如图 15-26b所示

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;

public class Test extends Application {
    // locationX是初始的X坐标,locationY是初始的Y坐标,upMove等4个int是移动的距离,把这些数据放到开头是因为有访问权限的问题
    double locationX = 100;
    double locationY = 100;
    Pane pane = new Pane();
    int upMove = 5;
    int downMove = 5;
    int leftMove = 5;
    int rightMove = 5;

    @Override
    public void start(Stage primaryStage) {
        Scene scene = new Scene(pane, 200, 200);

        // 调用move方法,其中code代表了上下左右按键
        scene.setOnKeyPressed(e -> {
            KeyCode code = e.getCode();
            move(code);
        });

        primaryStage.setScene(scene);
        primaryStage.setTitle("Draw Lines");
        primaryStage.show();
    }

    public void move(KeyCode code) {
        switch (code) {
            // 当用户点击"向上箭头",绘制一条直线,startX和startY坐标是Scene的中心(或者是上一次按键所形成直线的endX和endY坐标),同时按照逻辑更新endX和endY坐标,作为下一次按键的初始点
            case UP:
                Line upLine = new Line(locationX, locationY, locationX, locationY - upMove);
                pane.getChildren().add(upLine);
                //upMove += 5;
                locationX = upLine.getEndX();
                locationY = upLine.getEndY();
                break;
            case DOWN:
                // 当用户点击"向下箭头",绘制一条直线,startX和startY坐标是Scene的中心(或者是上一次按键所形成直线的endX和endY坐标),同时按照逻辑更新endX和endY坐标,作为下一次按键的初始点
                Line downLine = new Line(locationX, locationY, locationX, locationY + downMove);
                pane.getChildren().add(downLine);
                //downMove += 5;
                locationX = downLine.getEndX();
                locationY = downLine.getEndY();
                break;
            case LEFT:
                // 当用户点击"向左箭头",绘制一条直线,startX和startY坐标是Scene的中心(或者是上一次按键所形成直线的endX和endY坐标),同时按照逻辑更新endX和endY坐标,作为下一次按键的初始点
                Line leftLine = new Line(locationX, locationY, locationX - leftMove, locationY);
                pane.getChildren().add(leftLine);
                //leftMove += 5;
                locationX = leftLine.getEndX();
                locationY = leftLine.getEndY();
                break;
            case RIGHT:
                // 当用户点击"向右箭头",绘制一条直线,startX和startY坐标是Scene的中心(或者是上一次按键所形成直线的endX和endY坐标),同时按照逻辑更新endX和endY坐标,作为下一次按键的初始点
                Line rightLine = new Line(locationX, locationY, locationX + rightMove, locationY);
                pane.getChildren().add(rightLine);
                //rightMove += 5;
                locationX = rightLine.getEndX();
                locationY = rightLine.getEndY();
                break;
        }
    }
}

输出结果:

在这里插入图片描述



**15.10 (输入并显示字符串)

请编写一个程序,从键盘接收一个字符串并把它显示在面板上。回车键表明字符串结束。任何时候输入一个新字符串时都会将它显示在面板上

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

import java.util.ArrayList;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        StackPane pane = new StackPane();
        ArrayList<String> words = new ArrayList<>();

        Text text = new Text(50, 50, "");
        pane.getChildren().add(text);

        pane.setOnKeyPressed(e -> {
            KeyCode keyCode = e.getCode();
            if (keyCode.isLetterKey() || keyCode.isDigitKey()) {
                String keyText = keyCode.toString();
                words.add(keyText);
                text.setText(String.join("", words));
            } else if (keyCode == KeyCode.ENTER) {
                words.clear();
            }
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
        pane.requestFocus();
    }
}

输出结果:

在这里插入图片描述



*15.11(使用键移动圓)

请编写程序,可以使用箭头键向上、向下、向左、向右移动一个圆

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        Pane pane = new Pane();
        Circle circle = new Circle(50, 50, 20);
        circle.setStroke(Color.BLACK);
        circle.setFill(Color.TRANSPARENT);
        pane.getChildren().add(circle);

        pane.setOnKeyPressed(e -> {
            switch (e.getCode()) {
                case UP:
                    circle.setCenterY(circle.getCenterY() - 5);
                    break;
                case DOWN:
                    circle.setCenterY(circle.getCenterY() + 5);
                    break;
                case LEFT:
                    circle.setCenterX(circle.getCenterX() - 5);
                    break;
                case RIGHT:
                    circle.setCenterX(circle.getCenterX() + 5);
            }
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
        pane.requestFocus();
    }
}

输出结果:

在这里插入图片描述



**15.12(几何问题:是否在圆内)

请编写一个程序,绘制一个圆心在(100, 60)而半径为50的固定的圆。当鼠标移动时,显示一条消息表示鼠标点是在圆内还是在圆外,如图15-27a所示

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        Pane pane = new Pane();
        Text text = new Text(50, 50, "");
        Circle circle = new Circle(50, 50, 20);
        circle.setStroke(Color.BLACK);
        circle.setFill(Color.TRANSPARENT);
        pane.getChildren().add(circle);

        pane.setOnMouseMoved(e -> {
            pane.getChildren().remove(text);
            double x = e.getX();
            double y = e.getY();
            if (Math.pow(Math.pow((50 - x), 2) + Math.pow((50 - y), 2), 0.5) < 20) {
                text.setText("Inside");
            }
            if (Math.pow(Math.pow((50 - x), 2) + Math.pow((50 - y), 2), 0.5) > 20) {
                text.setText("Outside");
            }
            pane.getChildren().add(text);
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



**15.13 (几何问題:是否在矩形内?)

请编写一个程序,绘制一个中心在(100, 60)宽为100而高为40的固定的矩形。当鼠标移动时,显示一条消息表示鼠标指针是否在矩形内,如图15-27b所示。为了检査一个点是否在矩形内,使用定义在 Node 类中的contains方法

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        Pane pane = new Pane();
        Text text = new Text(50, 50, "");
        Rectangle rectangle = new Rectangle(50, 50, 30, 10);
        rectangle.setFill(Color.TRANSPARENT);
        rectangle.setStroke(Color.BLACK);
        pane.getChildren().add(rectangle);

        pane.setOnMouseMoved(e -> {
            pane.getChildren().remove(text);
            double x = e.getX();
            double y = e.getY();
            if (rectangle.contains(x, y)) {
                text.setText("Inside");
            }
            if (!rectangle.contains(x, y)) {
                text.setText("Outside");
            }
            pane.getChildren().add(text);
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



**15.14(几何问題:是否在多边形内?)

请编写一个程序,绘制端点分别是(40, 20)、(70, 40)、(60, 80)、(45, 45) 和(20, 60)的固定多边形。当鼠标移动时,显示一条消息表示鼠标点是否在多边形内,如图15-27c所示。为了检査一个点是否在多边形内,可以使用定义在Node类中的contains方法

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        Pane pane = new Pane();
        Text text = new Text(50, 50, "");
        Polygon polygon = new Polygon(40, 20, 70, 40, 60, 80, 45, 45, 20, 60);
        polygon.setStroke(Color.BLACK);
        polygon.setFill(Color.TRANSPARENT);
        pane.getChildren().add(polygon);

        pane.setOnMouseMoved(e -> {
            pane.getChildren().remove(text);
            double x = e.getX();
            double y = e.getY();
            if (polygon.contains(x, y)) {
                text.setText("Inside");
            }
            if (!polygon.contains(x, y)) {
                text.setText("Outside");
            }
            pane.getChildren().add(text);
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



**15.15 (几何问題:添加或删除点)

请编写一个程序,让用户在面板上单击以自动创建或移去点(参见15-28a)。当用户左击鼠标时(主按钮),就创建一个点并且显示在鼠标的位置,用户还可以将鼠标移到一个点上,然后右击鼠标(次按钮)以移去这个点

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        Pane pane = new Pane();

        pane.setOnMouseClicked(e -> {
            // 如果在pane面板内单击了左键,就创建一个圆
            if (e.getButton() == MouseButton.PRIMARY) {
                double x = e.getX();
                double y = e.getY();
                Circle circle = new Circle(x, y, 5);
                circle.setFill(Color.TRANSPARENT);
                circle.setStroke(Color.BLACK);
                pane.getChildren().add(circle);
            }

            // 如果在pane面板内单击了左键,就删除一个圆
            if (e.getButton() == MouseButton.SECONDARY) {
                double x = e.getX();
                double y = e.getY();
                // 利用循环遍历pane面板的每一个节点,去比较这个节点是否包含鼠标右击的坐标,如果包含,就删除这个对应的节点
                for (int i = 0; i < pane.getChildren().size(); i++) {
                    if (pane.getChildren().get(i).contains(x, y)) {
                        pane.getChildren().remove(pane.getChildren().get(i));
                    }
                }
            }
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



*15.16(两个可移动的顶点以及它们间的距离)

请编写一个程序,显示两个分别位于(40,40)和(120,150) 的半径为10的圆,并用一条直线连接两个圆,如图15-28b所示。圆之间的距离显示在直线上。 用户可以拖动圆,圆和它上面的直线会相应移动,并且两个圆之间的距离会更新

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    // circle1的圆心x坐标
    double x1 = 20;
    // circle1的圆心y坐标
    double y1 = 20;
    // circle2的圆心x坐标
    double x2 = 80;
    // circle2的圆心y坐标
    double y2 = 80;
    // text的x坐标
    double textX = (x1 + x2) / 2;
    // text的y坐标
    double textY = (y1 + y2) / 2;
    // 计算两个圆心的距离
    double length = Math.pow(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2), 0.5);
    // 格式化距离
    String formattedLength = String.format("%.2f", length);

    @Override
    public void start(Stage pStage) {
        // 创建节点并添加到面板,注意line连接了两个圆心,随后被圆圈覆盖,所以输出的时候圆内没有线段
        Pane pane = new Pane();
        Circle circle1 = new Circle(x1, y1, 5);
        Circle circle2 = new Circle(x2, y2, 5);
        Line line = new Line(x1, y1, x2, y2);
        Text text = new Text(textX, textY, formattedLength);
        circle1.setFill(Color.WHITE);
        circle1.setStroke(Color.BLACK);
        circle2.setFill(Color.WHITE);
        circle2.setStroke(Color.BLACK);
        pane.getChildren().add(line);
        pane.getChildren().addAll(circle1, circle2, text);

        // 移动circle1,重新设置各个参数
        circle1.setOnMouseDragged(e -> {
            x1 = e.getX();
            y1 = e.getY();
            textX = (x1 + x2) / 2;
            textY = (y1 + y2) / 2;
            length = Math.pow(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2), 0.5);
            formattedLength = String.format("%.2f", length);
            circle1.setCenterX(x1);
            circle1.setCenterY(y1);
            line.setStartX(x1);
            line.setStartY(y1);
            text.setX(textX);
            text.setY(textY);
            text.setText(formattedLength);
        });

        // 移动circle2,重新设置各个参数
        circle2.setOnMouseDragged(e -> {
            x2 = e.getX();
            y2 = e.getY();
            circle2.setCenterX(x2);
            circle2.setCenterY(y2);
            line.setEndX(x2);
            line.setEndY(y2);
            textX = (x1 + x2) / 2;
            textY = (y1 + y2) / 2;
            length = Math.pow(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2), 0.5);
            formattedLength = String.format("%.2f", length);
            text.setX(textX);
            text.setY(textY);
            text.setText(formattedLength);
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



**15.17 (几何问題:寻找边界矩形)

请编写一个程序,让用户可以在一个二维面板上动态地增加和移除点,如图15-29a所示。当点加入和移除的时候,一个最小的边界矩形更新显示。假设每个点的半径是 10 像素

package com.example.demo;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        // pane用来画圆圈,textPane用来提供说明,borderPane整合pane和textPane
        Pane pane = new Pane();
        BorderPane borderPane = new BorderPane();

        Pane textPane = new Pane();
        Rectangle rectangle = new Rectangle(40, 70);
        rectangle.setStroke(Color.BLACK);
        rectangle.setFill(Color.TRANSPARENT);
        Text text = new Text(5, 40, "hello");
        textPane.getChildren().addAll(rectangle, text);
        BorderPane.setMargin(textPane, new Insets(15,5,15,5));

        pane.setOnMouseClicked(e -> {
            // 如果在pane面板内单击了左键,就创建一个圆
            if (e.getButton() == MouseButton.PRIMARY) {
                double x = e.getX();
                double y = e.getY();
                Circle circle = new Circle(x, y, 5);
                circle.setFill(Color.TRANSPARENT);
                circle.setStroke(Color.BLACK);
                pane.getChildren().add(circle);
            }

            // 如果在pane面板内单击了左键,就删除一个圆
            if (e.getButton() == MouseButton.SECONDARY) {
                double x = e.getX();
                double y = e.getY();
                // 利用循环遍历pane面板的每一个节点,去比较这个节点是否包含鼠标右击的坐标,如果包含,就删除这个对应的节点
                for (int i = 0; i < pane.getChildren().size(); i++) {
                    if (pane.getChildren().get(i).contains(x, y)) {
                        pane.getChildren().remove(pane.getChildren().get(i));
                        // 点击右键会删除外圈的矩形,这里重新画出包围圆圈的矩形
                        Rectangle rectangleForCircle = new Rectangle(40, 70);
                        rectangleForCircle.setFill(Color.TRANSPARENT);
                        rectangleForCircle.setStroke(Color.BLACK);
                        pane.getChildren().add(rectangleForCircle);
                        BorderPane.setMargin(pane, new Insets(15, 5, 15, 5));
                    }
                }
            }
        });

        // 画出包围圆圈的矩形
        Rectangle rectangleForCircle = new Rectangle(40, 70);
        rectangleForCircle.setFill(Color.TRANSPARENT);
        rectangleForCircle.setStroke(Color.BLACK);
        pane.getChildren().add(rectangleForCircle);
        BorderPane.setMargin(pane, new Insets(15, 5, 15, 5));

        // 整合两个面板
        borderPane.setLeft(textPane);
        borderPane.setCenter(pane);

        Scene scene = new Scene(borderPane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



**15.18(使用鼠标来移动一个矩形)

请编写一个程序显示一个矩形。可以使用鼠标单击矩形内部并且拖动(即按住鼠标移动)矩形到鼠标的位置。鼠标点成为矩形的中央

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        Pane pane = new Pane();
        Scene scene = new Scene(pane, 100, 100);
        double width = 50;
        double height = 50;
        Rectangle rectangle = new Rectangle(25, 25, width, height);
        pane.getChildren().add(rectangle);

        pane.setOnMouseDragged(e -> {
            double x = e.getX();
            double y = e.getY();
            rectangle.setX(x - width / 2);
            rectangle.setY(y - height / 2);
        });
        
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



**15.19 (游戏:手眼协调)

请编写一个程序,显示一个半径为10像素的实心圆,该圆放置在面板上的随机位置,并填充随机的顔色,如图15-29b所示。单击这个圆时,它会消失,然后在另一个随机的位置显示新的随机颜色的圆。在单击了20个圆之后,在面板上显示所用的时间,如图15-29c所示

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    int count = 0;
    @Override
    public void start(Stage pStage) {
        Pane pane = new Pane();
        // 设置圆心范围,不会超出pane边界
        Circle circle = new Circle(Math.random() * 190 + 5, Math.random() * 90 + 5, 5);
        circle.setFill(Color.color(Math.random(), Math.random(), Math.random()));
        pane.getChildren().add(circle);
        long startTime = System.currentTimeMillis();

        circle.setOnMouseClicked(e -> {
            // 一个子节点circle只能有一个父节点pane,所以这里先清除父节点pane的所有子节点,之后就不会报错了
            pane.getChildren().clear();
            circle.setCenterX(Math.random() * 190 + 5);
            circle.setCenterY(Math.random() * 90 + 5);
            circle.setFill(Color.color(Math.random(), Math.random(), Math.random()));
            pane.getChildren().add(circle);
            count++;
            if (count == 5) {
                pane.getChildren().clear();
                long endTime = System.currentTimeMillis();
                Text text = new Text(50, 50, "Time spend is " + (endTime - startTime) + " milliseconds");
                // 文本居中
                text.setX(pane.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2);
                text.setY(pane.getHeight() / 2 + text.getLayoutBounds().getHeight() / 2);
                pane.getChildren().add(text);
            }
        });

        Scene scene = new Scene(pane, 200, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



**15.20 (几何问題:显示角度)

请编写一个程序,使用户可以拖动一个三角形的顶点,并在三角形改变时动态显示角度,如图15-30a所示。计算角度的公式在程序清单4-1中给出

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {
    // 创建3个点的坐标,为了避免突破边界,生成范围是5-95
    double x1 = Math.random() * 90 + 5, x2 = Math.random() * 90 + 5, x3 = Math.random() * 90 + 5;
    double y1 = Math.random() * 90 + 5, y2 = Math.random() * 90 + 5, y3 = Math.random() * 90 + 5;

    @Override
    public void start(Stage pStage) {
        Pane pane = new Pane();
        // 生成角度,并用文本的形式体现在面板上
        Text text1 = new Text(x1 + 5, y1 - 5, "c1: " + angleA(x1, y1, x2, y2, x3, y3));
        Text text2 = new Text(x2 + 5, y2 - 5, "c2: " + angleB(x1, y1, x2, y2, x3, y3));
        Text text3 = new Text(x3 + 5, y3 - 5, "c3: " + angleC(x1, y1, x2, y2, x3, y3));
        // 生成3个圆形并设置相关参数
        Circle circle1 = new Circle(x1, y1, 5, Color.TRANSPARENT);
        Circle circle2 = new Circle(x2, y2, 5, Color.TRANSPARENT);
        Circle circle3 = new Circle(x3, y3, 5, Color.TRANSPARENT);
        circle1.setStroke(Color.BLACK);
        circle2.setStroke(Color.BLACK);
        circle3.setStroke(Color.BLACK);
        // 生成3条边
        Line line1 = new Line(x1, y1, x2, y2);
        Line line2 = new Line(x2, y2, x3, y3);
        Line line3 = new Line(x3, y3, x1, y1);
        // 将圆形的圆心坐标绑定到3条边上,圆心移动,三条边随之移动
        line1.startXProperty().bind(circle1.centerXProperty());
        line1.startYProperty().bind(circle1.centerYProperty());
        line2.startXProperty().bind(circle2.centerXProperty());
        line2.startYProperty().bind(circle2.centerYProperty());
        line3.startXProperty().bind(circle3.centerXProperty());
        line3.startYProperty().bind(circle3.centerYProperty());
        line1.endXProperty().bind(circle2.centerXProperty());
        line1.endYProperty().bind(circle2.centerYProperty());
        line2.endXProperty().bind(circle3.centerXProperty());
        line2.endYProperty().bind(circle3.centerYProperty());
        line3.endXProperty().bind(circle1.centerXProperty());
        line3.endYProperty().bind(circle1.centerYProperty());
        pane.getChildren().addAll(circle1, circle2, circle3, line1, line2, line3, text1, text2, text3);

        // 拖动圆1,就是更改了圆1的圆心坐标和3个角的角度,因此重新设置圆1的圆心坐标和3个角度
        circle1.setOnMouseDragged(e -> {
            // 清空上一轮拖动显示的角度(如有)
            pane.getChildren().removeAll(text1, text2, text3);
            // 这里需要用x1,不能新建double x1,因为在拖动后,需要同步实时的更新圆心的位置
            x1 = e.getX();
            y1 = e.getY();
            circle1.setCenterX(x1);
            circle1.setCenterY(y1);
            text1.setX(x1 + 5);
            text1.setY(y1 - 5);
            text1.setText("c1: " + angleA(x1, y1, x2, y2, x3, y3));
            text2.setText("c2: " + angleB(x1, y1, x2, y2, x3, y3));
            text3.setText("c3: " + angleC(x1, y1, x2, y2, x3, y3));
            pane.getChildren().addAll(text1, text2, text3);
        });

        circle2.setOnMouseDragged(e -> {
            pane.getChildren().removeAll(text1, text2, text3);
            x2 = e.getX();
            y2 = e.getY();
            circle2.setCenterX(x2);
            circle2.setCenterY(y2);
            text2.setX(x2 + 5);
            text2.setY(y2 - 5);
            text1.setText("c1: " + angleA(x1, y1, x2, y2, x3, y3));
            text2.setText("c2: " + angleB(x1, y1, x2, y2, x3, y3));
            text3.setText("c3: " + angleC(x1, y1, x2, y2, x3, y3));
            pane.getChildren().addAll(text1, text2, text3);
        });

        circle3.setOnMouseDragged(e -> {
            pane.getChildren().removeAll(text1, text2, text3);
            x3 = e.getX();
            y3 = e.getY();
            circle3.setCenterX(x3);
            circle3.setCenterY(y3);
            text3.setX(x3 + 5);
            text3.setY(y3 - 5);
            text1.setText("c1: " + angleA(x1, y1, x2, y2, x3, y3));
            text2.setText("c2: " + angleB(x1, y1, x2, y2, x3, y3));
            text3.setText("c3: " + angleC(x1, y1, x2, y2, x3, y3));

            pane.getChildren().addAll(text1, text2, text3);
        });

        Scene scene = new Scene(pane, 100, 100);
        pStage.setTitle("Test Program");
        pStage.setScene(scene);
        pStage.show();
    }
    
    public double angleA(double x1, double y1, double x2, double y2, double x3, double y3) {
        // 计算边长
        double a = Math.sqrt((x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3));
        double b = Math.sqrt((x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3));
        double c = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));

        // 计算角度
        double A = Math.toDegrees(Math.acos((a * a - b * b - c * c) / (-2 * b * c)));

        return Math.round(A * 100) / 100.0;
    }

    public double angleB(double x1, double y1, double x2, double y2, double x3, double y3) {
        // 计算边长
        double a = Math.sqrt((x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3));
        double b = Math.sqrt((x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3));
        double c = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));

        // 计算角度
        double B = Math.toDegrees(Math.acos((b * b - a * a - c * c) / (-2 * a * c)));

        return Math.round(B * 100) / 100.0;
    }

    public double angleC(double x1, double y1, double x2, double y2, double x3, double y3) {
        // 计算边长
        double a = Math.sqrt((x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3));
        double b = Math.sqrt((x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3));
        double c = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));

        // 计算角度
        double C = Math.toDegrees(Math.acos((c * c - b * b - a * a) / (-2 * a * b)));

        return Math.round(C * 100) / 100.0;
    }
}

输出结果:

在这里插入图片描述



*15.21(拖动点)

绘制一个圆,在圆上有三个随机点。连接这些点构成一个三角形。显示三角形中的角度。使用鼠标沿着圆的边拖动点。拖动的时候,三角形以及角度动态地重新显示,如图15-30b 所示。计算三角形角度的公式参考程序清单4-1

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;

public class Test extends Application {
    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();

        // 设置大圆
        Circle bigCircle = new Circle(50, 50, 40, Color.TRANSPARENT);
        bigCircle.setStroke(Color.BLACK);

        // 设置3个圆心在大圆边上的小圆
        // 首先,随机生成一个角度
        double randomAngle1 = Math.random() * 2 * Math.PI;
        double randomAngle2 = Math.random() * 2 * Math.PI;
        double randomAngle3 = Math.random() * 2 * Math.PI;

        // 然后,根据极坐标计算点的坐标
        double x1 = bigCircle.getCenterX() + bigCircle.getRadius() * Math.cos(randomAngle1);
        double y1 = bigCircle.getCenterY() + bigCircle.getRadius() * Math.sin(randomAngle1);
        double x2 = bigCircle.getCenterX() + bigCircle.getRadius() * Math.cos(randomAngle2);
        double y2 = bigCircle.getCenterY() + bigCircle.getRadius() * Math.sin(randomAngle2);
        double x3 = bigCircle.getCenterX() + bigCircle.getRadius() * Math.cos(randomAngle3);
        double y3 = bigCircle.getCenterY() + bigCircle.getRadius() * Math.sin(randomAngle3);

        // 最后,创建3个小圆
        Circle smallCircle1 = new Circle(x1, y1, 5, Color.TRANSPARENT);
        smallCircle1.setStroke(Color.BLACK);
        Circle smallCircle2 = new Circle(x2, y2, 5, Color.TRANSPARENT);
        smallCircle2.setStroke(Color.BLACK);
        Circle smallCircle3 = new Circle(x3, y3, 5, Color.TRANSPARENT);
        smallCircle3.setStroke(Color.BLACK);

        // 生成3条边
        Line line1 = new Line(x1, y1, x2, y2);
        Line line2 = new Line(x2, y2, x3, y3);
        Line line3 = new Line(x3, y3, x1, y1);

        // 将圆形的圆心坐标绑定到3条边上,圆心移动,三条边随之移动
        line1.startXProperty().bind(smallCircle1.centerXProperty());
        line1.startYProperty().bind(smallCircle1.centerYProperty());
        line2.startXProperty().bind(smallCircle2.centerXProperty());
        line2.startYProperty().bind(smallCircle2.centerYProperty());
        line3.startXProperty().bind(smallCircle3.centerXProperty());
        line3.startYProperty().bind(smallCircle3.centerYProperty());
        line1.endXProperty().bind(smallCircle2.centerXProperty());
        line1.endYProperty().bind(smallCircle2.centerYProperty());
        line2.endXProperty().bind(smallCircle3.centerXProperty());
        line2.endYProperty().bind(smallCircle3.centerYProperty());
        line3.endXProperty().bind(smallCircle1.centerXProperty());
        line3.endYProperty().bind(smallCircle1.centerYProperty());

        pane.getChildren().addAll(bigCircle, smallCircle1, smallCircle2, smallCircle3, line1, line2, line3);

        smallCircle1.setOnMouseDragged(e -> {
            double mouseX = e.getX();
            double mouseY = e.getY();

            double angle = Math.atan2(mouseY - bigCircle.getCenterY(), mouseX - bigCircle.getCenterX());
            double smallCircleX = bigCircle.getCenterX() + bigCircle.getRadius() * Math.cos(angle);
            double smallCircleY = bigCircle.getCenterY() + bigCircle.getRadius() * Math.sin(angle);

            smallCircle1.setCenterX(smallCircleX);
            smallCircle1.setCenterY(smallCircleY);
        });

        smallCircle2.setOnMouseDragged(e -> {
            double mouseX = e.getX();
            double mouseY = e.getY();

            double angle = Math.atan2(mouseY - bigCircle.getCenterY(), mouseX - bigCircle.getCenterX());
            double smallCircleX = bigCircle.getCenterX() + bigCircle.getRadius() * Math.cos(angle);
            double smallCircleY = bigCircle.getCenterY() + bigCircle.getRadius() * Math.sin(angle);

            smallCircle2.setCenterX(smallCircleX);
            smallCircle2.setCenterY(smallCircleY);
        });

        smallCircle3.setOnMouseDragged(e -> {
            double mouseX = e.getX();
            double mouseY = e.getY();

            double angle = Math.atan2(mouseY - bigCircle.getCenterY(), mouseX - bigCircle.getCenterX());
            double smallCircleX = bigCircle.getCenterX() + bigCircle.getRadius() * Math.cos(angle);
            double smallCircleY = bigCircle.getCenterY() + bigCircle.getRadius() * Math.sin(angle);

            smallCircle3.setCenterX(smallCircleX);
            smallCircle3.setCenterY(smallCircleY);
        });

        Scene scene = new Scene(pane, 100, 100);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Drag Small Circle on Big Circle");
        primaryStage.show();
    }
}

输出结果:

在这里插入图片描述



*15.22(自动改变大小的圆柱)

重写编程练习题14.10,当窗体改变大小的时候,圆柱的宽度和高度自动改变大小

package com.example.demo;

import javafx.application.Application;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.scene.Scene;

public class Test extends Application {
    @Override
    public void start(Stage pStage) {
        // 一个一个画出来即可
        Pane pane = new Pane();

        Ellipse ellipse = new Ellipse(150, 50, 100, 30);
        ellipse.setStroke(Color.BLACK);
        ellipse.setFill(Color.TRANSPARENT);
        ellipse.centerXProperty().bind(pane.widthProperty().multiply(0.5));
        ellipse.centerYProperty().bind(pane.heightProperty().multiply(0.1667));
        ellipse.radiusXProperty().bind(pane.widthProperty().multiply(0.3333));
        ellipse.radiusYProperty().bind(pane.heightProperty().multiply(0.1));

        Line line1 = new Line(50, 50, 50, 250);
        Line line2 = new Line(250, 50, 250, 250);
        line1.startXProperty().bind(pane.widthProperty().multiply(0.1667));
        line1.startYProperty().bind(pane.heightProperty().multiply(0.1667));
        line1.endXProperty().bind(pane.widthProperty().multiply(0.1667));
        line1.endYProperty().bind(pane.heightProperty().multiply(0.8333));
        line2.startXProperty().bind(pane.widthProperty().multiply(0.8333));
        line2.startYProperty().bind(pane.heightProperty().multiply(0.1667));
        line2.endXProperty().bind(pane.widthProperty().multiply(0.8333));
        line2.endYProperty().bind(pane.heightProperty().multiply(0.8333));

        Arc arc1 = new Arc(150, 250, 100, 30, 0, -180);
        arc1.setType(ArcType.OPEN);
        arc1.setFill(Color.TRANSPARENT);
        arc1.setStroke(Color.BLACK);
        arc1.centerXProperty().bind(pane.widthProperty().multiply(0.5));
        arc1.centerYProperty().bind(pane.heightProperty().multiply(0.8333));
        arc1.radiusXProperty().bind(pane.widthProperty().multiply(0.3333));
        arc1.radiusYProperty().bind(pane.heightProperty().multiply(0.1));

        Arc arc2 = new Arc(150, 250, 100, 30, 0, 180);
        arc2.setType(ArcType.OPEN);
        arc2.setFill(Color.TRANSPARENT);
        arc2.setStroke(Color.BLACK);
        // 书中给的提示
        arc2.getStrokeDashArray().addAll(6.0, 21.0);
        arc2.centerXProperty().bind(pane.widthProperty().multiply(0.5));
        arc2.centerYProperty().bind(pane.heightProperty().multiply(0.8333));
        arc2.radiusXProperty().bind(pane.widthProperty().multiply(0.3333));
        arc2.radiusYProperty().bind(pane.heightProperty().multiply(0.1));

        pane.getChildren().addAll(ellipse, line1, line2, arc1, arc2);

        pStage.setTitle("Test Program");
        Scene scene = new Scene(pane, 300, 300);
        pStage.setScene(scene);
        pStage.show();
    }
}

输出结果:

在这里插入图片描述



*15.23 (自动改变大小的停止标识)

重写编程练习题14.15, 当窗体改变大小的时候,停止标识的宽度和髙度自动改变大小

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Test extends Application {

    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();
        Scene scene = new Scene(pane, 400, 400);

        // 创建正八边形
        Polygon octagon = createOctagon(200, 200, 100);
        octagon.setFill(Color.RED);

        // 创建文字 "STOP"
        Text text = new Text("STOP");
        text.setFont(Font.font(30));
        text.setFill(Color.WHITE);
        text.setX(200 - text.getLayoutBounds().getWidth() / 2);
        text.setY(200 + text.getLayoutBounds().getHeight() / 4);

        // 将正八边形和文字添加到面板中
        pane.getChildren().addAll(octagon, text);

        // 监听窗体大小改变事件
        scene.widthProperty().addListener((obs, oldWidth, newWidth) -> resizeShape(newWidth.doubleValue(), pane, octagon, text));
        scene.heightProperty().addListener((obs, oldHeight, newHeight) -> resizeShape(newHeight.doubleValue(), pane, octagon, text));

        primaryStage.setScene(scene);
        primaryStage.setTitle("Resizable Octagon with Text");
        primaryStage.show();
    }

    // 创建正八边形
    private Polygon createOctagon(double centerX, double centerY, double sideLength) {
        Polygon octagon = new Polygon();
        for (int i = 0; i < 8; i++) {
            double angle = 2 * Math.PI * i / 8;
            double x = centerX + sideLength * Math.cos(angle);
            double y = centerY + sideLength * Math.sin(angle);
            octagon.getPoints().addAll(x, y);
        }
        octagon.setRotate(22);
        return octagon;
    }

    // 调整形状和文字的大小和位置
    private void resizeShape(double size, Pane pane, Polygon octagon, Text text) {
        double centerX = pane.getWidth() / 2;
        double centerY = pane.getHeight() / 2;
        double sideLength = size * 0.2; // 根据窗体大小调整边长

        // 更新正八边形的顶点坐标
        octagon.getPoints().clear();
        for (int i = 0; i < 8; i++) {
            double angle = 2 * Math.PI * i / 8;
            double x = centerX + sideLength * Math.cos(angle);
            double y = centerY + sideLength * Math.sin(angle);
            octagon.getPoints().addAll(x, y);
        }

        // 更新文字的位置
        text.setX(centerX - text.getLayoutBounds().getWidth() / 2);
        text.setY(centerY + text.getLayoutBounds().getHeight() / 4);
    }
}

输出结果:

在这里插入图片描述



**15.24(动画:来回摆动)

编写一个程序,用动画完成来回摆动,如图15-31所示。单击/释放鼠标以暂停/恢复动画

package com.example.demo;

import javafx.animation.PathTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Test extends Application {

    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();
        Arc arc = new Arc(50, 50, 40, 20, 0, -180);
        arc.setFill(Color.TRANSPARENT);
        arc.setStroke(Color.BLACK);
        Circle circle = new Circle(5);
        pane.getChildren().addAll(arc, circle);

        PathTransition pt = new PathTransition();
        pt.setDuration(Duration.millis(4000));
        pt.setPath(arc);
        pt.setNode(circle);
        pt.setCycleCount(Timeline.INDEFINITE);
        pt.setAutoReverse(true);
        pt.play();

        pane.setOnMousePressed(e -> pt.pause());
        pane.setOnMouseReleased(e -> pt.play());

        Scene scene = new Scene(pane, 100, 100);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Test Program");
        primaryStage.show();
    }
}

输出结果:(人生中第一个会动的程序)

在这里插入图片描述



**15.25 (动画:曲线上的球)

请编写一个程序,用动画实现一个沿着正弦函数曲线移动的球,如图15-32所示。当球到达右边界时,它从左边重新开始。用户可以单击鼠标左/右按钮来继续/暂停动画

package com.example.demo;

import javafx.animation.PathTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Test extends Application {

    @Override
    public void start(Stage primaryStage) {
        double width = 200;
        double height = 200;

        // 创建一个画布
        Pane pane = new Pane();

        // 创建坐标系
        Line xAxis = new Line(0, height / 2, width, height / 2);
        Line yAxis = new Line(width / 2, 0, width / 2, height);

        // 创建正弦函数的路径
        Path sinePath = createSinePath(width, height);

        // 创建一个小球
        Circle ball = new Circle(5, Color.RED);

        // 创建路径动画
        PathTransition pathTransition = new PathTransition();
        pathTransition.setDuration(Duration.seconds(4));
        pathTransition.setPath(sinePath);
        pathTransition.setNode(ball);
        pathTransition.setCycleCount(Timeline.INDEFINITE);
        pathTransition.play();

        pane.setOnMousePressed(e -> pathTransition.pause());
        pane.setOnMouseReleased(e -> pathTransition.play());

        pane.getChildren().addAll(xAxis, yAxis, ball, sinePath);
        
        // 创建场景并显示舞台
        Scene scene = new Scene(pane, width, height);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Sine Function Animation");
        primaryStage.show();
    }

    private Path createSinePath(double width, double height) {
        double startX = -2 * Math.PI;
        double endX = 2 * Math.PI;
        double step = 0.1;

        Path path = new Path();
        path.setStroke(Color.BLUE);
        path.setStrokeWidth(2);

        // 创建起始点
        double startY = height / 2 - Math.sin(startX) * height / (2 * Math.PI);
        MoveTo moveTo = new MoveTo(mapX(startX, width), mapY(startY, height));
        path.getElements().add(moveTo);

        // 创建路径
        for (double x = startX + step; x <= endX; x += step) {
            double y = height / 2 - Math.sin(x) * height / (2 * Math.PI);
            LineTo lineTo = new LineTo(mapX(x, width), mapY(y, height));
            path.getElements().add(lineTo);
        }

        return path;
    }

    private double mapX(double x, double width) {
        return (x + 2 * Math.PI) / (4 * Math.PI) * width;
    }

    private double mapY(double y, double height) {
        return height - y;
    }
}

输出结果:

在这里插入图片描述



*15.26 (改变透明度)

重写编程练习题15.24,当球摆动的时候改变球的透明度

package com.example.demo;

import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.animation.PathTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Test extends Application {

    @Override
    public void start(Stage primaryStage) {
        double width = 200;
        double height = 200;

        // 创建一个画布
        Pane pane = new Pane();

        // 创建坐标系
        Line xAxis = new Line(0, height / 2, width, height / 2);
        Line yAxis = new Line(width / 2, 0, width / 2, height);

        // 创建正弦函数的路径
        Path sinePath = createSinePath(width, height);

        // 创建一个小球
        Circle ball = new Circle(5, Color.RED);

        // 创建路径动画
        PathTransition pathTransition = new PathTransition();
        pathTransition.setDuration(Duration.seconds(4));
        pathTransition.setPath(sinePath);
        pathTransition.setNode(ball);
        pathTransition.setCycleCount(Timeline.INDEFINITE);
        pathTransition.play();

        FadeTransition fadeTransition = new FadeTransition(Duration.seconds(2), ball);
        fadeTransition.setFromValue(1.0);
        fadeTransition.setToValue(0.1);
        fadeTransition.setCycleCount(Timeline.INDEFINITE);
        fadeTransition.setAutoReverse(true);
        fadeTransition.play();

        pane.setOnMousePressed(e -> pathTransition.pause());
        pane.setOnMouseReleased(e -> pathTransition.play());

        pane.getChildren().addAll(xAxis, yAxis, ball, sinePath);

        // 创建场景并显示舞台
        Scene scene = new Scene(pane, width, height);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Sine Function Animation");
        primaryStage.show();
    }

    private Path createSinePath(double width, double height) {
        double startX = -2 * Math.PI;
        double endX = 2 * Math.PI;
        double step = 0.1;

        Path path = new Path();
        path.setStroke(Color.BLUE);
        path.setStrokeWidth(2);

        // 创建起始点
        double startY = height / 2 - Math.sin(startX) * height / (2 * Math.PI);
        MoveTo moveTo = new MoveTo(mapX(startX, width), mapY(startY, height));
        path.getElements().add(moveTo);

        // 创建路径
        for (double x = startX + step; x <= endX; x += step) {
            double y = height / 2 - Math.sin(x) * height / (2 * Math.PI);
            LineTo lineTo = new LineTo(mapX(x, width), mapY(y, height));
            path.getElements().add(lineTo);
        }

        return path;
    }

    private double mapX(double x, double width) {
        return (x + 2 * Math.PI) / (4 * Math.PI) * width;
    }

    private double mapY(double y, double height) {
        return height - y;
    }
}

输出结果:

在这里插入图片描述



*15.27 (控制一个移动的文本)

请编写一个程序,显示一个移动的文本,如图15-33a和15-33b所示。文本从左到右循环的移动。当它消失在右侧的时候,又会从左侧再次出现。当鼠标按下的时候,文本停滞不动,当按钮释放的时候,将继续移动

package com.example.demo;

import javafx.animation.PathTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Test extends Application {
    double width = 200;
    double height = 200;
    // 标志变量,用于记录动画的暂停状态
    boolean isPaused = false;

    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();

        // 这里设定了line的终止位置在pane意外,所以text可以飞出去
        Line line = new Line(0, height / 2, width * 1.5, height / 2);
        Text text = new Text("Programming is fun");
        pane.getChildren().addAll(text);

        PathTransition pt = new PathTransition();
        pt.setDuration(Duration.seconds(3));
        pt.setPath(line);
        pt.setNode(text);
        pt.setCycleCount(Timeline.INDEFINITE);
        pt.play();

        pane.setOnMouseClicked(e -> {
            if (isPaused) {
                pt.play();
            } else {
                pt.pause();
            }
            isPaused = !isPaused;
        });

        // 创建场景并显示舞台
        Scene scene = new Scene(pane, width, height);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Test Program");
        primaryStage.show();
    }
}

输出结果:

在这里插入图片描述



**15.28(显示一个转动的风扇)

编写一个程序显示一个转动的风扇,如图15-33c所示。Pause、Resume和Reverse按钮用于暂停、继续和反转风扇的转动

package com.example.demo;

import javafx.animation.Animation;
import javafx.animation.RotateTransition;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class ControlCircleWithoutEventHandling extends Application {
    double width = 200;
    double height = 200;

    @Override
    public void start(Stage primaryStage) {
        StackPane stackPane = new StackPane();
        HBox hBox = new HBox();
        BorderPane borderPane = new BorderPane();

        // 创建叶片弧形
        Circle circle = new Circle(width / 2, height / 2, 60);
        circle.setFill(Color.TRANSPARENT);
        circle.setStroke(Color.BLACK);
        Arc arc1 = new Arc(width / 2, height / 2, 80, 80, 30, 35);
        arc1.setFill(Color.RED);
        arc1.setType(ArcType.ROUND);
        Arc arc2 = new Arc(width / 2, height / 2, 80, 80, 30 + 90, 35);
        arc2.setFill(Color.RED);
        arc2.setType(ArcType.ROUND);
        Arc arc3 = new Arc(width / 2, height / 2, 80, 80, 30 + 180, 35);
        arc3.setFill(Color.RED);
        arc3.setType(ArcType.ROUND);
        Arc arc4 = new Arc(width / 2, height / 2, 80, 80, 30 + 270, 35);
        arc4.setFill(Color.RED);
        arc4.setType(ArcType.ROUND);
        stackPane.getChildren().addAll(circle, arc1, arc2, arc3, arc4);

        // 创建按钮
        Button button1 = new Button("Pause");
        Button button2 = new Button("Resume");
        Button button3 = new Button("Reverse");
        hBox.getChildren().addAll(button1, button2, button3);
        hBox.setAlignment(Pos.CENTER);
        hBox.setSpacing(10);

        // 设置旋转动画
        RotateTransition rotateTransition = new RotateTransition(Duration.seconds(2), stackPane);
        rotateTransition.setByAngle(360);
        rotateTransition.setCycleCount(Animation.INDEFINITE);
        rotateTransition.play();

        // 设置按钮功能
        button1.setOnMouseClicked(e -> {
            rotateTransition.pause();
        });

        button2.setOnMouseClicked(e -> {
            rotateTransition.play();
        });

        // 通过将动画的速率乘以-1,可以实现反转动画的效果。每次点击button3,动画的旋转方向将切换
        button3.setOnMouseClicked(e -> {
            rotateTransition.setRate(rotateTransition.getRate() * -1);
        });

        borderPane.setBottom(hBox);
        borderPane.setCenter(stackPane);

        Scene scene = new Scene(borderPane, width, height);
        primaryStage.setTitle("Test");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

输出结果:

在这里插入图片描述



**15.29 (赛车)

编写一个程序,模拟汽车比赛,如图15-34a所示。汽车从左向右移动。当它到达右端,就从左边重新开始,然后继续同样的过程。可以使用定时器控制动画。使用新的坐标原点(x, y)重新绘制汽车,如图15-34b所示。同样让用户通过按钮的按下/释放来暂停/继续动画,并且通过按下UP和DOWN的箭头键来增加/降低汽车速度

package com.example.demo;

import javafx.animation.PathTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.shape.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Test extends Application {
    double width = 200;
    double height = 200;

    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();
        HBox hBox = new HBox();
        BorderPane borderPane = new BorderPane();

        // 这里设定了line的终止位置在pane意外,所以text可以飞出去
        Line line = new Line(0, height / 2, width * 1.5, height / 2);
        Text text = new Text("我是车");
        pane.getChildren().addAll(text);

        // 创建按钮
        Button button1 = new Button("Pause");
        Button button2 = new Button("Resume");
        hBox.getChildren().addAll(button1, button2);
        hBox.setAlignment(Pos.CENTER);
        hBox.setSpacing(10);

        PathTransition pt = new PathTransition();
        pt.setDuration(Duration.seconds(5));
        pt.setPath(line);
        pt.setNode(text);
        pt.setCycleCount(Timeline.INDEFINITE);
        pt.play();

        // 设置按钮功能
        button1.setOnMouseClicked(e -> {
            pt.pause();
        });

        button2.setOnMouseClicked(e -> {
            pt.play();
        });

        borderPane.setBottom(hBox);
        borderPane.setCenter(pane);

        // 创建场景并显示舞台
        Scene scene = new Scene(borderPane, width, height);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Test Program");
        primaryStage.show();
    }
}

输出结果:

在这里插入图片描述



**15.30 (播放幻灯片)

25张幻灯片都以图像文件(slide0.jpg, slide1.jpg, slide24.jpg)的形式存储在图像目录中,可以在本书的源代码中下载。每个图像的大小都是800 x 600像素。编写一个Java应用程序,自动重复显示这些幻灯片。每两秒显示一张幻灯片。幻灯片按顺序显示。当显示完最后一张幻灯片时,第一张幻灯片重复显示,依此类推。当动画正在播放的时候可以单击按钮暂停,如果动画当前是暂停的,单击恢复

package com.example.demo;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Test extends Application {
    private static final String IMAGE_DIRECTORY = "File:/Users/kevinwang/Downloads/Java语言程序设计/习题/JavaFX_Document/";
    private static final int SLIDE_COUNT = 5; // 幻灯片的总数
    private static final int SLIDE_WIDTH = 200; // 幻灯片宽度
    private static final int SLIDE_HEIGHT = 200; // 幻灯片高度
    private ImageView slideImageView;
    private int currentSlideIndex = 0; // 当前播放页数
    private boolean isPaused = false; // 暂停参数

    @Override
    public void start(Stage primaryStage) {
        HBox buttonBox = new HBox();
        BorderPane root = new BorderPane();

        // 创建幻灯片图像视图
        slideImageView = new ImageView();
        slideImageView.setFitWidth(SLIDE_WIDTH);
        slideImageView.setFitHeight(SLIDE_HEIGHT);

        // 创建按钮控制幻灯片播放
        Button pauseButton = new Button("Pause/Resume");
        pauseButton.setOnAction(e -> isPaused = !isPaused);
        buttonBox.getChildren().add(pauseButton);
        buttonBox.setAlignment(Pos.CENTER);

        root.setCenter(slideImageView);
        root.setBottom(buttonBox);

        // 创建场景
        Scene scene = new Scene(root, SLIDE_WIDTH, SLIDE_HEIGHT + 50);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Slideshow App");
        primaryStage.show();

        // 创建幻灯片切换定时器
        Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(2), e -> {
            if (!isPaused) {
                showNextSlide();
            }
        }));
        timeline.setCycleCount(Animation.INDEFINITE);
        timeline.play();
    }

    private void showNextSlide() {
        Image slideImage = new Image(IMAGE_DIRECTORY  + currentSlideIndex + ".png");
        slideImageView.setImage(slideImage);
        currentSlideIndex++;

        if (currentSlideIndex >= SLIDE_COUNT) {
            currentSlideIndex = 0;
        }
    }
}

输出结果:

在这里插入图片描述



**15.31(几何问題:钟摆)

编写一个程序,用动画完成钟摆,如图15-35所示。单击向上箭头UP键增加速度,单击向下箭头键DWON降低速度。单击S键停止动画,单击R键重新开始

package com.example.demo;

import javafx.animation.Animation;
import javafx.animation.RotateTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Test extends Application {
    // 声明rotateTransition为全局变量,让所有方法可以访问
    private RotateTransition rotateTransition;

    @Override
    public void start(Stage primaryStage) {
        Pane root = new Pane();

        // 创建钟摆线
        Line pendulum = new Line(70, 50, 120, 165);
        pendulum.setStrokeWidth(4);
        pendulum.setStroke(Color.BLACK);
        root.getChildren().add(pendulum);

        // 创建钟摆动画
        rotateTransition = new RotateTransition(Duration.seconds(2), pendulum);
        rotateTransition.setByAngle(45);
        rotateTransition.setCycleCount(Animation.INDEFINITE);
        rotateTransition.setAutoReverse(true);
        rotateTransition.play();

        // 处理键盘事,scene场景不需要显示调用requestFocus
        Scene scene = new Scene(root, 200, 200);
        scene.setOnKeyPressed(event -> {
            if (event.getCode() == KeyCode.UP) {
                increaseSpeed();
            } else if (event.getCode() == KeyCode.DOWN) {
                decreaseSpeed();
            } else if (event.getCode() == KeyCode.S) {
                stopAnimation();
            } else if (event.getCode() == KeyCode.R) {
                restartAnimation();
            }
        });

        // 创建场景
        primaryStage.setScene(scene);
        primaryStage.setTitle("Pendulum App");
        primaryStage.show();
    }

    private void increaseSpeed() {
        rotateTransition.setRate(rotateTransition.getRate() + 0.1);
    }

    private void decreaseSpeed() {
        double newRate = rotateTransition.getRate() - 0.1;
        if (newRate >= 0) {
            rotateTransition.setRate(newRate);
        }
    }

    private void stopAnimation() {
        rotateTransition.pause();
    }

    private void restartAnimation() {
        rotateTransition.play();
    }
}

输出结果:

在这里插入图片描述



*15.32(控制时钟)

修改程序淸单14-21,在类中加入动画。添加两个方法 start() 和 stop() 以启动和停止时钟。编写一个程序,让用户使用 Start 和 Stop 按钮来控制时钟,如图15-36a所示

package com.example.demo;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.time.LocalTime;

public class Test extends Application {
    private static final double WIDTH = 400;
    private static final double HEIGHT = 400;
    private static final double CENTER_X = WIDTH / 2;
    private static final double CENTER_Y = HEIGHT / 2;
    private static final double HAND_LENGTH = WIDTH * 0.4;
    private static final double HOUR_HAND_WIDTH = 6;
    private static final double MINUTE_HAND_WIDTH = 4;
    private static final double SECOND_HAND_WIDTH = 2;

    private GraphicsContext gc;
    private Timeline timeline;

    @Override
    public void start(Stage primaryStage) {
        // 与Pane等布局容器不同,Canvas没有内置的布局能力,是一个自定义绘制区域,需要手动编写绘制逻辑
        Canvas canvas = new Canvas(WIDTH, HEIGHT);
        // GraphicsContext2D是一个绘图上下文对象,提供了绘制图形、文本和图像等的方法,gc是一个变量,用于引用获取到的GraphicsContext2D对象,以便后续的绘图操作使用
        gc = canvas.getGraphicsContext2D();

        // 创建按钮并实现交互逻辑
        Button pauseButton = new Button("Pause");
        Button resumeButton = new Button("Resume");
        resumeButton.setDisable(true);

        pauseButton.setOnAction(event -> {
            timeline.pause();
            pauseButton.setDisable(true);
            resumeButton.setDisable(false);
        });

        resumeButton.setOnAction(event -> {
            timeline.play();
            pauseButton.setDisable(false);
            resumeButton.setDisable(true);
        });

        HBox buttonBox = new HBox(10, pauseButton, resumeButton);
        buttonBox.setAlignment(Pos.CENTER);

        // 创建根节点,其中canvas默认添加到center
        BorderPane root = new BorderPane(canvas);
        root.setBottom(buttonBox);

        // 实现javafx场景
        Scene scene = new Scene(root);
        primaryStage.setTitle("Clock");
        primaryStage.setScene(scene);
        primaryStage.show();

        // 画时钟
        drawClock();

        // 每1秒钟画一次时钟,次数是无限次
        timeline = new Timeline(new KeyFrame(Duration.seconds(1), e -> {
            drawClock();
        }));
        timeline.setCycleCount(Animation.INDEFINITE);
        timeline.play();
    }

    private void drawClock() {
        // 绘制一个长方形区域,清除掉这个长方形内的所有内容
        gc.clearRect(0, 0, WIDTH, HEIGHT);

        // 获取当前时间
        LocalTime currentTime = LocalTime.now();

        // 画时针
        drawHand(currentTime.getHour() % 12 * 30, HAND_LENGTH * 0.5, HOUR_HAND_WIDTH, Color.BLACK);

        // 画分针
        drawHand(currentTime.getMinute() * 6, HAND_LENGTH * 0.7, MINUTE_HAND_WIDTH, Color.BLACK);

        // 画秒针
        drawHand(currentTime.getSecond() * 6, HAND_LENGTH * 0.9, SECOND_HAND_WIDTH, Color.RED);
    }

    private void drawHand(double angle, double length, double width, Color color) {
        // 保存当前绘图状态
        gc.save();
        // 设置绘制的线条颜色
        gc.setStroke(color);
        // 设置绘制的线条宽度
        gc.setLineWidth(width);
        // 设置线条端点样式为圆形
        gc.setLineCap(javafx.scene.shape.StrokeLineCap.ROUND);
        // 将画布坐标系原点移动到钟表中心
        gc.translate(CENTER_X, CENTER_Y);
        // 旋转画布坐标系,使得线条绘制的方向与角度相匹配
        gc.rotate(angle);
        // 绘制线条,起始点为坐标(0, 0),终止点为坐标(0, -length),即在y轴上向上绘制
        gc.strokeLine(0, 0, 0, -length);
        // 恢复之前保存的绘图状态,包括坐标系的平移和旋转
        gc.restore();
    }
}

输出结果:

在这里插入图片描述



***15.33 (游戏:豆机动画)

编写程序用动画实现编程练习题7.21中介绍的豆机。在10个球掉下来之后动画结束,如图15-36b和15-36c所示

package com.example.demo;

import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polyline;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Test extends Application {
    @Override
    public void start(Stage primaryStage) {
        // 随便画两条线意思一下
        Pane pane = new Pane();

        Polyline polyline = new Polyline(120, 50, 120, 100, 30, 240, 30 ,280, 270, 280, 270, 240, 180, 100, 180, 50);

        Line line1 = new Line(60, 280, 60, 240);
        Line line2 = new Line(90, 280, 90, 240);
        Line line3 = new Line(120, 280, 120, 240);
        Line line4 = new Line(150, 280, 150, 240);
        Line line5 = new Line(180, 280, 180, 240);
        Line line6 = new Line(210, 280, 210, 240);
        Line line7 = new Line(240, 280, 240, 240);

        Circle circle1 = new Circle(60, 240, 5);
        Circle circle2 = new Circle(90, 240, 5);
        Circle circle3 = new Circle(120, 240, 5);
        Circle circle4 = new Circle(150, 240, 5);
        Circle circle5 = new Circle(180, 240, 5);
        Circle circle6 = new Circle(210, 240, 5);
        Circle circle7 = new Circle(240, 240, 5);

        Circle circle8 = new Circle(60, 240, 5);
        Circle circle9 = new Circle(75, 220, 5);
        Circle circle10 = new Circle(90, 200, 5);
        Circle circle11 = new Circle(105, 180, 5);
        Circle circle12 = new Circle(120, 160, 5);
        Circle circle13 = new Circle(135, 140, 5);
        Circle circle14 = new Circle(150, 120, 5);

        Circle circle15 = new Circle(105, 220, 5);
        Circle circle16 = new Circle(135, 220, 5);
        Circle circle17 = new Circle(165, 220, 5);
        Circle circle18 = new Circle(195, 220, 5);
        Circle circle19 = new Circle(225, 220, 5);

        Circle circle20 = new Circle(120, 200, 5);
        Circle circle21 = new Circle(150, 200, 5);
        Circle circle22 = new Circle(180, 200, 5);
        Circle circle23 = new Circle(210, 200, 5);

        Circle circle24 = new Circle(135, 180, 5);
        Circle circle25 = new Circle(165, 180, 5);
        Circle circle26 = new Circle(195, 180, 5);

        Circle circle27 = new Circle(150, 160, 5);
        Circle circle28 = new Circle(180, 160, 5);

        Circle circle29 = new Circle(165, 140, 5);

        pane.getChildren().addAll(polyline, line1, line2, line3, line4, line5, line6, line7,
                circle1, circle2, circle3, circle4, circle5, circle6, circle7,
                circle8, circle9, circle10, circle11, circle12, circle13, circle14,
                circle15, circle16, circle17, circle18, circle19,
                circle20, circle21, circle22, circle23, circle24, circle25, circle26,
                circle27, circle28, circle29);

        Polyline dir1 = new Polyline(140, 100, 45, 240, 45, 277);
        Circle target1 = new Circle(3);
        target1.setFill(Color.RED);
        pane.getChildren().addAll(target1);

        Polyline dir2 = new Polyline(140, 100, 140.5, 130, 225, 230, 225, 277);
        Circle target2 = new Circle(3);
        target2.setFill(Color.RED);
        pane.getChildren().addAll(target2);

        PathTransition pt = new PathTransition();
        pt.setDuration(Duration.seconds(4));
        pt.setPath(dir1);
        pt.setNode(target1);
        pt.play();

        PathTransition pt2 = new PathTransition();
        pt2.setDuration(Duration.seconds(4));
        pt2.setPath(dir2);
        pt2.setNode(target2);
        pt2.play();

        Scene scene = new Scene(pane, 300, 300);
        primaryStage.setTitle(getClass().getName());
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

输出结果:

在这里插入图片描述



***15.34 (模拟:自回避随机漫步)

在一个网格中的自回避漫步是指从一个点到另一点的过程中,不重复两次访问一个点。自回避漫步已经广泛应用在物理、化学和数学学科中。它们可以用来模拟像溶剂和聚合物这样的链状物。编写一个程序,显示一个从中心点出发到边界点结束的随机路径,如图15-37a所示,或者在一个尽头点结束(即该点被四个已经访问过的点包围),如图15- 37b所示。假设网格的大小是16×16

代码没有考虑不重复两次访问一个点

package com.example.demo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class Test extends Application {
    // 要画一个正方形,并且在正方形内填充等宽的线条,这里设定了正方形的长和宽,都是200
    double recWidth = 200, recHeight = 200;
    // 在填充线条时,设定填充10根线,在200的长宽限定下,每根线条之间的间隔是20,这里的indexX和Y表示第一根线条的坐标
    int indexX = 20, indexY = 20;
    // 线条交叉连接之后会形成交点,下面是各个焦点的坐标,用String表示
    String[][] array = {
            {"25 25", "45 25", "65 25", "85 25", "105 25", "125 25", "145 25", "165 25", "185 25", "205 25", "225 25"},
            {"25 45", "45 45", "65 45", "85 45", "105 45", "125 45", "145 45", "165 45", "185 45", "205 45", "225 45"},
            {"25 65", "45 65", "65 65", "85 65", "105 65", "125 65", "145 65", "165 65", "185 65", "205 65", "225 65"},
            {"25 85", "45 85", "65 85", "85 85", "105 85", "125 85", "145 85", "165 85", "185 85", "205 85", "225 85"},
            {"25 105", "45 105", "65 105", "85 105", "105 105", "125 105", "145 105", "165 105", "185 105", "205 105", "225 105"},
            {"25 125", "45 125", "65 125", "85 125", "105 125", "125 125", "145 125", "165 125", "185 125", "205 125", "225 125"},
            {"25 145", "45 145", "65 145", "85 145", "105 145", "125 145", "145 145", "165 145", "185 145", "205 145", "225 145"},
            {"25 165", "45 165", "65 165", "85 165", "105 165", "125 165", "145 165", "165 165", "185 165", "205 165", "225 165"},
            {"25 185", "45 185", "65 185", "85 185", "105 185", "125 185", "145 185", "165 185", "185 185", "205 185", "225 185"},
            {"25 205", "45 205", "65 205", "85 205", "105 205", "125 205", "145 205", "165 205", "185 205", "205 205", "225 205"},
            {"25 225", "45 225", "65 225", "85 225", "105 225", "125 225", "145 225", "165 225", "185 225", "205 225", "225 225"}
    };
    // 为了避免访问权限问题,设置全局变量,之后会解释
    int lineStartX;
    int lineStartY;
    int newLineStartX;
    int newLineStartY;

    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();

        // 绘制网格边框,也就是正方形的边框
        Rectangle rectangle = new Rectangle(25, 25, recWidth, recHeight);
        rectangle.setFill(Color.TRANSPARENT);
        rectangle.setStroke(Color.color(0.8, 0.8, 0.8));
        pane.getChildren().addAll(rectangle);

        // 绘制网格纵向线条
        for (int i = 0; i < 9; i++) {
            Line lineX = new Line(rectangle.getX() + indexX, rectangle.getY(), rectangle.getX() + indexX, rectangle.getY() + recHeight);
            lineX.setStroke(Color.color(0.8, 0.8, 0.8));
            indexX += 20;
            pane.getChildren().addAll(lineX);
        }

        // 绘制网格横向线条
        for (int i = 0; i < 9; i++) {
            Line lineY = new Line(rectangle.getX(), rectangle.getY() + indexY, rectangle.getX() + recWidth, rectangle.getY() + indexY);
            lineY.setStroke(Color.color(0.8, 0.8, 0.8));
            indexY += 20;
            pane.getChildren().addAll(lineY);
        }

        // 这里随机生成一个起始点,起始点对应的数组x,y坐标随机产生
        int randomStartX = (int) (Math.random() * 5 + 3), randomStartY = (int) (Math.random() * 5 + 3);
        // System.out.println("randomStartX is " + randomStartX);
        // System.out.println("randomStartY is " + randomStartY);
        // 使用正则表达式解析这个String数组元素,并把他们赋值给lineStartX/Y
        lineStartX = extractX(randomStartX, randomStartY);
        lineStartY = extractY(randomStartX, randomStartY);
        // System.out.println("lineStartX is " + lineStartX);
        // System.out.println("lineStartY is " + lineStartY);
        // 使用解析出来的坐标生成一个点,表示为起始点
        Circle start = new Circle(lineStartX, lineStartY, 3);
        pane.getChildren().add(start);

        // 设定循环条件,当抵达边界的时候就不再进行循环
        while (randomStartX < 10 && randomStartX > 0 && randomStartY < 10 && randomStartY > 0) {
            // 随机生成变化的方向,如果是0,那么变化X,如果是1,那么变化Y
            int changeXY = (int) (Math.random() * 2);
            // 随机生成变化的方向,如果是0,那么+1,如果是1,那么-1
            int change01 = (int) (Math.random() * 2);
            // System.out.println("changeXY is " + changeXY);
            // System.out.println("change01 is " + change01);

            // 在当前随机变化的方向上执行以下操作
            if (changeXY == 0) {
                if (change01 == 0) {
                    // changeXY == 0表示移动发生在X轴,change01 == 0表示X轴要+1
                    randomStartX++;
                    // System.out.println("new randomStartX is " + randomStartX);
                    // System.out.println("new randomStartY is " + randomStartY);

                    // 通过正则表达式找到新的x,y坐标
                    String input = array[randomStartX][randomStartY];
                    String[] numbers = input.split("\\s+");
                    String number1 = numbers[0];
                    String number2 = numbers[1];
                    newLineStartX = Integer.parseInt(number1);
                    newLineStartY = Integer.parseInt(number2);
                    // System.out.println("new lineStartX is " + newLineStartX);
                    // System.out.println("new lineStartY is " + newLineStartY);

                    // 把新的x,y坐标画入直线
                    Line newLine = new Line(lineStartX, lineStartY, newLineStartX, newLineStartY);
                    // 把直线的endX和endY写入startX和startY,这样下一条直线在生成的时候,就可以沿着上一条直线的路径走
                    lineStartX = newLineStartX;
                    lineStartY = newLineStartY;
                    pane.getChildren().add(newLine);
                } else if (change01 == 1) {
                    randomStartX--;
                    // System.out.println("new randomStartX is " + randomStartX);
                    // System.out.println("new randomStartY is " + randomStartY);

                    String input = array[randomStartX][randomStartY];
                    String[] numbers = input.split("\\s+");
                    String number1 = numbers[0];
                    String number2 = numbers[1];
                    newLineStartX = Integer.parseInt(number1);
                    newLineStartY = Integer.parseInt(number2);

                    // System.out.println("new lineStartX is " + newLineStartX);
                    // System.out.println("new lineStartY is " + newLineStartY);

                    Line newLine = new Line(lineStartX, lineStartY, newLineStartX, newLineStartY);
                    lineStartX = newLineStartX;
                    lineStartY = newLineStartY;
                    pane.getChildren().add(newLine);
                }
            }

            if (changeXY == 1) {
                if (change01 == 0) {
                    randomStartY++;
                    // System.out.println("new randomStartX is " + randomStartX);
                    // System.out.println("new randomStartY is " + randomStartY);

                    String input = array[randomStartX][randomStartY];
                    String[] numbers = input.split("\\s+");
                    String number1 = numbers[0];
                    String number2 = numbers[1];
                    newLineStartX = Integer.parseInt(number1);
                    newLineStartY = Integer.parseInt(number2);

                    // System.out.println("new lineStartX is " + newLineStartX);
                    // System.out.println("new lineStartY is " + newLineStartY);

                    Line newLine = new Line(lineStartX, lineStartY, newLineStartX, newLineStartY);
                    lineStartX = newLineStartX;
                    lineStartY = newLineStartY;
                    pane.getChildren().add(newLine);
                } else if (change01 == 1) {
                    randomStartY--;
                    // System.out.println("new randomStartX is " + randomStartX);
                    // System.out.println("new randomStartY is " + randomStartY);

                    String input = array[randomStartX][randomStartY];
                    String[] numbers = input.split("\\s+");
                    String number1 = numbers[0];
                    String number2 = numbers[1];
                    newLineStartX = Integer.parseInt(number1);
                    newLineStartY = Integer.parseInt(number2);

                    // System.out.println("new lineStartX is " + newLineStartX);
                    // System.out.println("new lineStartY is " + newLineStartY);

                    Line newLine = new Line(lineStartX, lineStartY, newLineStartX, newLineStartY);
                    lineStartX = newLineStartX;
                    lineStartY = newLineStartY;
                    pane.getChildren().add(newLine);
                }
            }
        }

        Scene scene = new Scene(pane, 250, 250);
        primaryStage.setTitle(getClass().getName());
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public int extractX(int x, int y) {
        String input = array[x][y];
        String[] numbers = input.split("\\s+");
        String number1 = numbers[0];
        return Integer.parseInt(number1);
    }

    public int extractY(int x, int y) {
        String input = array[x][y];
        String[] numbers = input.split("\\s+");
        String number2 = numbers[1];
        return Integer.parseInt(number2);
    }
}

输出结果:

在这里插入图片描述



***15.35 (动画:自回避随机漫步)

修改上一个练习题,在一个动画中逐步地显示漫步,如图15-37c和图15-37d所示

package com.example.demo;

import javafx.animation.FadeTransition;
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Test extends Application {
    // 代码使用透明度表示小球的消失
    double recWidth = 200, recHeight = 200;
    int indexX = 20, indexY = 20;
    String[][] array = {
            {"25 25", "45 25", "65 25", "85 25", "105 25", "125 25", "145 25", "165 25", "185 25", "205 25", "225 25"},
            {"25 45", "45 45", "65 45", "85 45", "105 45", "125 45", "145 45", "165 45", "185 45", "205 45", "225 45"},
            {"25 65", "45 65", "65 65", "85 65", "105 65", "125 65", "145 65", "165 65", "185 65", "205 65", "225 65"},
            {"25 85", "45 85", "65 85", "85 85", "105 85", "125 85", "145 85", "165 85", "185 85", "205 85", "225 85"},
            {"25 105", "45 105", "65 105", "85 105", "105 105", "125 105", "145 105", "165 105", "185 105", "205 105", "225 105"},
            {"25 125", "45 125", "65 125", "85 125", "105 125", "125 125", "145 125", "165 125", "185 125", "205 125", "225 125"},
            {"25 145", "45 145", "65 145", "85 145", "105 145", "125 145", "145 145", "165 145", "185 145", "205 145", "225 145"},
            {"25 165", "45 165", "65 165", "85 165", "105 165", "125 165", "145 165", "165 165", "185 165", "205 165", "225 165"},
            {"25 185", "45 185", "65 185", "85 185", "105 185", "125 185", "145 185", "165 185", "185 185", "205 185", "225 185"},
            {"25 205", "45 205", "65 205", "85 205", "105 205", "125 205", "145 205", "165 205", "185 205", "205 205", "225 205"},
            {"25 225", "45 225", "65 225", "85 225", "105 225", "125 225", "145 225", "165 225", "185 225", "205 225", "225 225"}
    };
    int lineStartX;
    int lineStartY;
    int newLineStartX;
    int newLineStartY;

    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();

        // 绘制网格边框
        Rectangle rectangle = new Rectangle(25, 25, recWidth, recHeight);
        rectangle.setFill(Color.TRANSPARENT);
        rectangle.setStroke(Color.color(0.8, 0.8, 0.8));
        pane.getChildren().addAll(rectangle);

        // 绘制网格纵向线条
        for (int i = 0; i < 9; i++) {
            Line lineX = new Line(rectangle.getX() + indexX, rectangle.getY(), rectangle.getX() + indexX, rectangle.getY() + recHeight);
            lineX.setStroke(Color.color(0.8, 0.8, 0.8));
            indexX += 20;
            pane.getChildren().addAll(lineX);
        }

        // 绘制网格横向线条
        for (int i = 0; i < 9; i++) {
            Line lineY = new Line(rectangle.getX(), rectangle.getY() + indexY, rectangle.getX() + recWidth, rectangle.getY() + indexY);
            lineY.setStroke(Color.color(0.8, 0.8, 0.8));
            indexY += 20;
            pane.getChildren().addAll(lineY);
        }

        int randomStartX = (int) (Math.random() * 5 + 3), randomStartY = (int) (Math.random() * 5 + 3);
        System.out.println("randomStartX is " + randomStartX);
        System.out.println("randomStartY is " + randomStartY);
        lineStartX = extractX(randomStartX, randomStartY);
        lineStartY = extractY(randomStartX, randomStartY);
        System.out.println("lineStartX is " + lineStartX);
        System.out.println("lineStartY is " + lineStartY);
        Line line = new Line(lineStartX, lineStartY, lineStartX, lineStartY);
        pane.getChildren().add(line);
        line.setStrokeWidth(3);

        while (randomStartX < 10 && randomStartX > 0 && randomStartY < 10 && randomStartY > 0) {
            // 如果是0,那么变化X,如果是1,那么变化Y
            int changeXY = (int) (Math.random() * 2);
            // 如果是0,那么+1,如果是1,那么-1
            int change01 = (int) (Math.random() * 2);
            System.out.println("changeXY is " + changeXY);
            System.out.println("change01 is " + change01);

            if (changeXY == 0) {
                if (change01 == 0) {
                    randomStartX++;
                    System.out.println("new randomStartX is " + randomStartX);
                    System.out.println("new randomStartY is " + randomStartY);

                    String input = array[randomStartX][randomStartY];
                    String[] numbers = input.split("\\s+");
                    String number1 = numbers[0];
                    String number2 = numbers[1];
                    newLineStartX = Integer.parseInt(number1);
                    newLineStartY = Integer.parseInt(number2);
                    System.out.println("new lineStartX is " + newLineStartX);
                    System.out.println("new lineStartY is " + newLineStartY);

                    Line newLine = new Line(lineStartX, lineStartY, newLineStartX, newLineStartY);
                    lineStartX = newLineStartX;
                    lineStartY = newLineStartY;

                    Circle circle = new Circle(3);
                    pane.getChildren().add(circle);

                    // 创建路径动画
                    PathTransition pathTransition = new PathTransition();
                    pathTransition.setDuration(Duration.seconds(2));
                    pathTransition.setPath(newLine);
                    pathTransition.setNode(circle);

                    // 创建透明度动画
                    FadeTransition fadeTransition = new FadeTransition(Duration.seconds(1), circle);
                    fadeTransition.setFromValue(1);
                    fadeTransition.setToValue(0);
                    fadeTransition.setDelay(Duration.seconds(0.5));

                    // 播放动画序列
                    pathTransition.setOnFinished(event -> fadeTransition.play());
                    pathTransition.play();

                    pane.getChildren().add(newLine);
                    line.setStrokeWidth(3);
                } else if (change01 == 1) {
                    randomStartX--;
                    System.out.println("new randomStartX is " + randomStartX);
                    System.out.println("new randomStartY is " + randomStartY);

                    String input = array[randomStartX][randomStartY];
                    String[] numbers = input.split("\\s+");
                    String number1 = numbers[0];
                    String number2 = numbers[1];
                    newLineStartX = Integer.parseInt(number1);
                    newLineStartY = Integer.parseInt(number2);

                    System.out.println("new lineStartX is " + newLineStartX);
                    System.out.println("new lineStartY is " + newLineStartY);

                    Line newLine = new Line(lineStartX, lineStartY, newLineStartX, newLineStartY);
                    lineStartX = newLineStartX;
                    lineStartY = newLineStartY;

                    Circle circle = new Circle(3);
                    pane.getChildren().add(circle);

                    // 创建路径动画
                    PathTransition pathTransition = new PathTransition();
                    pathTransition.setDuration(Duration.seconds(2));
                    pathTransition.setPath(newLine);
                    pathTransition.setNode(circle);

                    // 创建透明度动画
                    FadeTransition fadeTransition = new FadeTransition(Duration.seconds(1), circle);
                    fadeTransition.setFromValue(1);
                    fadeTransition.setToValue(0);
                    fadeTransition.setDelay(Duration.seconds(0.5));

                    // 播放动画序列
                    pathTransition.setOnFinished(event -> fadeTransition.play());
                    pathTransition.play();

                    pane.getChildren().add(newLine);
                    line.setStrokeWidth(3);
                }
            }

            if (changeXY == 1) {
                if (change01 == 0) {
                    randomStartY++;
                    System.out.println("new randomStartX is " + randomStartX);
                    System.out.println("new randomStartY is " + randomStartY);

                    String input = array[randomStartX][randomStartY];
                    String[] numbers = input.split("\\s+");
                    String number1 = numbers[0];
                    String number2 = numbers[1];
                    newLineStartX = Integer.parseInt(number1);
                    newLineStartY = Integer.parseInt(number2);

                    System.out.println("new lineStartX is " + newLineStartX);
                    System.out.println("new lineStartY is " + newLineStartY);

                    Line newLine = new Line(lineStartX, lineStartY, newLineStartX, newLineStartY);
                    lineStartX = newLineStartX;
                    lineStartY = newLineStartY;

                    Circle circle = new Circle(3);
                    pane.getChildren().add(circle);

                    // 创建路径动画
                    PathTransition pathTransition = new PathTransition();
                    pathTransition.setDuration(Duration.seconds(2));
                    pathTransition.setPath(newLine);
                    pathTransition.setNode(circle);

                    // 创建透明度动画
                    FadeTransition fadeTransition = new FadeTransition(Duration.seconds(1), circle);
                    fadeTransition.setFromValue(1);
                    fadeTransition.setToValue(0);
                    fadeTransition.setDelay(Duration.seconds(0.5));

                    // 播放动画序列
                    pathTransition.setOnFinished(event -> fadeTransition.play());
                    pathTransition.play();

                    pane.getChildren().add(newLine);
                    line.setStrokeWidth(3);
                } else if (change01 == 1) {
                    randomStartY--;
                    System.out.println("new randomStartX is " + randomStartX);
                    System.out.println("new randomStartY is " + randomStartY);

                    String input = array[randomStartX][randomStartY];
                    String[] numbers = input.split("\\s+");
                    String number1 = numbers[0];
                    String number2 = numbers[1];
                    newLineStartX = Integer.parseInt(number1);
                    newLineStartY = Integer.parseInt(number2);

                    System.out.println("new lineStartX is " + newLineStartX);
                    System.out.println("new lineStartY is " + newLineStartY);

                    Line newLine = new Line(lineStartX, lineStartY, newLineStartX, newLineStartY);
                    lineStartX = newLineStartX;
                    lineStartY = newLineStartY;

                    Circle circle = new Circle(3);
                    pane.getChildren().add(circle);

                    // 创建路径动画
                    PathTransition pathTransition = new PathTransition();
                    pathTransition.setDuration(Duration.seconds(2));
                    pathTransition.setPath(newLine);
                    pathTransition.setNode(circle);

                    // 创建透明度动画
                    FadeTransition fadeTransition = new FadeTransition(Duration.seconds(1), circle);
                    fadeTransition.setFromValue(1);
                    fadeTransition.setToValue(0);
                    fadeTransition.setDelay(Duration.seconds(0.5));

                    // 播放动画序列
                    pathTransition.setOnFinished(event -> fadeTransition.play());
                    pathTransition.play();

                    pane.getChildren().add(newLine);
                    line.setStrokeWidth(3);
                }
            }
        }

        Scene scene = new Scene(pane, 250, 250);
        primaryStage.setTitle(getClass().getName());
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public int extractX(int x, int y) {
        String input = array[x][y];
        String[] numbers = input.split("\\s+");
        String number1 = numbers[0];
        return Integer.parseInt(number1);
    }

    public int extractY(int x, int y) {
        String input = array[x][y];
        String[] numbers = input.split("\\s+");
        String number2 = numbers[1];
        return Integer.parseInt(number2);
    }
}

输出结果:

在这里插入图片描述



**15.36 (模拟:自回避随机漫步)

编写一个模拟程序,显示出现尽头点路径的可能性随着格子数量的增加而变大。程序模拟大小从10到80的网格。对每种大小的网格,模拟自回避随机漫步10000 次,然后显示出现尽头点路径的概率,如下面的示例输出所示

package com.example.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class SelfAvoidingWalk {
    private static final int SIZE = 10; // 网格大小
    private static final int[][] DIRECTIONS = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 上、下、左、右四个方向

    public static void main(String[] args) {
        boolean[][] visited = new boolean[SIZE][SIZE]; // 记录每个位置是否已经访问过
        List<int[]> path = new ArrayList<>(); // 记录路径

        int x = SIZE / 2; // 起始位置 x 坐标
        int y = SIZE / 2; // 起始位置 y 坐标

        path.add(new int[]{x, y});
        visited[x][y] = true;

        Random random = new Random();

        // 执行自回避漫步直到到达边界点或尽头点
        while (x > 0 && x < SIZE - 1 && y > 0 && y < SIZE - 1 && !isDeadEnd(x, y, visited)) {
            int[] direction = DIRECTIONS[random.nextInt(4)]; // 随机选择一个方向

            int newX = x + direction[0];
            int newY = y + direction[1];

            if (!visited[newX][newY]) {
                x = newX;
                y = newY;
                path.add(new int[]{x, y});
                visited[x][y] = true;
            }
        }

//        // 打印路径
//        for (int[] point : path) {
//            System.out.println("(" + point[0] + ", " + point[1] + ")");
//        }

        double endCount = 0;
        double notEndCount = 0;
        for (int i = 0; i < 10000; i++) {
            if (path.get(path.size() - 1)[0] == 0 || path.get(path.size() - 1)[0] == 9 || path.get(path.size() - 1)[1] == 0 || path.get(path.size() - 1)[1] == 9) {
                endCount++;
            } else {
                notEndCount++;
            }
        }
        // 通过改变SIZE可以输出20等样本
        System.out.println("For a lattice size of 10, the probability of dead-end paths is " + notEndCount / 10000.0);
    }

    // 判断当前位置是否是尽头点(被四个已访问过的点包围)
    private static boolean isDeadEnd(int x, int y, boolean[][] visited) {
        int count = 0;
        for (int[] direction : DIRECTIONS) {
            int newX = x + direction[0];
            int newY = y + direction[1];
            if (visited[newX][newY]) {
                count++;
            }
        }
        return count == 4;
    }
}

输出结果:

For a lattice size of 10, the probability of dead-end paths is 0.0



版权声明:本文为weixin_40020256原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。