[转]ActionScript2.0编程技术教程(第二章)

来源:互联网 发布:淘宝不锈钢高速钢锯片 编辑:程序博客网 时间:2024/05/17 03:04

  2.4 路径

  在一个学校中有校长、老师、学生,老师中又有语文老师、数学老师等。校长和老师之间有信息交流,老师和学生也有信息交流等。

  一个 Flash 影片好比是一个学校,可以由多个影片剪辑组成,这些影片剪辑中,可以有自己的时间轴,有自己的变量等。影片剪辑之间是怎样进行交换信息的呢?这与 Flash 中的路径有关。

  2.4.1 绝对路径

  如果校长要找学生,可以这样进行,校长先找到下一级的老师,老师再找下一级的学生,表示如下:

  校长 . 老师 . 学生

  主场景( _root )好比是校长,主场景中的影片剪辑( mcA )好比是老师,影片剪辑( mcA )下的影片剪辑( mc1 )好比是学生,如果要在主场景中访问影片剪辑 mc1 ,用如下方式:

_root.mcA.mc1; 

  在 Flash 影片中从起点(如从主场景开始)调用变量或影片剪辑,这样的语法称为绝对路径。它简单、容易理解,但移植性差。

  范例 2-13 :使用绝对路径

  ( 1 )打开 Flash MX 2004 ,新建一个 Flash 文档。

  当建立一个新文件时,出现在面前的场景即主场景,用 _root 表示。

  ( 2 )在主场景中建立一个实例名为“ mcA ”的影片剪辑实例,双击 mcA 进入元件的编辑场景,建立一个实例名为“ mc1 ”的影片剪辑实例。在【时间轴】的上方可以显示出影片剪辑实例的顺序。如图 2.18 所示。

图 2.18 影片剪辑实例的顺序

  ( 3 )返回主场景,新建一层,单击此层的第 1 帧,打开【动作】面板,输入下列代码:
trace(_root.mcA._width);
// 输出影片剪辑实例 mcA 的宽度
trace(_root.mcA.mc1._width);
// 输出影片剪辑 mc1 的宽度
  ( 4 )测试影片,观看【输出】面板中输出的数据。
  ( 5 )返回主场景,把主场景第 1 帧中的代码中改为:

trace(mcA._width); 
trace(mcA.mc1._width); 

  测试影片,结果是一样的。

  从测试结果可看出,在主场景中使用绝对路径,可省略 _root 。

  ( 6 )返回主场景,把第 1 帧中的代码加上注释,在主场景中加入一个按钮,单击按钮,打开【动作】面板,输入下列代码:

on (release) { 
trace(_root.mcA._width); 
trace(_root.mcA.mc1._width); 

  测试影片,单击按钮,观看测试结果。
  ( 7 )返回主场景中,把按钮中的代码改为:

on (release) { 
trace(mcA._width); 
trace(mcA.mc1._width); 

  测试影片,单击按钮,观看测试结果,测试结果不变。

  从测试结果可看出,主场景中的按钮上的代码可以看成是在主场景中的时间轴上执行,所以可以不加 _root 。

  ( 8 )返回主场景,把按钮中的代码加上注释,双击场景中的影片剪辑实例,在第 1 帧上加上下列代码:

trace(_root.mcA._width); 
trace(_root.mcA.mc1._width); 

  测试影片,结果与前面相同。

  ( 9 )关闭测试窗口,把第 1 帧上的代码改为:

trace(mcA._width); 
trace(mcA.mc1._width); 

  测试影片,结果错误。

  从测试结果可看出,影片剪辑有自己的时间轴,它的代码是在自己的时间轴上执行,所以必须加路径 _root 。

  源文件见“ 2-13.fla ”(文件路径:配套光盘 / 源文件 /part2 /2-13.fla )。

  试一试: 在本例的基础上,做如下修改:

  Flash 文件可以由多个影片组成,一个影片可从外部动态的导入到另一个影片的影片剪辑中,比如,影片“ 1.swf ”被导入到影片“ main.swf ”中的影片剪辑实例 mc 中,如果在影片“ 1.swf ”中的程序中有 _root ,导入到 mc 后,不再是主场景,所以路径改变,这时“ 1.swf ”的中的代码就不能正确的执行,这时,就要使用相对路径来解决这个问题。

  相对路径是以自己所处的起点去访问其它的变量或影片剪辑。如在范例 2-13 中,以 mc1 为起点访问上一级的影片剪辑 mcA 就属于相对路径,这里的上一级用 _patent 表示。因为 mcA 是 mc1 的上一级,在 mc1 的时间轴上加上:
_parent._x ;
  意思是访问 mc1 的上一级 mcA 的 x 坐标,转换为绝对路径即:
_root.mcA._x;
  使用相对路径有两层意思,一是以自己为起点向上访问,需要用 _parent 。二是以自己为起点向下访问,只需用点运算符。

  范例 2-14 :使用相对路径访问变量

  ( 1 )打开 Flash MX 2004 ,新建一个 Flash 文档。

  ( 2 )建立如图 2.20 所示影片剪辑实例及其结构,图 2.20 中的名称代表它们的实例名。

图 2.20 影片剪辑的关系

  ( 3 )单击主场景的第 1 帧,打开【动作】面板,输入下列代码:

var root_var = "_root"; 

// 在主场景中定义一个字符串变量,代表主场景
  ( 4 )在四个影片剪辑实例的时间轴的第 1 帧上分别定义一个变量,如下所示:
  在 mcB 中:

var mcB_var = "mcB"; 

  在 mc2 中:

var mc2_var = "mc2"; 

  在 mcA 中:

var mcA_var = "mcA"; 

  在 mc1 中:

var mc1_var = "mc1"; 

  ( 5 )在 mcB 的时间轴上的第 1 帧上输入下列代码:

trace(_parent.root_var); 

  ( 6 )测试影片,输出 _root 。
  在这个范例中, _root 是 mcA 的上一级,即 _root 是 mcA 的父级,要在 mcA 的时间轴上访问 _root 中的变量 root_var ,只需向上访问一级,表示如下:

_parent.root_var; 

  而 mcA 又是 mc1 的父级,要在 mc1 的时间轴上访问 _root 中的变量 root_var ,需向上访问两级,表示如下:

_parent._parent.root_var; 

mcB 是 _root 的下一级,即 mcB 是 _root 的子级。要在 _root 的时间轴中访问 mcB 的变量 mcB_var ,只需用点运算符,向下访问一级,表示如下:

mcB._mcB_var; 

  同样的,要在 mcB 的时间轴上访问 mc2 中的变量,表示如下:

mc2._mc2_var; 

  要在 _root 的时间轴上访问 mc2 中的变量,要向下访问二级,表示如下:

mcB.mc2._mc2_var; 

要在 mc2 的时间轴中访问 mc1 中的变量 mc1_var ,先向上访问二级,再向下访问二级,表示如下:

_parent._parent.mcA.mc1.mc1_var; 

  源文件见“ 2-14.fla ”(文件路径:配套光盘 / 源文件 /part2 /2-14.fla )。
  试一试: 在本例的基础上,做如下修改:
  ( 1 )在 mcA 中访问 mc2 中的变量。
  ( 2 )在 mc2 中访问 mcA 中的变量。
  分别用 trace() 语句输出。

  注意: 在定义主场景中按钮的动作脚本时,访问主场景中的变量不用加路径。

  通过【动作】面板的【插入目标路径】按钮可把实例的路径自动添加到【动作】面板中,这给编程带来了很多方便。

  打开源文件“ 2-14.fla ”,单击第 1 帧,按 F9 打开【动作】面板,单击【动作】面板上的【插入目标路径】按钮 ,弹出【插入目标路径】对话框,如图 2.21 所示。

图 2.21 【插入目标路径】对话框

  利用【插入目标路径】对话框可添加实例的相对路径和绝对路径。单击【插入目标路径】对话框中【绝对】单选按钮,再单击要插入目标路径的实例名,即可显示出相关实例的绝对路径,单击【确定】按钮可把路径添加到动作面板中。如图 2.22 所示。

图 2.22 实例的绝对路径

  双击主场景中的实例 mcB ,单击时间轴的第 1 帧,打开【动作】面板上的【插入目标路径】对话框, 单击【相对】单选按钮,再单击要插入目标路径的实例名,可显示出相关实例的相对路径。
另外,要获取相对路径还可用上一节的方法:先获得绝对路径,理清楚各个影片剪辑实例的层次关系,再决定向上还是向下访问。

  2.4.3 动态路径

  影片剪辑一般通过手工创建,通过【属性】面板可以设定影片剪辑实例的实例名,通过实例名和路径就可以访问不同层次的影片剪辑实例。但有些影片剪辑的实例是通过程序来创建的,例如通过复制函数可以复制出若干个新的电影剪辑实例,它们的实例名也是在程序中指定的。要访问这些复制出来的影片剪辑实例,最好的方法是用动态路径。

  动态路径是利用数组运算符来实现的,在范例 2-14 中可以使用绝对路径访问 mcA :
_root.mcA
  如果用动态路径可以这样表示:
_root[“mc”+”A”]
  注意: 动态路径使用数组运算符“ [ ] ”实现,路径和实例名间没有点运算符 。

  使用动态路径的好处是可以批量实现路径。假如主场景中有影片剪辑实例 mc1 、 mc2 、 … 、 mc100 ,假如用绝对路径,要写 100 行程序,用动态路径就非常方便,因为这些实例名有一定的规律,开头两个字母是一样是,都是 mc ,后面的数字可用一个变量 n 来表示,因为变量是可以变化的,表示如下:
_root[“mc”+n]
  Flash 中的关键字 this 代表自己,如果不用 _root ,可用 this 来指定,如:
this[“mc”+n]
  使用 this 的前提是不使用其它路径,就是指当前路径,这里指的是主场景。
  使用动态路径时也可以用相对路径,如:
_parent[“mc”+n]
  如果有多重路径,要访问主场景下的 mcA 下的 mc1 到 mc100 ,可以这样表示:
_root.mcA[“mc”+n]
  范例 2-15 :使用动态路径批量更改影片剪辑的属性
  ( 1 )打开 Flash MX 2004 ,新建一个 Flash 文档。
  ( 2 )在主场景的【图层 1 】上建立五个影片剪辑,实例名分别为 mc1 、 mc2 、 mc3 、 mc4 、 mc5 。
  ( 3 )新建一层,在此层第 1 帧上输入下列代码:

var n = 1; 
// 定义变量 n ,初始值为 1 

  ( 4 )在第 2 帧插入空白关键帧,输入下列代码:

_root["mc"+n]._visible = false; 
// 设置影片剪辑实例为不可见 

  ( 5 )在第 3 帧插入空白关键帧,输入下列代码:

n += 1; 
// 变量 n 加 1 
gotoAndPlay(2); 
// 跳到第 2 帧并播放 

  ( 6 )测试影片,可以看到全部的影片剪辑实例都变化不可见。

  源文件见“ 2-15.fla ”(文件路径:配套光盘 /part2/ 源文件 /2-15.fla )。

  分析 : 此范例中,在第 1 帧中定义一个初始值为 1 的变量 n ,当运行到第 2 帧时,因为 n=1 , _root["mc"+n]._visible = false 的作用是相当于 _root.mc1._visible=false ,所以 mc1 变为不可见。运行第 3 帧, n 加 1 ,变为 2 ,然后跳到第 2 帧,使 mc2 变为不可见。由于第 2 和 3 帧不断循环, n 不断加 1 ,使场景上影片剪辑实例一个个变为不可见。
试一试: :把第 2 帧上代码改为:

this["mc"+n]._visible = false; 

  测试影片看看效果。
  动态路径在实际编程中的运用非常广泛,它大大提高了程序的编写效率,有关动态路径的更多应用将在后面的章节中讲解。

  PII 应用程序开发
  翻译:zjs35
  zjs35.5dblog.com

  这部分内容将教你怎样用ActionScript 2.0构建完整的应用程序,你将学会架起和建立一个OO对象的最好实践,并学会UI组件和MC怎样适合一个有好结构的flash应用程序,你还将学会怎样和其它开发者共享代码和使用其它人开发的代码库。这此都将帮助你建立一个可升级的、扩展的、稳定的应用程序。

  第11章    一个纯OOP的应用程序框架
Flash is notoriously open-ended,If there are several of ways to skin a cat, there are even more ways to build a Flash application. (大致意思是flash建立应用程序的方法太多了)flash的弹性能让开发者混淆,特别是它们建立第一个应用程序时。通过提供一个清晰的怎样构建一个OOP的应用程序的例子,达到解决这种混淆的目标。这里介绍的这个例子决不是创建flash应用程序的唯一方法,但它对确是合理的、可复用的能为任何OOP工程打好基础的方法。我们先在抽象层次上考虑这个例子,此时并没有涉及任何特殊的应用。,我们的框架能够应用到从一个email应用程序到一个计算机游戏的任何东西。在下一章,我们将把我们的普通框架应用到一个真实的案例里――货币转换应用程序。
本章描述的应用程序的框架源自Java思想,更确切的说,是一个微软的VB风格。那也就是说,这个应用程序将是一个纯粹的OOP应用程序,所有的东西都在类里面,而fla文件只是用来加载类和提供组件联接。并不是每个flash应用程序都是纯粹OOP的。Flash也充分支持drag-and-drop(拖放)的可视的开发。可视开发的操作包括在创作时通过面板手工的把组件放在场景中、设置组件的属性和数据绑定,这些操作超出这本书的范围。(下面的部分内容教你怎样使用帮助和google查看信息,还有源文件下载。skip)
11.1 基本的目录结构
在我们开始创作之前,先建立应用程序的目录结构。
(步骤略,另和我说不会,哈哈)
创建目录结构如下,随便放在硬盘的哪个位置:
AppName/deploy/source
在我们的例子中,我们有一些外部的素材如文本,声音、图片等,如果这些素材很多,们应该建立一个AppName/assets目录来存放这些素材。
11.2 flash文档文件
每个flash应用程序至少包含一个fla文件,fla文件是用来导出影片(swf文件)的源文件,影片实际上是在播放器中展示内容的。许多应用程序都含有多个在运行时单个加载的swf。例如,在一个游戏中,一个swf可能代表某一级别的游戏,在一个站点中,一个swf可能代表如“联系我们”这部分内容。在一个含有多个swf的应用中,某个swf只是一个一个用来显示进度条的容器等等(moock可真会写)。
我们的基本的应用程序只包含一个swf文件,因此,只需一个fla文件。然而,我们的应用程序框架不排除其它swf的使用,这些swf可能是站点的某部分,一个表单或测验的某个场景,一个游戏的不同级别部分。
下面创建我们的应用程序的主要fla文件:
建立一个flash文档文件,存放在AppName/source目录中,文件名为AppName.fla。
下面让我们来创建应用程序的类文件。
11.3    类
由于我们建立的是一个纯粹OOP的应用程序,因此所有的内容都在类文件中创建。在flash中,大部分应用程序都是包括可视的用户界面图形,因此,一个典型的OOP应用程序中,由类文件创建和管理UI组件。但在我们的例子里,只有两个类。现在,把我们的焦点只放在框架的构建上,所以既不用类创建视觉元素,也不用类完成特定的实际任务。为了强调框架的一般性和自然性,我们把两个类命名为A和B。我们来看怎样创建组件和管理基于现在的框架的结构用户界面。
一个OOP应用程序可能有许多个类,但只有一个用来开始应用程序,在我们的例子中,类A用来开始应用程序,类A有一个通常命名为main()的方法,按照习惯,这个方法包含应用程序的开始代码。在真实的应用程序中,类A将被含一个用来完成开始任务的main()方法的类代替。例如,在一个测验应用程序中,主要的类可能是Quiz,main( )方法用来初始化测验和显示第一个问题。在一个聊天应用程序中,首要的类可能是ChatClient,main()方法可能用来连接服务器。在上面的两个例子中,应用程序的接下去的部分是从main()方法执行之后开始的。例如,第1个问题答案的出现可能使第2个问题出现,或者服务器的成功连接使聊天界面出现。
基于对我们的应用程序的框架的考虑,我们认为类A的开始任务是创建类B的一个实例。
注:类方法main()的使用是严格按照java方法的,在flash中,main()的使用是可以选择的。在java中,由于语言的需要,main()方法是必须的,main()方法会被自动调用。但在flash中,你必须手工调用main()方法,时间轴的第1帧要跟在应用程序预载之后。
我们将把类保存在包com.somedomain中,在你的应用程序中,你应让包com.somedomain与你的站点的蜮名相匹配。例如,我的站点的蜮名是moock.org,我创建的所有类都在包moock.org之内。
创建包:
1、    在AppName/source目录下建立一个名为com的文件夹
2、在AppName/source/com目录下,建立一个名为somedomain的子文件夹
创建类A:
1、建立一个动作脚本文件
2、输入下列代码:

import com.somedomain.B; 
class A { 
    private static var bInstance:B; 
    public function A() { 
        // 在这个例子中,类A的构造函数不使用 
    } 
    public static function main():Void { 
        trace("Starting application."); 
         bInstance = new B( ); 
    } 

3、保存到AppName/source/com/somedomain,文件名A.as
创建类B:

class com.somedomain.B { 
    public function B() { 
        trace("类B的一个实例被创建"); 
    } 

在我们的框架中,请注意,我们把类保存在AppName/source目录中,这是合理的,但这不是一定要这样做,理论上讲,类能保存在任何地方,只要这个目录能被添加到全局或文档类路径,些目录中的类就能在时间轴上或其它类中使用。
11.4 时间轴
现在我们已经创建两个类和一个flash文档,让我们看看AppName.fla怎样加载类和调用类方法A.main()来开始我们的应用程序。
在我们的应用程序框架中,将使用AppName.fla来创建基本的应用程序状态:loading 和 main,每种状态都是在相应的有标签的帧通过暂停播放头来实现,在帧标签loading中,当类加载时显示一个加载信息,在帧标签main中,调用A.main( )方法开始应用程序。
注:在应用程序中使用帧标签作为程序的状态是一个通常的做法(不管是OOP还是没有OOP),虽然这种用法是非常普遍的,但对于熟悉其它语言的程序员来说,这种做法很不适应,也很不熟悉。为了解决这个情况,flash引入了基于屏幕的表单应用程序,关于屏幕类更多的信息请看….
要加载类A和B,需要这些步骤:
1、在帧中为类指定输出帧
2、在AppName.fla的时间轴上添加帧标签loading 和 main
3、当影片加载时,添加代码显示加载信息
这些步骤的详细方法将在下面列出来:
1、打开AppName.fla文件
2、选择文件――发布设置
3、在发布设置对话框中,选择flash标签,单击设置按钮,在弹出的对话框中,在导出用于类的帧的文本框中输入10,这个10是任意的,但这个指定的帧须在显示加载信息的代码之后(也就是预载之后)。
4、单击两次确定按钮完成操作
在AppName.fla的时间轴上添加帧标签,
1、修改图层1的名称为scripts,我们将把代码放在这一层中,做为一个好的习惯,你应该把所有的代码放在scripts图层中,在时间轴中,这个图层位于最上层,它只包含代码,不含mc或其它内容,一些开发者用actions代替scripts,但目的是一样的。
2、选择scripts图层的第15帧
3、播放一关键帧
4、插入一个新的图层,
5、把新图层命名为labels.
6、在labels图层的第4和15帧,插入一关键帧,就像scipts图层只包含代码一样,labels图层专门用于设置帧标签,用帧标签代替帧数目指定某个帧是非常便利的、友好的。
7、在labels图层的第4帧,加入标签loading
8、在labels图层的第15帧,加入标签main
添加代码用来显示加载信息:
1、在scripts图层的第5帧插入一关键帧
2、在scripts图层的第5帧,输入下列代码:

if (_framesloaded == _totalframes) { 
    gotoAndStop("main"); 
} else { 
    gotoAndPlay("loading"); 

3、在scripts图层的第1帧,输入下列代码:

this.createTextField("loadmsg_txt", 0, 200, 200, 0, 0); 
loadmsg_txt.autoSize = true; 
loadmsg_txt.text = "Loading...Please wait."; 

4、在scripts图层的第15帧,输入下列代码:

loadmsg_txt.removeTextField( ); 

我们现在已经提供了基本的时间轴结构,接下去调用A.main()方法来开始应用程序,我们将在帧标签main处来完成它,下面是此帧的代码:

loadmsg_txt.removeTextField(); 
import com.somedomain.A; 
A.main(); 

在理论上讲,这是我们对AppName.fla的最后一次改动,除非我们要增加组件、声音或传统的图形。应用程序的所有代码都在通过A.main()方法直接引用或间接引用类中,在AppName.fla中将不放置任何代码。
我们的应用程序完成了,现在我们测试应用程序是否能正常运行,要进行测试,我们要输出一个swf文件,然后在flash播放器中运行。
11.5 输出flash影片
指定一个目录用来输出AppName.swf文件:
1、在AppName.fla打开的状态下,选择文件――发布设置――格式
2、在文件下面,输入:../deploy/AppName.swf.
3、单击确定按钮
4、为了在测试模式下测试我们的应用程序,选择控制――测试影片,实际上测试影片就是在AppName/deploy目录创建一个swf文件和立即把它加载进播放器的调试器。
对于我们的应用程序,我们将输出flash7格式,如果你想别人使用其它版本的播放器,你也可以输入其它版本的格式
如果你的应用程序能正常运行,在输出面板中将看到下列信息:
Starting application.
An instance of class B was constructed.
如果上述的信息没有出现,可尝试比较你的源文件和本书的源文件
发布html文件:略
在所有支持的flash播放器版本、所有的网页浏览器和所有的平台中测试是不个好想法,如果你等到所有项目完成之后才在各种各样的浏览器中测试,你可能会发现严重的问题,如果它们被尽早发现,修复起来也是容易的。
11.6 flash中的项目
为了在大的应用程序中管理文件,flash支持项目的概念,一个项目是一群有关系的文件,这些文件能通过项目面板进行管理,一个项目面板像资源管理器,有以下的特征:
l    综合了控制应用程序的源文件
l    容易访问关联的文件
l    即使在编辑类文件时,也可以发布应用程序
这个内容超出了本书的范围,更多内容请看帮助
11.7 小结
虽然这章感觉是非常概念化的,但它展示了非常重要的OOP开发的基础,也是这本书下面的许多例子的基础。
译自:Essential ActionScript 2.0 第二部分第11章

  货币转换应用程序这个例子只进行简单的货币转换,下图显示了这个程序所需的界面,包括一些UI组件实例(Button, ComboBox, Label, TextArea, and TextInput)。在这个应用程序中,用户必须输入一个加拿大元的数目,然后从下拉列表中选择货币类型,再单击转换按钮,就能在文本框中显示出转换后的等量的货币数值。 
图12-1 应用程序的界面
和第11章的使用的结构对应,我们将把元素放在下面的目录下,在创建目录时要注意, deploy和source都是CurrencyConverte文件夹的子目录,org/moock/tools是source文件夹的子目录。
CurrencyConverter/
deploy/
source/
org/
moock/
tools/
这个应用程序的主要部分是名为CurrencyConverter.fla的flash文档文件,为了创建这个文件,我们把AppName.fla(在第11章创建的)复制到CurrencyConverter/source目录中,然后把AppName.fla重命名为CurrencyConverter.fla。这样CurrencyConverter.fla就具有了基本的所需结构,包括类的预载等(详见第11章)。
这个应用程序只有一个CurrencyConverter类,这个类位于CurrencyConverter/source/org/moock/tools中,而导出的CurrencyConverter.swf位于CurrencyConverter/deploy目录中。
现在让我们开始货币转换应用程序的每个细节。
12.2 准备flash文档文件
CurrencyConverter类将在运行时实例化应用程序所需的组件。虽然我们是在运行时创建组件的实例,flash仍然要求把组件添加到CurrencyConverter.fla的库中。不幸的是,flash不允许直接从组件面板中把组件拖到库中。如果我们要在库中添加一个组件,必须在场景中先创建组件的实例。虽然组件实例可以直接留在了fla文件的场景中,但这种开发模式不是我们现在的重点,因此必须把场景中的组件实例删除。不管哪种方法,flash都把组件加入了库中(这是我们最初的目的)。
12.1.1 添加组件到flash文档文件
步骤:拖入组件,然后删除
如果我们在一个全新的fla文件中创作,组件就已准备好被实例化了,然而,回忆我们在第11章的fla文件中的创作,当类被加载时,在第10帧导出类和显示加载信息。由于这个预载结构,如果我们想使用组件,这些组件将不能工作,因此我们要把组件整合到预载结构里。
注:当一个类在fla文件中的第1帧以后被导出时,组件将不能工作,除非组件是在类导出帧的后面加载。
为了在第10帧以后加载组件,必须设定组件在第1帧不能导出,然后我们在第10帧后放置每个组件的虚的实例,这些虚的实例是不能被使用的,它只是用来加载组件。
下面的步骤就是阻止Button实例在第1帧导出,其它组件的设置方法与些类似:
1、在库中选中Button组件
2、单击库面板的右上角,从下拉菜单中选择“链接”
3、在链接属性对话框中的链接一栏,不选中“第1帧导出”多选按钮
4、单击确定按钮
当一个组件的“第1帧导出”多选按钮没选中时,除非一个组件实例被放在了时间轴上,否则组件将不被影片编译。组件将在放置它的实例的帧上导出,但是在组件导出之前,组件的初始化过程仍需要一个可用的类。因此,在CurrencyConverter.fla文件中,我们将在第12帧放置每个组件的实例。为了存放虚的组件实例,要创建新的图层和关键帧:
建立一个名为load components的图层,在第12和13帧插入关键帧,这两帧的作用是不让虚的实例显示在应用程序中,虚的实例只是用来加载,CurrencyConverter类是真正的组件实例的创造者。
当组件加载帧制作好后,把虚的实例放在时间轴上:
1、选中第12帧
2、从库中拖入每个组件到场景中
3、就像在图12-2中显示的一样,随便的使用组件检查器为组件实例增加虚的表示不被使用的文字。
图12-2 应用程序的时间轴和场景
12.2.2 开始应用程序
在第11章,我们知道了通过调用第一个类的mian()方法来开始应用程序,在这里,也将通过调用urrencyConverter类的mian()方法来开始应用程序,当类和组件被加载后,main方法将在CurrencyConverter.fla时间的scripts图层的第15帧被调用。下面是调用的代码:

import org.moock.tools.CurrencyConverter; 
CurrencyConverter.main(this, 0, 150, 100); 

注意:通过使用完整的、有效的包路径,import关键字允许我们通过CurrencyConverter类的类名访问CurrencyConverter。上述的代码的意思是,类的main( )方法要指定四个参数:拥有货币转换的mc、深度、水平位置和垂直位置。
fla文件现在已经准备好了,我们将把注意力转移到类的创建和管理应用程序本身。
12.3 CurrencyConverter类
CurrencyConverter类要实现3个任务:
l    提供一个方法开始应用程序
l    创建应用程序界面
l    响应用户的输入等操作
在调试完成上述任务的类代码之前,你应该快速浏览例12-1中代码清单。现在,你没有必要太仔细的阅读代码,在这章的后面将会对这些代码做详细的介绍。
例12-1: CurrencyConverter类
12.3.1 导入组件包
CurrencyConverter类需要用到各种各样的组件类,我们需要的组件类位于包mx.controls中。在CurrencyConverter类中,我们要通过组件类的类名来使用这些组件,如as mx.controls.Button 或 mx.controls.ComboBox,这个明显是很麻烦的,因此,在定义CurrencyConverter类之前,我们导入整个包:

import mx.controls.*; 

一旦这个包被导入,我们就不用使用完整的类名,可以直接使用如Button等组件类,要特别注意的是,并不是所有的组件都是位于mx.controls包中,如一些容器组件如Window 和 ScrollPane是位于包mx.containers中。要查看组件的包,可参考帮助。
12.3.2 CurrencyConverter类属性
CurrencyConverter类定义了两种一般的属性:类属性指定了货币的兑换率,实例属性保存了应用程序所需的组件:
代码略
出于对本例子的考虑,在类属性中的兑换率是不变的,在真实的货币转换应用程序中,它们更可能是通过动态的、保存在服务器中的数据代替。
要注意,不是所有UI组件都保存在实例属性中,如果一些组件不需要被再次使用,就不用保存在实例属性中。当创建一个组件时,我们保存对组件实例的一个引用只是设想类里的另一个方法稍后要使用它。
12.3.3 main()方法
这个应用程序开始的方法就是main()方法:

public static function main(target:MovieClip, depth:Number, x:Number, y:Number):Void { 
        var converter:CurrencyConverter = new CurrencyConverter(target, depth, x, y); 
    } 

main()方法是一个类方法,因为它是为完整的应用程序调用,并且没有和一个特别的类关联。Main()方法创建了CurrencyConverter类的第一个实例,并把实例保存在一个本地变量converter中。实际上, CurrencyConverter类创建了一个自己的实例,这个做法是合理的、普遍的。
就像我们先前看到的,main()方法从帧标签main处被调用,当main()方法消失时,指向实例的变量converter也将不存在,它会自动被删除。然而,应用程序将继续运行,创建的mc和组件实例即使没有被保存在变量或属性中,也继续留在场景上,直到它们被移除。
正如我们后面看到的,通过main()方法产生的实例在buildConverter( )方法中能继续存在,通过buildConverter( )方法创建的mc和组件实例也保持着一个对实例的指向,没有这个指向,应用程序将不会响应组件事件。
12.3.4 类的构造函数
这个类的构造函数是简单的,它只是调用了buildConverter( )方法来创建应用程序的用户界面。

public function CurrencyConverter(target:MovieClip, depth:Number, x:Number, y:Number) { 
        buildConverter(target, depth, x, y); 
    } 

这个构造函数传递参数给程序的另一个部分,在这个例子中,是传递给通过参数创建用户界面的buildConverter( )方法。
12.3.5 创建用户界面
正如刚才学的,这个应用程序的界面是通过buildConverter( )方法创建的,为了创建用户界面,buildConverter( )方法实例化了UI组件,定义了事件处理器来指示界面的行为。
你已经在前面的例12-1中看了buildConverter( )方法,现在让我们一行一行来看。
buildConverter( )方法的第一行对一些程序员可能是陌生的,它定义一个局部变量thisConverter,来保存对当前对象的一个引用。
var thisConverter:CurrencyConverter = this;
保存一个当前对象到一个局部变量,只是允许当前对象能被嵌套的函数调用,当这些嵌套的函数存在时,当前对象也是存在的。不久我们将看到,事件处理器能在嵌套的函数中执行。它们使用变量thisConverter来访问CurrencyConverter类的实例,关于这个技术的更多信息,在第14章还会提到。
12.3.5.1 界面容器
现在我们继续创建界面元素,首先,要创建一个影片剪辑容器用来放置所有的组件,我们将给这个容器命名为converter,放在指定深度的target影片剪辑里面,回忆在main()中的参数target和depth,这两个参数先传递给构造函数,再传递给buildConverter( )方法:
converter_mc = target.createEmptyMovieClip("converter", depth);
把所有的组件放在一个影片剪辑里,把这些组件做为一个群体来操作将变得非常容易。例如,要移动所有的组件,只需移动影片剪辑的坐标:

converter_mc._x = x; 
converter_mc._y = y; 

这时的x和y是提供给main()方法的参数。
注意:我们的影片剪辑容器保存在实例属性converter_mc中,保存影片剪辑到属性保证了它能被外部的buildConverter( )访问,或者说是出于重新设置或删除它的目的。在我们的这个案例里,我们没有重置或删除这个容器。因此,从理论上讲,我们能把它保存在代替属性的局部变量里。然而,我们把它保存在了实例属性converter_mc里,只是为了在将来能把类增加功能。
13.5.5.2 标签(Label)组件
现在我们的容器已经准备好了,我们就能把组件放在它的里面,通过使用Label组件,先给应用程序一个标题,Label组件常来显示一个单行的文本:
下面的代码是在converter_mc里创建一个名为title的Label组件实例,title的深度为0:
var title:Label = converter_mc.createClassObject(Label, "title", 0);
在我们这个应用程序里,所有的组件都是用UIObject 类的 createClassObject( )方法来创建,这个方法有三个参数,分别用来指定:
l    要创建的组件类
l    组件的实例名
l    在父影片剪辑或组件里的深度
createClassObject( )方法返回了一个新组件实例的指向,在这个案例的title实例里,我们把这个指向保存在了变量title里,之所以使用局部变量title,是因为在后面不需要使用title实例,如果在其它地方我们还要访问title,我们应把它保存在一个实例属性中。
注意那个实例名title,只是按照习惯要被使用,我们从来不会在代码中使用这个名字。在我们的应用程序中,我们都通过变量或属性来指向组件。在这个案例中,我们通过变量名title指向title实例,而不是通过它的实例名(这时变量名和实例名碰巧是相同的)。按照习惯,大部分的组件的实例名都与保存这个实例名的变量或属性相匹配。然而,没有规定实例名要和变量或属性名要匹配,只有变量或实例名与我们的应用程序有关(实例名被忽略)。
现在让我们近处看一下常用于创建Label实例的代码:

converter_mc.createClassObject(Label, "title", 0); 

MovieClip类有没有定义createClassObject( )方法,为什么能在MovieClip类的实例converter_mc里面调用createClassObject( )方法呢?因为在运行时,通过V2组件架构(连同各种各样的方法)把这个方法加入到了MovieClip类里。只有一个类声明为动态时,才能在运行时把方法或属性加到这个类里面,就像在MovieClip类里面定义一样。
因为我们是通过mc来访问createClassObject( )方法,所以我们也不用担心这个方法返回了一个数据类型为UIObject的对象。通过使用title变量,类型Label是不必需的。从理论上讲,下面的代码通常会出现类型不匹配的错误,因为createClassObject( )方法返回的是UIObject的类型,但是title的数据类型是Label。

var title:Label = converter_mc.createClassObject(Label, "title", 0); 

然而,没有错误发生是因为类型检查在运行时对动态加入的方法没有进行,但如果这个方法不是动态加入的,如任何组件都有createClassObject( )方法,如果你在一个组件中调用createClassObject( )方法,就要进行类型检查,此时这个不匹配错误就会发生。为了阻止一个编译错误,你必须指定返回值为你创建的对象的类型,例如,这里我们指定createClassObject( )方法的返回值为Label类型,如果converter_mc里用组件实例代替影片剪辑实例,下面的类型转换是必需的:

var title:Label = Label(converter_mc.createClassObject(Label, "title", 0)); 

现在,title实例已经被创建,我们将调整它的属性:

title.autoSize = "left"; 
title.text = "Canadian Currency Converter"; 
title.setStyle("color", 0x770000); 
title.setStyle("fontSize", 16); 

12.5.3 说明(Label)组件
要创建这个说明组件,我们将创建另一个Label组件,创建方法与title的类似,我们把label实例保存在名为instructions的变量中:

var instructions:Label = converter_mc.createClassObject(Label, "instructions", 1); 
instructions.autoSize = "left"; 
instructions.text = "Enter Amount in Canadian Dollars"; 
instructions.move(instructions.x, title.y+title.height+5); 

上述代码的最后一行是把instructions实例放在低于title实例5像素的位置。
注:设置组件位置的方法只有move()方法,通过读写属性x和y来设置组件的位置是没有用的,要注意,组件支持x,y,width等属性(没有下划线),而MovieClip实例支持_x等属性(有下划线)
12.3.5.4 输入(TextInput)组件
随着两个Label的创建,我们把目光移向接受用户输入的TextInput组件,第一个TextInput组件创建的代码如下:

input = converter_mc.createClassObject(TextInput, "input", 2); 
input.setSize(200, 25); 
input.move(input.x, instructions.y+instructions.height); 
input.restrict = "0-9."; 

注:设置组件的尺寸只能通过setSize()方法,不能使用width和height属性。
接下去要定义当用户按下ENTER键时发生什么,首先创建一个事件侦听器对象,用来接收事件:

var enterHandler:Object = new Object( ); 

在这个对象实例里面,定义一个enter()方法,当用户按下ENTER键时,这个方法会自动被调用:

enterHandler.enter = function(e:Object) { 
    thisConverter.convert(); 

enter()方法通过thisConverter变量访问了convert()方法,当input实例存在时,变量thisConverter是可以被嵌套的enterHandler.enter()方法访问:
最后,注册对象实例接收enter事件:
input.addEventListener("enter", enterHandler);
虽然使用一个对象实例处理事件是很合理的,但在更复杂的例子中,一个事件侦听器对象有充分的理由是一个独立的类的实例,不管怎样,当一个单一的事件产生单一的响应时,用事件处理函数比用对象实例处理事件更有可能的。在的应用程序转换按钮中,我们将使用事件处理函数来处理事件。后面我们也将学习MM为什么不鼓励使用事件处理函数。在这个特殊的enterHandler.enter()的例子中,由于Textinput类不能使用事件处理函数的bug,我们被迫使用一个侦听器对象。
12.3.5.5 货币拾取(ComboBox)组件
现在我们准备一个下拉菜单,来列出让用户进行转换的货币类型,ComboBox组件保存在currencyPicker属性中:

currencyPicker = converter_mc.createClassObject(ComboBox, "picker", 3); 
currencyPicker.setSize(200, currencyPicker.height); 
currencyPicker.move(currencyPicker.x, input.y+input.height+10); 

然后列出用户可以选择的货币类型,其中data属性将被convert( )方法使用:

currencyPicker.dataProvider = [ 
{label:"Select Target Currency", data:null}, 
{label:"Canadian to U.S. Dollar", data:"US"}, 
{label:"Canadian to UK Pound Sterling", data:"UK"}, 
{label:"Canadian to EURO", data:"EU"}]; 

12.3.5.5 转换按钮(Button)组件
在前面我们已经设置一个按键事件(ENTER),现在要为不喜欢使用按钮的用户增加一个可见的转换按钮。
我们这时创建是的mx.controls.Button组件类的实例,注意要和Button符号的实例区别开来。因为我们较早的导入mx.controls.*包,编译器就能知道它指向的是Button组件类的实例。
创建Button组件实例:

var convertButton:Button = converter_mc.createClassObject(Button, "convertButton", 4); 
convertButton.move(currencyPicker.x+currencyPicker.width+5, currencyPicker.y); 
convertButton.label = "Convert!"; 

最后,我们用事件处理函数定义一个click事件:

convertButton.clickHandler = function(e:Object) { 
    thisConverter.convert(); 
}; 

上面的代码示例了组件事件使用的一种方法,它定义了一个的匿名函数,当click事件发生时,这个函数能自动调用。
由于匿名函数是嵌套在buildConverter( )方法的里面,它已经访问了buildConverter( )的局部变量,因此,通过局部变量thisConverter ,convertButton能指向当前的CurrencyConverter类的实例。
13.3.5.7 结果(TextArea)组件
我们的界面几乎已经完成了,还需要一个TextArea组件实例显示货币转换的结果,下面是创建的代码:

result = converter_mc.createClassObject(TextArea, "result", 5); 
result.setSize(200, 25); 
result.move(result.x, currencyPicker.y+currencyPicker.height+10); 

为了阻止用户替换TextArea组件实例中的内容,要加上下面的代码,因为它缺省值是true,表示可编辑的:
result.editable = false;
12.3.6 基于用户的货币转换
在前面,我们创建了用户界面,当用户click转换按钮时,会调用convert( )方法,这个方法能够进行转换货币,也能显示出错误信息,方法中的代码提供了组件怎样被访问和操作的一个很好的例子,代码如下:
这个类方法首先创建两个局部变量,convertedAmount用来保存转换后的货币值,origAmount用来保存用户输入的值:

var convertedAmount:Number; 
var origAmount:Number = parseFloat(input.text); 

输入框的内容可能是空的,或者它不是一个有效的值,因此下面代码的任务是检查是否把这个值进行了String到Number的转换,如果转换不成功,origAmount的值将是NaN,因此当这个值是NaN时,我们说它不是一个有效值。
if (!isNaN(origAmount)) {
如果这个值是有效的,我们就检查currencyPicker.selectedItem.data(下拉列表中被选中的货币值), selectedItem属性保存了一个对ComboBBox中当前选项的指向,它是前面创建的dataProvider数组的一个对象,为了确定哪个选项被选中,我们参考对象的data属性,它可能是null, "US," "UK," or "EU."中的一个,如果data属性为null,说明没有选项被选中,我们就不用进行转换货币:
if (currencyPicker.selectedItem.data != null) {
如果是其它的几个之一,我们用switch语句来确定到底是哪个值,根据这个值进行相应的转换:

switch (currencyPicker.selectedItem.data) { 
                case "US" : 
                    convertedAmount = origAmount/CurrencyConverter.rateUS; 
                    break; 
                case "UK" : 
                    convertedAmount = origAmount/CurrencyConverter.rateUK; 
                    break; 
                case "EU" : 
                    convertedAmount = origAmount/CurrencyConverter.rateEU; 
                    break; 
                } 

一旦转换成功,我们将结果列出:

result.text = "Result: "+convertedAmount; 

当没有货币被选中时,我们提示用户进行选择:

result.text = "请选择一种货币"; 

如果是一个无效值,提示用户输入一个有效值:

result.text = "当前值是无效的"; 

12.3.7 导出最终的应用程序

导出时要注意版本,V2 组件要求播放器的版本是6.0.79.0 或更高,但我在6.0.40.0中测试时,应用程序能很好的执行。

  这章讨论在许多工程中和开发者之间共享类库的各种方法。在开始讨论之前,要注意:flash的类分配特征并不是很精炼,到目前为止,共享类最简单的方法是发布源代码。在讲不通过发布源代码怎样共享类之前,我们将先讲这种最简单的方法,因为当你出售一个专业类库的时候可能要那样做。
类库是一个通常的程序术语,只是简单的指一群类,不要把它和fla文件库、库面板、共享库(在创作时共享库资源)和运行共享库(在运行时共享库资源)混合起来,这些术语中的每一个都是唯一的,这不是当前讨论的内容。
类库只是程序员用来发布一群类到一个团队或其它地方的行话,在另一个方面,包是一个正式的用来定义类的语法命名空间(为了防止和其它类的命名冲突)。
类库可能通过as、swf和swc的形式分配给其它开发者,这章将讨论这三种方法。 
14.1 共享类的源文件
共享类库最可能的原因是为了在多个工程中重复使用它们。例如,假设你正在做一个名叫Spider Services的网上商店(http://www.spiderservices.com/),你已经写了一个处理各种文本效果的TextAnimation类,你要在你做的两个站点中使用这个类,比在两个工程的文件夹中放置一份拷贝更好的方法是,你把类放在中间,每个工程只是指向它,例如,在windows中,你应该把类保存在下面的目录中:
c:/data/actionscript/com/spiderservices/effects/TextAnimation.as
为了使这个类能被两个工程都能访问,你应把c:/data/actionscript增加到flash的全局类路径中。
如果在你的团队中有多个成员,你可能认为把类放在中间服务器是方便的,这样每个人都可以使用它。例如:你可能要把所有的共享类都放在叫codecentral的服务器上,存放目录与你公司域名相配(/com/spiderservices)。
//codecentral/actionscript/com/spiderservices/effects/TextAnimation.as
这个方法是极度危险的,也是不推荐使用的。
注:
如果你把类放在一个中间服务器上并允许其它开发者直接修改,一个开发者容易覆盖另一个开发者的修改,而且,如果服务器和个人电脑的时钟不同步,在编译时,类的最后一个版本可能不会被包括在影片内。为了避免这个问题,你将总是使用版本控制软件去管理你的类文件。一个流行的选择是CVS (见 www.cvshome.org)。
在一个大的工程中,你可能使用一些工具使swf的输出自动化,例如Apache Ant (http://ant.apache.org).,为了那样做,你将执行JSFL告诉flash为每个fla创建swf文件,这些内容超出了本书的范围,但这有一个例子

// Code in exportPetSupplies.jsfl: 
// =============================== 
// Open the .fla file. 
var doc = fl.openDocument("file:///c|/data/projects/pet/petsupplies.fla"); 
// Export the .swf file. 
doc.exportSWF("file:///c|/data/projects/pet/petsupplies.swf", true); 
// Quit the Flash MX 2004 authoring tool (optional). 
fl.quit(false); 
// Command issued on command line from /pet/ directory: 
// ==================================================== 
"c:/program files/macromedia/flash mx 2004/flash.exe" exportPetSupplies.jsfl 

14.1.1 在运行时加载类
当使用同一个类制作多个swf文件时,把类编译到每个swf中是一种空间浪费,当文件的尺寸有关系时,为阻止这种事情发生,你可能把类库放到一个单独的swf文件并在运行时加载它,一旦这个库在第一时间加载,它可能隐藏在用户的机器中,然后不需重新下载它就能被其它swf利用。
注:一个类库在运行时加载称为动态类库
为了创建这个动态类库,我们编译类库到一个swf文件中,然后使用loadMovie()把它加载到任何一个需要类库的swf文件中。然而,为了使这种技术能正常工作,我们必须确定在库中的类被排除在加载它们的影片中。为了实现阻止类被编译到加载它们的影片中,我们使用排除XML文件。这个xml能让fla文件本地访问类,但能阻止这些类被包含在输入的swf文件,这样允许在运行时被加载。
为了创建运行加载的类库,我们返回到前面的例子:
下面这些步骤是必须要做的:
1、在类库中创建类
2、创建含类库的swf文件
3、创建加载含类库swf文件的影片
4、创建排除xml文件
14.1.1.1、在类库中创建类
创建一个类库的第一步是创建它包含的类,我们的角色类是TextAnimation,保存在c:/data/actionscript/com/spiderservices/effects/TextAnimation.as,对于这个例子,我们的焦点是类库的建立 ,因此我们没给出真实的类代码,只在构造函数中提供了一个trace()来检查这具类是否在工作。

class com.spiderservices.effects.TextAnimation { 
public function TextAnimation ( ) { 
trace("Imagine a text effect with great majesty."); 

现在类库已经建好,下一步创建含类的swf文件
14.1.1.2、创建类库(swf文件)
为了创建这个swf文件,跟随以下步骤
1、创建目录c:/data/spiderservices/spidercore。
2、增加全局类路径c:/data/actionscript
3、建立一个fla文件,保存在c:/data/spiderservices/spidercore/spidercore_runtime.fla
4、第1帧输入:

com.spiderservices.effects.TextAnimation 

如果类库中含有更多有类,这些类都应列在第1帧,编译器会自动查找并包括所有有关的类(例如,在TextAnimation中使用了Tween,Tween类就会自动的包括到swf文件中),这样,我们就不用一个个导入TextAnimation类中所需的类。
5、在第2帧插入一关键帧,输入:
6、在第2帧输入代码:

_parent.spidercoreLoaded(); 
stop(); 

7、输出swf文件。
8、保存fla文件
类库创建好后,就可以在任何一个影片中使用它们
14.4.1.3、创建加载类库的影片
在这个例子中,加载spidercore_runtime.swf是公司的主页,创建主页影片:
1、增加目录c:/data/spiderservices/barkys
2、新建一个fla文件,保存在c:/data/spiderservices/barkys/barkyshome.fla
3、在第1帧输入:

import as.TextAnimation; 
function spidercoreLoaded():Void { 
    var ta:TextAnimation = new TextAnimation(); 

// Load class library. 
this.createEmptyMovieClip("spidercore", 0); 
this.spidercore.loadMovie("../spidercore/spidercore_runtime.swf"); 

homepage这个影片是准备用来使用类库的,这里有个问题:如果在当前使用,它会自动包含TextAnimation类到影片中,我们可不想让这种事情发生,因为我们要通过spidercore_runtime.swf来加载类。我们不需要加载两次类,因此,需要创建一个特殊的xml文件,告诉编译器不要把TextAnimation包含进barkyshome.swf中。
你可能设想只通过移除c:/data/actionscript路径,就能使TextAnimation类不被包含到barkyshome.swf中。事实上,就是可以的,但由于类型检查的原因也阻止编译器查找TextAnimation类,因此,类型错误将发生,barkyshome.swf将不能被编译,因此,虽然我们在运行时加载类,我们仍需在创作时访问这些类。
14.1.14、创建xml文件
在这个文件中,指定哪些类将被编译进相关的swf文件中,我们将阻止在运行时动态加载的类编译进应用的swf文件中。
1、创建一个名为barkyshome_exclude.xml文件,保存在c:/data/spiderservices/barkys/barkyshome_exclude.xml。
排除文件的精确名字是很重要的,这必须与应用它的fla文件名正确匹配。
2、这个排除文件阻止指定类被编译进相关的swf文件,为了阻止TextAnimation类被编译到swf文件,在barkyshome_exclude.xml中增加代码:


 
 

这时有一个假定的排除文件,显示怎样来排除多个类,它排除了下面列出的三个类


 
 
 
 

3、保存
在标签中通过列出TextAnimation类,我们强迫它排除在barkyshome.swf文件中。在spidercore_runtime.swf类库中的每个类都应该列在标签中。
注:编译不会自动排除依赖的类,例如:要spidercore类库包含一个被TextAnimation 使用的Tween类,但barkyshome_exclude.xml没有在标签列出此类,Tween将被包含在barkyshome.swf中。
我们的spidercore类库现在已经准备好使用了
14.1.1.5 测试
通过输出barkyshome .swf文件来测试这个类库,barkyshome.sw将不包含TextAnimation类,但通过动态类库spidercore_runtime.swf可以访问它,记住,类库能加载是因为我们告诉它:

// Load class library. 
this.createEmptyMovieClip("spidercore", 0); 
this.spidercore.loadMovie("../spidercore/spidercore_runtime.swf"); 

你将在输出面板中看到:
Imagine a text effect with great majesty.
如果我们想要加载另一个类库,第2版本的类库不会覆盖第一个版本的,它将被hu略,为了防止命名冲突,特别我们使用动态类库的时候,让你的类在有唯一名字的包里。

原创粉丝点击