0%

Java/事件驱动编程和动画

Java 事件驱动编程和动画

引言

要点提示: 可以编写代码以处理诸如单击按钮、鼠标移动以及按键盘之类的时间。

为了响应一个按钮单击事件,你需要编写代码来处理按钮单击动作。按钮是一个事件源对象,即动作起源的地方。需要创建一个能对一个按钮动作事件进行处理的对象,即事件处理器。

image-20200522103644432

不是多有对象都可以成为一个动作事件的处理器。要成为一个动作事件的处理器,必须满足两个要求:

  1. 该对象必须是EventHandler接口的一个示例。接口定义了所有处理器的共同行为。T extends Event是一个Event子类型的泛型。
  2. EventHandler对象handler必须使用方法source.setOnAction(handler)和事件源对象注册
  3. EventHandler 接口包含了 handle ( ActionEvent ) 方法用于处理动作事件。你的处理器类必须覆盖这个方法来响应事件 。 15 ActionEvent 事件的代码 。

事件和事件源

要点提示: 事件是从一个事件源上产生的对象。触发一个事件意味着产生一个事件并委托处理器处理该事件。

事件驱动编程: 当运行一个Java GUI程序的时候,程序和用户进行交互,并且事件驱动它的执行。这称为事件驱动编程。

事件可以被定义为一个告知程序某件事发生的信号。

事件由外部的用户动作,比如鼠标的移动、单击和键盘按键所触发。

事件源对象: 产生一个事件并且出发它的组件称为事件源对象,或称为源对象或者源组件。

image-20200522110409887

注意:如果一个组件可以触发一个事件,那么这个组件的任何子类都可以触发同样类型的事件。比如,每个JavaFX形状、布局面板和组件都可以触发MouseEvent和KeyEvent事件,因为Node是形状、布局面板和组件的超类。

image-20200522110728861

注册处理器和处理事件

要点提示: 处理器是一个对象,它必须通过一个事件源对象进行注册,并且它必须是一个恰当的事件处理接口的实例。

image-20200522111645923

技巧:设计一个类来建模一个包含了支持方法的面板是一个好的策略,这样相关的方法和面板都耦在一个对象中来。

内部类

要点提示: 内部类,或者称为嵌套类,是一个定义在另外一个类范围中的类。内部类对于定义处理器非常有用。

一个内部类可以如常规类一样使用。通常,在一个类只被它的外部类所使用的时候,才将它定义为内部类。

一个内部类具有下面的特征:

  • 一个内部类被被编译为OuterClassName$InnerClassName的类。
  • 一个内部类可以引用定义在它所在的外部类中的数据和方法。所以,没有必要将外部类对象的引用传递给内部类的构造方法。内部类可以使程序更加精简。
  • 一个内部类可以使用可见性修饰符所定义,和应用于一个类中的成员的可见性规则一样
  • 一个内部类可以被定义为static。一个static的内部类可以使用外部类的名字所访问。一个static的内部类不能访问外部类中非静态成员。
  • 内部类对象通常在外部类中所创建。也可以从另外一个类中来创建一个内部类的对象。如果内部类是非静态的,你必须先创建一个外部类的实例,然后使用以下语法来创建一个内部类的对象。
1
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
  • 如果内部类是静态的,使用以下语法来创建一个内部类对象。
1
OuterClass.InnerClass innerObject = new OuterClass.InnerClass();

内部类的用途:

  • 将相互依赖的类结合到一个主类中
  • 避免类名的冲突

一个处理器类被设计为针对一个GUI组件创建一个处理器对象(比如,一个按钮)。处理器类不会被其他应用所共享,所以将它定义在主类里面作为一个内部类是恰如其分的。

匿名内部类处理器

要点提示:一个匿名内部类是一个没有名字的内部类。它将进一步实现定义一个内部类以及创建一个内部类的实例。

下面是一个内部类被匿名内部类替代的示例:

image-20200522154958242

匿名内部类的语法如下所示:

1
2
3
4
5
new superClassName/InterfaceName(){
// Implement or override methods in superclass or interface
// Other methods if necessary

}

匿名内部类是一种特殊类型的内部类,它被当作一个内部类对待,同时具有下面的特征:

  • 一个匿名内部类必须总是从一个父类继承或者实现一个接口,但是它不能有显式的extends或者implements子句
  • 一个匿名内部类必须实现父类或者接口中的所有抽象方法
  • 一个匿名内部类总是使用它父类的无参构造方法来创建一个实例。如果一个匿名内部类实现一个接口,构造方法是Object().
  • 一个匿名内部类被编译成一个名为OuterClassName$n.class。

例如如果外部类Test有两个匿名的内部类,它们将被编译成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

## 使用lambda表达式简化事件处理

要点提示: lambda表达式可以用于极大简化事件处理器的代码编写.

lambda表达式可以被看作使用精简语法的匿名内部类。

如图是将匿名内部类用lambda表达式代替的一个例子:

![image-20200524001209498](https://tva1.sinaimg.cn/large/007S8ZIlly1gf2u5ud8zgj31qq0kmdrp.jpg)

一个lambda表达式的基础语法是:

```java
(type1 parm1, type2 parm2, ...) -> expression

或者

1
(type1 parm1, type2 parm2, ...) -> {statements;}

一个参数的数据类型既可以显式声明,也可以由编译器隐式推断。如果只有一个参数,并且没有显示的数据类型,圆括号可以被省略。

因而上面(截图)的例子可以表示为:

1
2
3
e -> {
// Code for processing event e
}

image-20200524124547574

示例学习: 贷款计算器

示例学习: 鼠标事件

要点提示:当一个鼠标按键在一个节点或者一个场景中被按下、释放、单击、移动或者拖动时,一个MouseEvent事件被触发。

MouseEvent对象捕捉事件,例如和它相关的单击数、鼠标位置或者那个叫鼠标按键被按下:

image-20200524151800627

四个常数——PRIMARY,SECONDARY,MIDDLE和None在MouseEvent中被定义,表明鼠标的左、右、中以及无按钮。shiyonggetButton()方法来探测哪个按钮被按下。

在任何节点和场景都可触发鼠标事件。

键盘事件 KeyEvent

要点提示:在一个节点或者一个场景上面只要按下、释放或者敲击键盘,就会触发一个KeyEvent事件。

键盘事件使得可以采用键盘来控制和执行动作,或者从键盘获得输入。KeyEvent对象描述了事件的性质(即,一个按键被按下释放或者敲击)以及键值。

image-20200524154213607

每个键盘事件有一个相关的编码,可以通过KeyEvent的getCode()方法返回。键的编码是定义在KeyCode中的常量。KeyCode是一个enum类型的变量。

对于按下键和释放键的事件,getCode()返回表中的值, getText()返回一个描述键的代码的字符串, getCharacter()返回一个空字符串。对于敲击键额事件,geCode()返回UNDEFINED, getcCharacter()返回相应的Unicode字符或者和敲击事件相关的一个字符序列。

image-20200524154812371

image-20200524154825125

注意

在一个枚举类型值的switch语句中,case后面跟的是枚举常量。常量是不受限制的(unqalified)即无须加KeyCode等类限定。例如: 在case子句中使用keyCode.DOWN将出现错误。

只有一个被拒交的节点可以接受KeyEvent事件。在一个text上调用requestFocus()使得text可以接受键盘输入。这个方法必须在舞台被显示后调用。

注意: 单击一个按钮之后,circlePane将不再被聚焦,为了修复这个问题,可以在每次按钮被单击后,在circlePane上再次调用requestFocus()。

可观察对象的监听器

*要点提示: *可以通过添加一个监听器来处理可观察对象中的值的变化

一个Observable类的实例被认为是一个可观察对象,它包含了一个addListener(InvalidationListener listener)方法用于添加监听器。监听器类必须实现InvalidationListenr接口以重写invalidate(Observable o)方法,从而可以处理值的改变。一旦observable中的值改变了,通过调用invalidate(Observable o)方法,监听器得到通知。每个绑定属性都是Observable的实例。

可以使用lambda来简化添加监听器的流程。注意处理器使用的是e,监听器使用的是ov。

动画

*要点提示: *JavaFx中的Animaiton类为所有的动画制作提供了核心功能。

JavaFX提供了许多Animation的具体子类。

image-20200524191335802

其中autoReverse是一个Boolean属性,表示下一周期中动画是否要倒转方向。cycleCount表示了该动画的循环次数。使用Tiemline.INDEFINTE表示无限循环。rate定义了动画的速度。一个负的rate值表示动画的相反方向。status是只读属性,表明了动画的状态(Animation.Status.PAUSED、Animation.Status.RUNNING和Animation.Status.STOPPED)。方法pause(),play(),stop()分别表示暂停、播放和终止动画。

PathTrasition

PathTrasition类制作一个在给定时间,节点沿着一条路从一个端点到另一个端点的移动动画,PathTransition是Animation的子类型。

image-20200524192103626

Duration类定义了持续事件。它是一个不可更改的类。这个类定义类常量INDEFINTE,ONE,UNKNOW和ZERO来代表一个无限循环、1毫秒、未知以及哦的持续时间。可以使用new Duration(double millis)来创建一个Duration实例,使用add、substract、multiply和divide方法来执行算数操作,还可以使用toHours(),toMinutes(),toSeconds()和tomMillis()来返回持续时间值中的小时数、分钟数、秒钟数和毫秒数。还可以使用comPareTo来比较两个持续时间。

常量NONE和ORTHOGONAL_TO_TANGET在PathTransiton.OrientationType中定义。后者确定节点在沿着几何路径移动的过程中是否和路径的切线保持垂直。

FadeTransition

FadeTransition类在一个给定的时间内,通过改变一个节点的透明度来产生动画。FadeTransition是Animation的子类型。

image-20200525230347355

Timeline

PathTransition和FadeTransition定义类的特定的动画。Timeline类可以通过使用一个或者更多的KeyFrame(关键帧)来编写任意动画。每个KeyFrame在一个给定的时间间隔内顺序执行。Timeline继承自Animation。

通过new Timeline(KeyFrame… keyframe)来构建一个Timeline。

一个KeyFrame可以使用以下语句来构建:

1
new KeyFrame(Duration duration, EeventHandler<ActionEvent> onFinished)

处理器onFinished方法当这个关键帧的持续时间结束后被调用。

示例学习 :弹球