学习目标
1. 了解 MXML 组件开发的特点。
2. 学习使用 MXML 语言开发组件,逻辑部分要结合 ActionScript 语言。
3. 开发组件“自定义播放器”。
MXML 组件开发的特点
为什么要开发组件?简而言之,是因为组件有利于代码的维护和复用。组件可以被定义在 MXML 文件(以 .mxml 为后缀的文件)或者 ActionScript 文件(以 .as 为后缀的文件)中。因此,有两种组件开发的形式:MXML 组件开发及 ActionScript 组件开发。凡是定义在 MXML 文件中的组件都可以转化为定义在 ActionScript 文件中的组件。Flex SDK 中的大部分组件都是定义在 ActionScript 文件中的。
然而,MXML 组件开发也有其适用之处。MXML 组件比较适合“组合模式”的组件,通过 MXML,可以很方便的进行子组件的布局及数据绑定。 以下是一些 MXML 组件开发的示例,更多的内容还需读者在实践中体会摸索。
MXML 组件开发的优点:
- 可以利用“设计”模式,进行所见即所得的界面开发。
- 可以快速的添加子组件。不需要申明一个实例,然后再调用 addChild(),将其添加到父组件的布局中去。
- 可以很方便地进行数据绑定。使用 “{ binding_expression }”可以快速将任意可绑定的数据源绑定到指定位置。
- 可以很方便的定义类实例,不需要显示的初始化。例如定义<fx:Binding/>后,应用程序运行时会自动初始化该实例。
MXML 语言的缺点:
- 没有 ActionScript 的辅助,无法完成复杂的逻辑。
- MXML 组件默认都是 public 的,没有访问限制。
- MXML 标签中定义的事件是不可以被移除的。如<mx:Button click="doClick()"/>,click 事件是不可以被移除的。
- 不能自定义构造函数。
使用 Flash Builder 4 和 MXML 语言开发组件的步骤:
- 新建 Flex 库项目。Flex 库项目编译后可以产生 swc 文件,可以作为组件发布的主要形式。
- 新建 MXML 组件文件,文件名即为组件名。
- 继承现有组件。文件内部第一级标签即为所继承组件名。
- 在 MXML 文件主标签里加 implements 属性可以继承接口。
- 在 MXML 文件里添加各种标签,包括可视组件,数据服务,验证,特效等。如果是非可视的标签,如特效等要加到 <fx:Declarations> 标签里,这是 Flex 4 的新特点。
- 可以在 <fx:Script> 标签里添加 ActionScript 代码,以实现逻辑,比如事件处理等。代码要被 <![CDATA[ 和 ]]> 包围。
- 可以在 <fx:Style> 标签里添加CSS样式代码,以设置组件样式。
- 可以添加 <fx:XML>、<fx:XMLList>、<fx:Array>、<fx:Model> 等实用标签,以提供更好的功能。这些标签都要加在 <fx:Declarations> 标签里。
开发实例
本节通过开发一个“自定义播放器”组件,来讲解 MXML 组件开发技术。界面布局、组件使用等部分采用 MXML 语言,逻辑部分采用 ActionScript 语言。因为是组合式组件,不涉及组件生命周期等,所以可以利用 MXML 来开发,方便布局和属性设置等。
先看一下最终使用效果图:(图五十九)
图五十九
播放器预览
上部即是我们要做的“自定义播放器”组件,中间和下部是文字介绍和播放列表,不属于“自定义播放器”组件。
接下来,和我一起一步一步开发“自定义播放器”。
1. 设计“自定义播放器”组件功能
播放器是一种很常用的组件,既可以是一个独立产品,也可以集成到大系统中,如教学系统、会议系统、娱乐网站等,主要分为音频播放器和视频播放器。
对于视频播放器,Flex 3 框架的 mx.controls.VideoDisplay 类提供了视频播放的视频显示和基本控制,但是没有提供控制界面等播放器必需的元素。Flex 4 框架保留了此类,但是在新增的 spark 组件体系中,有功能更加完善的视频播放器支持类。spark.primitives.VideoElement 类提供了基本控制和显示功能,但是没有外壳,spark.components.VideoPlayer 类是带完整外壳的视频播放器组件,spark.skins.default.VideoPlayerSkin 是 spark.components.VideoPlayer 类的默认皮肤,还有支持组件:VideoPlayerScrubBar(时间条),VideoPlayerVolumeBar(音量条),VideoPlayerVolumeBarMuteButton(静音按钮)。可以说功能非常完善了。
对于音频播放器,Flex 4 框架还没有提供现成的完整组件,但是 flash.media 包提供了对声音的基本控制。互联网上有封装好的开源音频播放器类库,接口类似于 Flex 视频播放器,可以拿来复用。地址为:http://rojored.com/#simple-flex-audio-player 。
我们的“自定义播放器”就是要结合视频播放器和音频播放器的功能,可以同时播放视频和音频,自动切换显示区的有无。
详细功能列表:
1. 可以加载视频和音频文件,自动判断并播放。
2. 如果是视频,则有显示区,是音频,则无显示区。
3. 播放/暂停按钮,具有播放/暂停控制功能;停止按钮,具有停止并返回开头控制功能。
4. 时间条,可以显示播放进度;拖动控制播放进度。
5. 已播放时间、总时间显示。
6. 音量条,可以控制声音大小。切换视频和音频,音量不变。
7. 静音按钮,可以切换静音。静音时,音量条可以调节,可以设置音量,以便在不静音时生效。
8. 信息,可以显示播放内容的额外信息,如内容描述。
9. 播放视频时,单击显示区切换视频播放和暂停状态。
2. 新建“自定义播放器”组件库项目及文件、示例项目及文件
a. 新建 Flex 库项目,项目名:CustomPlayer。其它选项默认即可。构建后,Flash Builder 4 会自动在 bin 文件夹中生成 swc 文件,即编译打包的组件。
b. 在 src 文件夹中新建包,名称:cn.airia.fb4.customPlayer。Flash Builder 4 会自动创建相应文件夹。
c. 在包 cn.airia.fb4.customPlayer 中新建 MXML 组件 CustomPlayer,继承自 spark.components.Group。宽度 100%,高度不填,布局改为 spark.layouts.VerticalLayout。
d. 新建 Flex 项目,项目名:CustomPlayerSample,作为示例项目,默认新建CustomPlayer.mxml 文件作为主应用程序文件。示例项目需要使用组件,有多种方法,我们采用这种方法:“库路径” > “添加项目” > “CustomPlayer”。在发布时,可以将最终 CustomPlayer 组件的 swc 文件复制到 CustomPlayerSample 项目的 libs 文件夹中,并从库路径中删除 CustomPlayer 项目,这样就可以独立运行 CustomPlayerSample 了。
e. 在CustomPlayerSample项目的“属性” > “项目引用”里添加对 CustomPlayer 项目的引用。在某些时候有用,比如打开CustomPlayerSample项目时也打开CustomPlayer 项目。
3. 编写示例程序类 CustomPlayerSample
示例程序用来使用和测试“自定义播放器”组件。可以简单处理,这里不详解了。
设计界面如下:(图六十)
图六十
播放器设计界面
上部是“自定义播放器”组件。各位读者因为还没编好“自定义播放器”,所以只会显示空白,没有播放控件。 中间是一段介绍文字。下部是播放列表,一个 List,选择项目后,“自定义播放器”会播放相应视音频。
我把示例程序 CustomPlayerSample 的主题设为了Wireframe,如果主题不同,组件在示例程序中的表现也不同,但是组件本身是一样的。
代码如下:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/halo"
xmlns:customPlayer="cn.airia.fb4.customPlayer.*"
minWidth="800"
minHeight="600"
viewSourceURL="srcview/index.html">
<!-- 这是 Flex 4 的新布局组件 -->
<s:layout>
<s:BasicLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
// 播放列表数据,可绑定
[Bindable]
private var playListDP:ArrayCollection = new ArrayCollection([
{label: "10秒钟的视频", url: "videos/10秒.flv"},
{label: "黑猩猩打空手道", url: "videos/空手道.flv"},
{label: "电话来了铃声", url: "audios/dddd.mp3"}]);
/**
* 给播放器要播放文件的源路径和额外信息,播放
*/
private function play(event:Event):void {
var item:Object = (event.target as List).selectedItem;
player.source = item.url;
player.info = item.label;
player.play();
}
]]>
</fx:Script>
<!-- 这就是我们要编写的“自定义播放器”组件 -->
<customPlayer:CustomPlayer id="player"
horizontalCenter="0"
width="450"
bottom="250"
top="20">
</customPlayer:CustomPlayer>
<!-- 文字介绍 -->
<mx:Text y="360"
width="450"
horizontalCenter="0"
color="#EA9FA4"
text="上面是一个简单的“自定义播放器”组件,可用于会议系统、教学系统、娱乐系统等。选择播放下面列表里的视频或音频:"/>
<!-- 播放列表,选择项目后播放对应视音频 -->
<s:List id="playList"
dataProvider="{playListDP}"
labelField="label"
y="400"
width="450"
height="100"
horizontalCenter="0"
selectionChanged="play(event);"/>
</s:Application>
4. 编写组件类 CustomPlayer
CustomPlayer 组合了官方视频类——VideoElement 类,以及开源音频类——rojored 的 Audio 类,并加上统一的控件控制两者,和额外的功能、改进,如点击视频切换播放暂停,静音后还能设置音量值等。
具体讲解可以参见代码注释,结合代码讲解更加直观,易于理解。
设计界面如下:
图六十一
CustomPlayer
在播放视频时会自动显示视频显示区。
代码如下: (代码片段包含了本例讲解、注释)
<?xml version="1.0" encoding="utf-8"?>
<!-- 这是 Flex 4 的新容器组件,没有皮肤,性能更好 -->
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/halo"
xmlns:rojored="com.rojored.view.controls.*"
width="100%"
creationComplete="init();">
<!-- 这是 Flex 4 的新布局组件,这里采用垂直布局 -->
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
/**
* 标示播放类型的常量
*/
public static const VIDEO:String = "video";
public static const AUDIO:String = "audio";
/**
* 设置的音量
* 不等同于播放对象的音量,在静音时不为0,仍然保持设置值
*/
[Bindable]
private var volume:Number;
/**
* 播放对象
* 可选 VideoElement 和 Audio 两种类
* 但是调用的接口方法类似,所以不限定类型
*/
[Bindable]
private var playObject:*;
/**
* 源属性
* 直接和播放对象的源挂钩
*/
[Bindable]
public function get source():Object {
return playObject.source;
}
public function set source(value:Object):void {
// 设置源后,要设置播放对象,以便选择播放视频或音频
setPlayable(value);
}
/**
* 显示在控制栏的额外信息
*/
private var _info:String;
[Bindable]
public function get info():String {
return _info;
}
public function set info(value:String):void {
_info = value;
}
/**
* 初始化
* 默认播放是视频,音量值 0.7
*/
private function init():void {
playObject = video;
volume = 0.7;
}
/**
* 播放
* 因为视音频播放对象播放方法一样,所以可以不用判断类型
*/
public function play():void {
playObject.play();
}
/**
* 暂停
* 因为视音频播放对象暂停方法一样,所以可以不用判断类型
*/
public function pause():void {
playObject.pause();
}
/**
* 切换播放和暂停状态
* 如果已播放,则暂停,如果已暂停,则播放
*/
public function togglePlayPause():void {
playObject.playing ? pause() : play();
}
/**
* 停止
* 因为视音频播放对象停止方法一样,所以可以不用判断类型
*/
public function stop():void {
playObject.stop();
}
/**
* 根据源来设置播放对象
* 先判断类型,然后停止非此类型的播放器,切换到此类型的播放器
* 如果是视频类型,显示视频容器,如果是音频类型,隐藏视频容器
*/
private function setPlayable(source:Object):void {
switch (judgeType(source)) {
case VIDEO:
audio.source = null;
audio.stop();
// 显示视频容器
videoContainer.visible = true;
// 标记显示列表失效,这样程序会自动更新绘制显示列表,否则视频位置和大小会有问题
videoContainer.invalidateDisplayList();
playObject = video;
playObject.source = source;
break;
case AUDIO:
video.source = null;
video.stop();
// 隐藏视频容器
videoContainer.visible = false;
playObject = audio;
playObject.source = source;
break;
}
}
/**
* 判断播放类型
* 利用正则表达式判断文件名后缀,如果是.flv,则为视频,是.mp3,则为音频
* 还可以添加其它文件类型的判断,现在暂时不考虑
*/
private function judgeType(source:Object):String {
if (new RegExp("(\.flv)$", "i").test(source.toString())) {
return VIDEO;
} else if (new RegExp("(\.mp3)$", "i").test(source.toString())) {
return AUDIO;
}
return null;
}
/**
* 格式化时间
* 将
* VideoElement 返回的是秒,Audio 返回的是毫秒,要处理好
*/
private function format(value:Number):String {
var seconds:Number;
if (playObject is Audio) {
seconds = Math.floor(value / 1000);
} else if (playObject is VideoElement) {
seconds = value;
}
var result:String = Math.floor(seconds % 60).toString();
// 如果是秒数是 1 位数,则在前面添加 1 个 0
if (result.length == 1) {
result = Math.floor(seconds / 60).toString() + ":0" + result;
} else {
result = Math.floor(seconds / 60).toString() + ":" + result;
}
return result;
}
]]>
</fx:Script>
<!-- 将播放对象的音量绑定为设置音量,如果静音了,则为0 -->
<fx:Binding destination="video.volume"
source="muteBtn.selected ? 0 : volume"/>
<fx:Binding destination="audio.volume"
source="muteBtn.selected ? 0 : volume"/>
<!-- 音频播放对象,因为是非可视对象,所以要放在 <fx:Declarations> 标签内 -->
<fx:Declarations>
<rojored:Audio id="audio"/>
</fx:Declarations>
<!-- 视频播放对象,因为 VideoElement 不能接受 click 事件,所以用一个 Group 包装 -->
<s:Group id="videoContainer"
click="togglePlayPause();"
width="100%"
height="100%">
<s:VideoElement id="video"
width="100%"
height="100%"
autoRewind="true"/>
<!-- 停止后自动回复到开头 -->
</s:Group>
<!-- 播放时间条,将值和播放对象的播放头时间双向绑定,这样就可以同步了 -->
<!-- 这里不采用 s:HSlider 组件,否则 value 的双向绑定会导致播放问题 -->
<mx:HSlider id="timeSlider"
width="100%"
minimum="0"
maximum="{playObject.totalTime}"
value="@{playObject.playheadTime}"
liveDragging="false"/>
<s:Group width="100%">
<!-- 播放暂停按钮,采用切换按钮,切换播放和暂停 -->
<s:ToggleButton id="playPauseBtn"
label="{playObject.playing ? '暂停' : '播放'}"
width="54"
x="0"
selected="{playObject.playing}"
click="togglePlayPause();"/>
<!-- 停止按钮,停止播放 -->
<s:Button id="stopBtn"
label="停止"
click="stop();"
x="60"
width="52"/>
<!-- 播放时间显示,包括当前播放头时间和总时间 -->
<mx:Label id="time"
x="119"
width="76"
text="{format(playObject.playheadTime)}/{format(playObject.totalTime)}"/>
<!-- 静音按钮,采用切换按钮,切换后会自动影响绑定它的播放对象音量值 -->
<s:ToggleButton id="muteBtn"
label="静"
width="33"
x="218"/>
<!-- 音量条,将值和设置的音量值双向绑定,而非播放对象的当前音量,因此不受静音影响 -->
<!-- 因为 valueInterval 默认为1,不合要求,要改为0,也就是无值间隔 -->
<s:HSlider id="volumeSlider"
width="86"
x="252"
y="5"
minimum="0"
maximum="1"
valueInterval="0"
value="@{volume}"
liveDragging="true"/>
<!-- 信息标签,显示赋给的额外信息 -->
<mx:Label id="infoLabel"
text="{info}"
x="346"
width="104"/>
</s:Group>
</s:Group>
总结:
本节使用了官方组件和开源类库自定义播放器组件,满足自己的特殊需要。比如可以同时播放视频和音频、点击视频能暂停等。 MXML 组件开发利用 MXML 做界面布局,ActionScript 做逻辑运算,各得其所,方便高效。
开发时,经常要自定义组件,这时可以充分利用已有的组件,减少重复开发带来的浪费。所以,要多多关注全世界的类库,同时,也可以把自己的成果贡献给世界。