AS3.0相关知识

as3.0
2010-01-06 00:21

继承

继承是指一种代码重用的形式,允许程序员基于现有类开发新类。现有类通常称为“基类”或“超类”,新类通常称为“子类”。继承的主要优势是,允许重复使用基类中的代码,但不修改现有代码。此外,继承不要求改变其它类与基类交互的方式。不必修改可能已经过彻底测试或可能已被使用的现有类,使用继承可将该类视为一个集成模块,可使用其它属性或方法对它进行扩展。因此,您使用 extends 关键字指明类从另一类继承。

通过继承还可以在代码中利用“多态”。有一种方法在应用于不同数据类型时会有不同行为,多态就是对这样的方法应用一个方法名的能力。名为 Shape 的基类就是一个简单的示例,该类有名为 Circle 和 Square 的两个子类。Shape 类定义了名为 area() 的方法,该方法返回形状的面积。如果已实现多态,则可以对 Circle 和 Square 类型的对象调用 area() 方法,然后执行正确的计算。使用继承能实现多态,实现的方式是允许子类继承和重新定义或“覆盖”基类中的方法。在下面的示例中,由 Circle 和 Square 两个类重新定义了 area() 方法:

class Shape
{
    public function area():Number
    {
        return NaN;
    }
}

class Circle extends Shape
{
    private var radius:Number = 1;
    override public function area():Number
    {
        return (Math.PI * (radius * radius));
    }
}

class Square extends Shape
{
    private var side:Number = 1;
    override public function area():Number
    {
        return (side * side);
    }
}

var cir:Circle = new Circle();
trace(cir.area()); // 输出:3.141592653589793
var sq:Square = new Square();
trace(sq.area()); // 输出:1

因为每个类定义一个数据类型,所以使用继承会在基类和扩展基类的类之间创建一个特殊关系。子类保证拥有其基类的所有属性,这意味着子类的实例总是可以替换基类的实例。例如,如果方法定义了 Shape 类型的参数 (parameter),由于 Circle 扩展了 Shape,因此 Circle 类型的参数 (argument) 是合法的,如下所示:

function draw(shapeToDraw:Shape) {}

var myCircle:Circle = new Circle();
draw(myCircle);

子主题

实例属性和继承 覆盖方法 不继承静态属性 静态属性和作用域链

实例属性和继承

对于实例属性 (property),无论是使用 functionvar 还是使用 const 关键字定义的,只要在基类中未使用 private 属性 (attribute) 声明该属性 (property),这些属性都可以由子类继承。例如,Flash Player API 中的 Event 类具有很多子类,它们继承了所有事件对象共有的属性。

对于某些类型的事件,Event 类包含了定义事件所需的所有属性。这些类型的事件不需要 Event 类中定义的实例属性以外的实例属性。complete 事件和 connect 事件就是这样的事件,前者在成功加载数据时发生,后者在建立网络连接时发生。

下面的示例是从 Event 类中摘录的,显示由子类继承的某些属性和方法。由于继承了属性,因此任何子类的实例都可以访问这些属性。

public class Event
{
    public function get type():String;
    public function get bubbles():Boolean;
    ...

    public function stopPropagation():void {}
    public function stopImmediatePropagation():void {}
    public function preventDefault():void {}
    public function isDefaultPrevented():Boolean {}
    ...
}

其它类型的事件需要 Event 类中没有提供的特有属性。这些事件是使用 Event 类的子类定义的,所以可向 Event 类中定义的属性添加新属性。MouseEvent 类就是这样的一个子类,它可添加与鼠标移动或鼠标单击相关的事件的特有属性,如 mouseMove  click 事件。下面的示例是从 MouseEvent 类中摘录的,它说明了在子类中存在但在基类中不存在的属性的定义:

public class MouseEvent extends Event
{
    public static const CLICK:String      = "click";
    public static const MOUSE_MOVE:String = "mouseMove";
    ...

    public function get stageX():Number {}
    public function get stageY():Number {}
    ...
}

访问控制说明符和继承

如果某一属性是用 public 关键字声明的,则该属性对任何位置的代码可见。这表示 public 关键字与 privateprotected  internal 关键字不同,它对属性继承没有任何限制。

如果属性是使用 private 关键字声明的,该属性只在定义该属性的类中可见,这表示它不能由任何子类继承。此行为与以前版本的 ActionScript 不同,在这些版本中,private 关键字的行为更类似于 ActionScript 3.0 中的 protected 关键字。

protected 关键字指出某一属性不仅在定义该属性的类中可见,而且还在所有子类中可见。与 Java 编程语言中的 protected 关键字不一样,ActionScript 3.0 中的 protected 关键字并不使属性对同一包中的所有其它类可见。在 ActionScript 3.0 中,只有子类可以访问使用 protected 关键字声明的属性。此外,protected 属性对子类可见,不管子类和基类是在同一包中,还是在不同包中。

要限制某一属性在定义该属性的包中的可见性,请使用 internal 关键字或者不使用任何访问控制说明符。未指定访问控制说明符时,应用的默认访问控制说明符是 internal 访问控制说明符。标记为 internal 的属性将只由位于在同一包中的子类继承。

可以使用下面的示例来查看每一个访问控制说明符如何影响跨越包边界的继承。以下代码定义了一个名为 AccessControl 的主应用程序类和两个名为 Base 的 Extender 的其它类。Base 类在名为 foo 的包中,Extender 类是 Base 类的子类,它在名为 bar 的包中。AccessControl 类只导入 Extender 类,并创建一个 Extender 实例,该实例试图访问在 Base 类中定义的名为 str 的变量。str 变量将声明为 public 以使代码能够进行编译和运行,如以下摘录中所示:

// Base.as 位于名为 foo 的文件夹中
package foo
{
    public class Base
    {
        public var str:String = "hello"; // 在该行上更改 public 
    }
}

// Extender.as 位于名为 bar 的文件夹中
package bar
{
    import foo.Base;
    public class Extender extends Base
    {
        public function getString():String {
            return str;
        }
    }
}

// 名为 ProtectedExample.as 文件中的主应用程序类
    import flash.display.MovieClip;
    import bar.Extender;
    public class AccessControl extends MovieClip
    {
        public function AccessControl()
        {
            var myExt:Extender = new Extender();
            trace(myExt.testString);  // 如果 str 不是 public,则发生错误
            trace(myExt.getString()); // 如果 str 是 private 或 internal,则发生错误
        }
    }
}

要查看其它访问控制说明符如何影响先前示例的编译和执行,请在 AccessControl 类中删除或注释掉以下行,然后将 str 变量的访问控制说明符更改为 privateprotected  internal

trace(myExt.testString);  // 如果 str 不是 public,则发生错误

不允许覆盖变量

将继承使用 var  const 关键字声明的属性,但不能对其进行覆盖。覆盖某一属性就表示在子类中重新定义该属性。唯一可覆盖的属性类型是方法,即使用 function 关键字声明的属性。虽然不能覆盖实例变量,但是通过为实例变量创建 getter 和 setter 方法并覆盖这些方法,可实现类似的功能。有关详细信息,请参阅覆盖 getter 和 setter

覆盖方法

覆盖方法表示重新定义已继承方法的行为。静态方法不能继承,也不能覆盖。但是,实例方法可由子类继承,也可覆盖,只要符合以下两个条件:

  • 实例方法在基类中不是使用 final 关键字声明的。当 final 关键字与实例方法一起使用时,该关键字指明程序员的设计目的是要禁止子类覆盖方法。
  • 实例方法在基类中不是使用 private 访问控制说明符声明的。如果某个方法在基类中标记为 private,则在子类中定义同名方法时不需要使用 override 关键字,因为基类方法在子类中不可见。

要覆盖符合这些条件的实例方法,子类中的方法定义必须使用 override 关键字,且必须在以下几个方面与方法的超类版本相匹配:

  • 覆盖方法必须与基类方法具有相同级别的访问控制。标记为内部的方法与没有访问控制说明符的方法具有相同级别的访问控制。
  • 覆盖方法必须与基类方法具有相同的参数数。
  • 覆盖方法参数必须与基类方法参数具有相同的数据类型注释。
  • 覆盖方法必须与基类方法具有相同的返回类型。

但是,覆盖方法中的参数名不必与基类中的参数名相匹配,只要参数数和每个参数的数据类相匹配即可。

super 语句

覆盖方法时,程序员经常希望在要覆盖的超类方法的行为上添加行为,而不是完全替换该行为。这需要通过某种机制来允许子类中的方法调用它本身的超类版本。super 语句就提供了这样一种机制,其中包含对直接超类的引用。下面的示例定义了名为 Base 的类(其中包含名为 thanks() 的方法),还包含名为 Extender 的 Base 类的子类(用于覆盖 thanks() 方法)。Extender.thanks() 方法使用 super 语句调用 Base.thanks()

package {
    import flash.display.MovieClip;
    public class SuperExample extends MovieClip
    {
        public function SuperExample()
        {
            var myExt:Extender = new Extender()
            trace(myExt.thanks()); // 输出:Mahalo nui loa
        }
    }
}

class Base {
    public function thanks():String
    {
        return "Mahalo";
    }
}

class Extender extends Base
{
    override public function thanks():String
    {
        return super.thanks() + " nui loa";
    }
}

覆盖 getter 和 setter

虽然不能覆盖超类中定义的变量,但是可以覆盖 getter 和 setter。例如,以下代码用于覆盖在 Flash Player API 的 MovieClip 类中定义的名为 currentLabel 的 getter:

package
{
    import flash.display.MovieClip;
    public class OverrideExample extends MovieClip
    {
        public function OverrideExample()
        {
            trace(currentLabel)
        }
        override public function get currentLabel():String
        {
            var str:String = "Override: ";
            str += super.currentLabel;
            return str;
        }
    }
}

OverrideExample 类构造函数中的 trace() 语句的输出是 Override: null,这说明该示例能够覆盖继承的 currentLabel 属性。

不继承静态属性

静态属性不由子类继承。这意味着不能通过子类的实例访问静态属性。只能通过定义静态属性的类对象来访问静态属性。例如,以下代码定义了名为 Base 的基类和扩展名为 Extender 的 Base 子类。Base 类中定义了名为 test 的静态变量。以下摘录中编写的代码在严格模式下不会进行编译,在标准模式下会生成运行时错误。

package {
    import flash.display.MovieClip;
    public class StaticExample extends MovieClip
    {
        public function StaticExample()
        {
            var myExt:Extender = new Extender();
            trace(myExt.test);  // 错误
        }
    }
}

class Base {
    public static var test:String = "static";
}

class Extender extends Base { }

访问静态变量 test 的唯一方式是通过类对象,如以下代码所示:

Base.test;

但允许使用与静态属性相同的名称定义实例属性。可以在与静态属性相同的类中或在子类中定义这样的实例属性。例如,以上示例中的 Base 类可以具有一个名为 test 的实例属性。在以下代码中,由于 Extender 类继承了实例属性,因此代码能够进行编译和执行。如果 test 实例变量的定义移到(不是复制)Extender 类,该代码也会编译并执行。

package
{
    import flash.display.MovieClip;
    public class StaticExample extends MovieClip
    {
        public function StaticExample()
        {
            var myExt:Extender = new Extender();
            trace(myExt.test);  // 输出:实例
        }
    }
}

class Base
{
    public static var test:String = "static";
    public var test:String = "instance";
}

class Extender extends Base {}

静态属性和作用域链

虽然并不继承静态属性,但是静态属性在定义它们的类或该类的任何子类的作用域链中。同样,可以认为静态属性在定义它们的类和任何子类的“作用域”中。这意味着在定义静态属性的类体及该类的任何子类中可直接访问静态属性。

下面的示例对上一个示例中定义的类进行了修改,以说明 Base 类中定义的 test 静态变量在 Extender 类的作用域中。换句话说, Extender 类可以访问 test 静态变量,而不必用定义 test 的类名作为变量的前缀。

package
{
    import flash.display.MovieClip;
    public class StaticExample extends MovieClip
    {
        public function StaticExample()
        {
            var myExt:Extender = new Extender();
        }
    }
}

class Base {
    public static var test:String = "static";
}

class Extender extends Base
{
    public function Extender()
    {
        trace(test); // 输出:静态
    }
    
}

如果使用与同类或超类中的静态属性相同的名称定义实例属性,则实例属性在作用域链中的优先级比较高。因此认为实例属性“遮蔽”了静态属性,这意味着会使用实例属性的值,而不使用静态属性的值。例如,以下代码显示如果 Extender 类定义名为 test 的实例变量,trace() 语句将使用实例变量的值,而不使用静态变量的值:

package
{
    import flash.display.MovieClip;
    public class StaticExample extends MovieClip
    {
        public function StaticExample()
        {
            var myExt:Extender = new Extender();
        }
    }
}

class Base
{
    public static var test:String = "static";
}

class Extender extends Base
{
    public var test:String = "instance";
    public function Extender()
    {
        trace(test); // 输出:实例
    }
    
}

 

处理日期和时间

时间可能并不代表一切,但它在软件应用程序中通常是一个关键要素。ActionScript 3.0 提供了多种强大的手段来管理日历日期、时间和时间间隔。以下两个主类提供了大部分的计时功能:Date 类和 flash.utils 包中的新 Timer 类。

目录

日期和时间基础知识 管理日历日期和时间 控制时间间隔 示例:简单的模拟时钟

日期和时间基础知识

处理日期和时间简介

日期和时间是在 ActionScript 程序中使用的一种常见信息类型。例如,您可能需要了解当前星期值,或测量用户在特定屏幕上花费多少时间,并且还可能会执行很多其它操作。在 ActionScript 中,可以使用 Date 类来表示某一时刻,其中包含日期和时间信息。Date 实例中包含各个日期和时间单位的值,其中包括年、月、日、星期、小时、分钟、秒、毫秒以及时区。对于更高级的用法,ActionScript 还包括 Timer 类,您可以使用该类在一定延迟后执行动作,或按重复间隔执行动作。

常见日期和时间任务

本章介绍了以下常见的日期和时间信息处理任务:

  • 处理 Date 对象
  • 获取当前日期和时间
  • 访问各个日期和时间单位(日、年、小时、分钟等)
  • 使用日期和时间执行运算
  • 在时区之间进行转换
  • 执行重复动作
  • 在设定的时间间隔后执行动作

重要概念和术语

以下参考列表包含将会在本章中遇到的重要术语:

  • UTC 时间 (UTC time):通用协调时间,即“零小时”基准时区。所有其它时区均被定义为相对于 UTC 时间快或慢一定的小时数。

完成本章中的示例

学习本章的过程中,您可能想要自己动手测试一些示例代码清单。由于本章中的代码清单主要用于处理 Date 对象,测试示例将涉及查看示例中使用的变量值,方法是:通过将值写入舞台上的文本字段实例,或使用 trace() 函数将值输出到“输出”面板。测试本章内的示例代码清单中对这些技术进行了详细说明。

管理日历日期和时间

ActionScript 3.0 的所有日历日期和时间管理函数都集中在顶级 Date 类中。Date 类包含一些方法和属性,这些方法和属性能够使您按照通用协调时间 (UTC) 或特定于时区的本地时间来处理日期和时间。UTC 是一种标准时间定义,它实质上与格林尼治标准时间 (GMT) 相同。

子主题

创建 Date 对象 获取时间单位值 执行日期和时间运算 在时区之间进行转换

创建 Date 对象

Date 类是所有核心类中构造函数方法形式最为多变的类之一。您可以用以下四种方式来调用 Date 类。

第一,如果未给定参数,则 Date() 构造函数将按照您所在时区的本地时间返回包含当前日期和时间的 Date 对象。下面是一个示例:

var now:Date = new Date();

第二,如果仅给定了一个数字参数,则 Date() 构造函数将其视为自 1970 年 1 月 1 日以来经过的毫秒数,并且返回对应的 Date 对象。请注意,您传入的毫秒值将被视为自 1970 年 1 月 1 日(UTC 时间)以来经过的毫秒数。但是,该 Date 对象会按照您所在的本地时区来显示值,除非您使用特定于 UTC 的方法来检索和显示这些值。如果仅使用一个毫秒参数来创建新的 Date 对象,则应确保考虑到您的当地时间和 UTC 之间的时区差异。以下语句创建一个设置为 1970 年 1 月 1 日午夜(UTC 时间)的 Date 对象:

var millisecondsPerDay:int = 1000 * 60 * 60 * 24;
// 获取一个表示自起始日期 1970 年 1 月 1 日后又过了一天时间的 Date 对象
var startTime:Date = new Date(millisecondsPerDay);

第三,您可以将多个数值参数传递给 Date() 构造函数。该构造函数将这些参数分别视为年、月、日、小时、分钟、秒和毫秒,并将返回一个对应的 Date 对象。假定这些输入参数采用的是本地时间而不是 UTC。以下语句获取一个设置为 2000 年 1 月 1 日开始的午夜(本地时间)的 Date 对象:

var millenium:Date = new Date(2000, 0, 1, 0, 0, 0, 0);

第四,您可以将单个字符串参数传递给 Date() 构造函数。该构造函数将尝试把字符串解析为日期或时间部分,然后返回对应的 Date 对象。如果您使用此方法,最好将 Date() 构造函数包含在 try..catch 块中以捕获任何解析错误。Date() 构造函数接受多种不同的字符串格式,如《ActionScript 3.0 语言和组件参考》中所列。以下语句使用字符串值初始化一个新的 Date 对象:

var nextDay:Date = new Date(“Mon May 1 2006 11:30:00 AM”);

如果 Date() 构造函数无法成功解析该字符串参数,它将不会引发异常。但是,所得到的 Date 对象将包含一个无效的日期值。

获取时间单位值

可以使用 Date 类的属性或方法从 Date 对象中提取各种时间单位的值。下面的每个属性为您提供了 Date 对象中的一个时间单位的值:

  • fullYear 属性
  • month 属性,以数字格式表示,分别以 0 到 11 表示一月到十二月
  • date 属性,表示月中某一天的日历数字,范围从 1 到 31
  • day 属性,以数字格式表示一周中的某一天,其中 0 表示星期日
  • hours 属性,范围从 0 到 23
  • minutes 属性
  • seconds 属性
  • milliseconds 属性

实际上,Date 类为您提供了获取这些值的多种方式。例如,您可以用四种不同方式获取 Date 对象的月份值:

  • month 属性
  • getMonth() 方法
  • monthUTC 属性
  • getMonthUTC() 方法

所有四种方式实质上具有同等的效率,因此您可以任意使用一种最适合应用程序的方法。

刚才列出的属性表示总日期值的各个部分。例如,milliseconds 属性永远不会大于 999,因为当它达到 1000 时,秒钟值就会增加 1 并且 milliseconds 属性会重置为 0。

如果要获得 Date 对象自 1970 年 1 月 1 日 (UTC) 起所经过毫秒数的值,您可以使用 getTime() 方法。通过使用与其相对应的 setTime() 方法,您可以使用自 1970 年 1 月 1 日 (UTC) 起经过的毫秒数更改现有 Date 对象的值。

执行日期和时间运算

您可以使用 Date 类对日期和时间执行加法和减法运算。日期值在内部以毫秒的形式保存,因此您应将其它值转换成毫秒,然后再将它们与 Date 对象进行加减。

如果应用程序将执行大量的日期和时间运算,您可能会发现创建常量来保存常见时间单位值(以毫秒的形式)非常有用,如下所示:

public static const millisecondsPerMinute:int = 1000 * 60;
public static const millisecondsPerHour:int = 1000 * 60 * 60;
public static const millisecondsPerDay:int = 1000 * 60 * 60 * 24;

现在,可以方便地使用标准时间单位来执行日期运算。下列代码使用 getTime()  setTime() 方法将日期值设置为当前时间一个小时后的时间:

var oneHourFromNow:Date = new Date();
oneHourFromNow.setTime(oneHourFromNow.getTime() + millisecondsPerHour);

设置日期值的另一种方式是仅使用一个毫秒参数创建新的 Date 对象。例如,下列代码将一个日期加上 30 天以计算另一个日期:

// 将发票日期设置为今天的日期
var invoiceDate:Date = new Date();

// 加上 30 天以获得到期日期
var dueDate:Date = new Date(invoiceDate.getTime() + (30 * millisecondsPerDay));

接着,将 millisecondsPerDay 常量乘以 30 以表示 30 天的时间,并将得到的结果与 invoiceDate 值相加并将其用于设置 dueDate 值。

在时区之间进行转换

在需要将日期从一种时区转换成另一种时区时,使用日期和时间运算十分方便。也可以使用 getTimezoneOffset() 方法,该方法返回的值表示 Date 对象的时区与 UTC 之间相差的分钟数。此方法之所以返回以分钟为单位的值是因为并不是所有时区之间都正好相差一个小时,有些时区与邻近的时区仅相差半个小时。

以下示例使用时区偏移量将日期从本地时间转换成 UTC。该示例首先以毫秒为单位计算时区值,然后按照该量调整 Date 值:

// 按本地时间创建 Date
var nextDay:Date = new Date("Mon May 1 2006 11:30:00 AM");

// 通过加上或减去时区偏移量,将 Date 转换为 UTC
var offsetMilliseconds:Number = nextDay.getTimezoneOffset() * 60 * 1000;
nextDay.setTime(nextDay.getTime() + offsetMilliseconds);

控制时间间隔

使用 Adobe Flash CS3 Professional 开发应用程序时,您可以访问时间轴,这会使您稳定且逐帧地完成该应用程序。但在纯 ActionScript 项目中,您必须依靠其它计时机制。

子主题

循环与计时器之比较 Timer 类 flash.utils 包中的计时函数

循环与计时器之比较

在某些编程语言中,您必须使用循环语句(如 fordo..while)来设计您自己的计时方案。

通常,循环语句会以本地计算机所允许的速度尽可能快地执行,这表明应用程序在某些计算机上的运行速度较快而在其它计算机上则较慢。如果应用程序需要一致的计时间隔,则您需要将其与实际的日历或时钟时间联系在一起。许多应用程序(如游戏、动画和实时控制器)需要在不同计算机上均能保持一致的、规则的时间驱动计时机制。

ActionScript 3.0 的 Timer 类提供了一个功能强大的解决方案。使用 ActionScript 3.0 事件模型,Timer 类在每次达到指定的时间间隔时都会调度计时器事件。

Timer 类

在 ActionScript 3.0 中处理计时函数的首选方式是使用 Timer 类 (flash.utils.Timer),可以使用它在每次达到间隔时调度事件。

要启动计时器,请先创建 Timer 类的实例,并告诉它每隔多长时间生成一次计时器事件以及在停止前生成多少次事件。

例如,下列代码创建一个每秒调度一个事件且持续 60 秒的 Timer 实例:

var oneMinuteTimer:Timer = new Timer(1000, 60);

Timer 对象在每次达到指定的间隔时都会调度 TimerEvent 对象。TimerEvent 对象的事件类型是 timer(由常量 TimerEvent.TIMER 定义)。TimerEvent 对象包含的属性与标准 Event 对象包含的属性相同。

如果将 Timer 实例设置为固定的间隔数,则在达到最后一次间隔时,它还会调度 timerComplete 事件(由常量 TimerEvent.TIMER_COMPLETE 定义)。

以下是一个用来展示 Timer 类实际操作的小示例应用程序:

package 
{
    import flash.display.Sprite;
    import flash.events.TimerEvent;
    import flash.utils.Timer;

    public class ShortTimer extends Sprite
    {
        public function ShortTimer() 
        {
            // 创建一个新的五秒的 Timer
            var minuteTimer:Timer = new Timer(1000, 5);
            
            // 为间隔和完成事件指定侦听器
            minuteTimer.addEventListener(TimerEvent.TIMER, onTick);
            minuteTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete);
            
            // 启动计时器计时
            minuteTimer.start();
        }

        public function onTick(event:TimerEvent):void 
        {
            // 显示到目前为止的时间计数
            // 该事件的目标是 Timer 实例本身。
            trace("tick" + event.target.currentCount);
        }

        public function onTimerComplete(event:TimerEvent):void
        {
            trace("Time's Up!");
        }
    }
}

创建 ShortTimer 类时,它会创建一个用于每秒计时一次并持续五秒的 Timer 实例。然后,它将两个侦听器添加到计时器:一个用于侦听每次计时,另一个用于侦听 timerComplete 事件。

接着,它启动计数器计时,并且从此时起以一秒钟的间隔执行 onTick() 方法。

onTick() 方法只显示当前的时间计数。五秒钟后,执行 onTimerComplete() 方法,告诉您时间已到。

运行该示例时,您应会看到下列行以每秒一行的速度显示在控制台或跟踪窗口中:

tick 1
tick 2
tick 3
tick 4
tick 5
Time's Up!

flash.utils 包中的计时函数

ActionScript 3.0 包含许多与 ActionScript 2.0 提供的计时函数类似的计时函数。这些函数是作为 flash.utils 包中的包级别函数提供的,这些函数的工作方式与 ActionScript 2.0 完全相同。

函数

说明

clearInterval(id:uint):void

取消指定的 setInterval() 调用。

clearTimeout(id:uint):void

取消指定的 setTimeout() 调用。

getTimer():int

返回自 Adobe Flash Player 被初始化以来经过的毫秒数。

setInterval(closure:Function, delay:Number, ... arguments):uint

以指定的间隔(以毫秒为单位)运行函数。

setTimeout(closure:Function, delay:Number, ... arguments):uint

在指定的延迟(以毫秒为单位)后运行指定的函数。

这些函数仍保留在 ActionScript 3.0 以实现向后兼容。Adobe 不建议您在新的 ActionScript 3.0 应用程序中使用这些函数。通常,在应用程序中使用 Timer 类会更容易且更有效。

示例:简单的模拟时钟

简单的模拟时钟示例说明了在本章中讨论的以下两个日期和时间概念:

  • 获取当前日期和时间并提取小时、分钟和秒的值
  • 使用 Timer 设置应用程序的运行速度

要获取该范例的应用程序文件,请访问 www.adobe.com/go/learn_programmingAS3samples_flash_cn。可以在 Samples/SimpleClock 文件夹中找到 SimpleClock 应用程序文件。该应用程序包含以下文件:

文件

描述

SimpleClockApp.mxml

SimpleClockApp.fla

Flash 或 Flex 中的主应用程序文件(分别为 FLA 和 MXML)。

com/example/programmingas3/simpleclock/SimpleClock.as

主应用程序文件。

com/example/programmingas3/simpleclock/AnalogClockFace.as

根据时间绘制一个圆形的钟面以及时针、分针和秒针。

子主题

定义 SimpleClock 类 创建钟面 启动计时器 显示当前时间

定义 SimpleClock 类

此时钟示例很简单,但是将即使很简单的应用程序也组织得十分有条理是一种很好的做法,以便您将来能够很轻松地扩展这些应用程序。为此,SimpleClock 应用程序使用 SimpleClock 类处理启动和时间保持任务,然后使用另一个名称为 AnalogClockFace 的类来实际显示该时间。

以下代码用于定义和初始化 SimpleClock 类(请注意,在 Flash 版本中,SimpleClock 扩展了 Sprite 类):

public class SimpleClock extends UIComponent
{
    /**
     * 时间显示组件。
     */
    private var face:AnalogClockFace;
    
    /**
     * Timer 的作用就像是应用程序的心跳。
     */
    private var ticker:Timer;

该类具有两个重要的属性:

  • face 属性,它是 AnalogClockFace 类的实例
  • ticker 属性,它是 Timer 类的实例

SimpleClock 类使用默认构造函数。initClock() 方法处理实际的设置工作,它创建钟面并启动 Timer 实例的计时。

创建钟面

SimpleClock 代码中后面的几行代码创建用于显示时间的钟面:

/**
     * 设置 SimpleClock 实例。
     */
    public function initClock(faceSize:Number = 200) 
    {
        // 创建钟面并将其添加到显示列表中
        face = new AnalogClockFace(Math.max(20, faceSize));
        face.init();
        addChild(face);
        
        // 绘制初始时钟显示
        face.draw();

可以将钟面的大小传递给 initClock() 方法。如果未传递 faceSize 值,则使用 200 个像素的默认大小。

接着,应用程序将钟面初始化,然后使用从 DisplayObject 类继承的 addChild() 方法将该钟面添加到显示列表。然后,它调用 AnalogClockFace.draw() 方法显示一次钟面,同时显示当前时间。

启动计时器

创建钟面后,initClock() 方法会设置一个计时器:

// 创建用来每秒触发一次事件的 Timer
        ticker = new Timer(1000); 
    
        // 指定 onTick() 方法来处理 Timer 事件
        ticker.addEventListener(TimerEvent.TIMER, onTick);

        // 启动时钟计时
        ticker.start();

首先,该方法初始化一个每秒(每隔 1000 毫秒)调度一次事件的 Timer 实例。由于没有向 Timer() 构造函数传递第二个 repeatCount 参数,因此 Timer 将无限期地重复计时。

SimpleClock.onTick() 方法将在每秒收到 timer 事件时执行一次。

public function onTick(event:TimerEvent):void 
    {
        // 更新时钟显示
        face.draw();
    }

AnalogClockFace.draw() 方法仅绘制钟面和指针。

显示当前时间

AnalogClockFace 类中大多数代码都与设置钟面的显示元素有关。在对 AnalogClockFace 进行初始化时,它会绘制一个圆形轮廓,将数字文本标签放在每个小时刻度处,然后创建三个 Shape 对象,分别表示时钟的时针、分针和秒针。

在 SimpleClock 应用程序运行后,它会每秒调用一次 AnalogClockFace.draw() 方法,如下所示:

/**
     * 在绘制时钟显示时由父容器进行调用。
     */
    public override function draw():void
    {
        // 将当前日期和时间存储在实例变量中
        currentTime = new Date();
        showTime(currentTime);
    }

此方法将当前时间保存在变量中,因此在绘制时钟指针的过程中无法改变时间。然后,调用 showTime() 方法以显示指针,如下所示:

/**
     * 以看起来不错的老式模拟时钟样式显示指定的 Date/Time。
     */
    public function showTime(time:Date):void 
    {
        // 获取时间值
        var seconds:uint = time.getSeconds();
        var minutes:uint = time.getMinutes();
        var hours:uint = time.getHours();

        // 乘以 6 得到度数
        this.secondHand.rotation = 180 + (seconds * 6);
        this.minuteHand.rotation = 180 + (minutes * 6);

        // 乘以 30 得到基本度数,然后
        // 最多加上 29.5 度 (59 * 0.5)
        // 以计算分钟。
        this.hourHand.rotation = 180 + (hours * 30) + (minutes * 0.5);
    }

首先,此方法提取当前时间的小时、分钟和秒的值。然后使用这些值来计算每个指针的角度。由于秒针会在 60 秒内旋转一圈,因此它每秒都会旋转 6 度 (360/60)。分针每分钟都旋转同样的度数。

时针每分钟都在更新,因此时针能够随着分针的跳动显示出某些时间变化过程。时针每小时旋转 30 度 (360/12),但也会每分钟旋转半度(30 度除以 60 分钟)。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在前两篇中,我们介绍了 React Router 和路由的基础知识,以及如何在应用中使用路由。在本篇中,我们将深入学习 React Router,并介绍一些更高级的用法。 ## 动态路由 在前面的教程中,我们已经学习了如何定义静态路由。但是在实际开发中,我们通常需要处理动态路由。例如,我们可能需要在 URL 中传递参数,以便根据参数来渲染不同的组件。 在 React Router 中,我们可以使用 `:param` 来定义动态路由参数。例如,我们可以定义一个动态路由 `/user/:id`,其中 `:id` 表示一个动态参数,表示用户的 ID。当浏览器访问 `/user/123` 时,React Router 会自动将 `123` 作为参数传递给对应的组件。 下面是一个简单的例子: ```jsx import { BrowserRouter as Router, Route, Link } from "react-router-dom"; function User({ match }) { return <h1>Hello, {match.params.id}!</h1>; } function App() { return ( <Router> <div> <ul> <li> <Link to="/user/123">User 123</Link> </li> <li> <Link to="/user/456">User 456</Link> </li> </ul> <Route path="/user/:id" component={User} /> </div> </Router> ); } ``` 在上面的例子中,我们定义了一个动态路由 `/user/:id`,其中 `:id` 表示用户的 ID。当浏览器访问 `/user/123` 时,React Router 会自动将 `123` 作为参数传递给 `User` 组件,并显示 `Hello, 123!`。 同样,当浏览器访问 `/user/456` 时,React Router 会自动将 `456` 作为参数传递给 `User` 组件,并显示 `Hello, 456!`。 ## 嵌套路由 在实际开发中,我们经常需要在一个页面中嵌套多个组件。在 React Router 中,我们可以使用嵌套路由来实现这个功能。 具体来说,我们可以在一个组件中定义多个 `<Route>` 组件,从而实现嵌套路由。例如,我们可以定义一个嵌套路由 `/user/:id/posts`,其中 `:id` 表示用户的 ID,`posts` 表示用户的帖子列表。 下面是一个简单的例子: ```jsx import { BrowserRouter as Router, Route, Link } from "react-router-dom"; function User({ match }) { return ( <div> <h1>Hello, {match.params.id}!</h1> <ul> <li> <Link to={`${match.url}/posts`}>Posts</Link> </li> </ul> <Route path={`${match.path}/posts`} component={Posts} /> </div> ); } function Posts() { return <h2>Posts</h2>; } function App() { return ( <Router> <div> <ul> <li> <Link to="/user/123">User 123</Link> </li> <li> <Link to="/user/456">User 456</Link> </li> </ul> <Route path="/user/:id" component={User} /> </div> </Router> ); } ``` 在上面的例子中,我们定义了一个嵌套路由 `/user/:id/posts`,其中 `:id` 表示用户的 ID,`posts` 表示用户的帖子列表。当浏览器访问 `/user/123/posts` 时,React Router 会自动将 `123` 作为参数传递给 `User` 组件,并显示 `Posts` 组件。 ## 路由守卫 在实际开发中,我们通常需要实现一些路由守卫功能,例如登录验证、权限控制等。在 React Router 中,我们可以使用 `Route` 组件的 `render` 属性来实现路由守卫。 具体来说,我们可以定义一个高阶组件 `AuthRoute`,用来检查用户是否登录。如果用户已登录,则渲染对应的组件;否则,跳转到登录页面。 下面是一个简单的例子: ```jsx import { BrowserRouter as Router, Route, Link, Redirect } from "react-router-dom"; function AuthRoute({ component: Component, ...rest }) { const isAuthenticated = localStorage.getItem("isAuthenticated"); return ( <Route {...rest} render={(props) => isAuthenticated ? ( <Component {...props} /> ) : ( <Redirect to={{ pathname: "/login", state: { from: props.location } }} /> ) } /> ); } function Home() { return <h1>Welcome Home!</h1>; } function Login() { const login = () => { localStorage.setItem("isAuthenticated", true); }; return ( <div> <h2>Login</h2> <button onClick={login}>Login</button> </div> ); } function App() { return ( <Router> <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/login">Login</Link> </li> </ul> <AuthRoute exact path="/" component={Home} /> <Route path="/login" component={Login} /> </div> </Router> ); } ``` 在上面的例子中,我们定义了一个高阶组件 `AuthRoute`,用来检查用户是否登录。如果用户已登录,则渲染对应的组件;否则,跳转到登录页面。具体来说,我们首先从本地存储中检查用户是否已登录,然后根据检查结果来渲染对应的组件或跳转到登录页面。 ## 总结 在本篇教程中,我们深入学习了 React Router,并介绍了一些更高级的用法。具体来说,我们学习了如何定义动态路由、嵌套路由和路由守卫。 希望通过本篇教程,你已经掌握了 React Router 的高级用法,并能够在实际开发中灵活使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值