自定义组件开发 第二节   MXML组件开发
        本节,我们以“自定义播放器”为实例,来学习 MXML 组件开发的方法。

学习目标

1. 了解 MXML 组件开发的特点。

2. 学习使用 MXML 语言开发组件,逻辑部分要结合 ActionScript 语言。

3. 开发组件“自定义播放器”。

 

MXML 组件开发的特点

为什么要开发组件?简而言之,是因为组件有利于代码的维护和复用。组件可以被定义在 MXML 文件(以 .mxml 为后缀的文件)或者 ActionScript 文件(以 .as 为后缀的文件)中。因此,有两种组件开发的形式:MXML 组件开发及 ActionScript 组件开发。凡是定义在 MXML 文件中的组件都可以转化为定义在 ActionScript 文件中的组件。Flex SDK 中的大部分组件都是定义在 ActionScript 文件中的。

然而,MXML 组件开发也有其适用之处。MXML 组件比较适合“组合模式”的组件,通过 MXML,可以很方便的进行子组件的布局及数据绑定。 以下是一些 MXML 组件开发的示例,更多的内容还需读者在实践中体会摸索。

MXML 组件开发的优点:

  1. 可以利用“设计”模式,进行所见即所得的界面开发。
  2. 可以快速的添加子组件。不需要申明一个实例,然后再调用 addChild(),将其添加到父组件的布局中去。
  3. 可以很方便地进行数据绑定。使用 “{ binding_expression }”可以快速将任意可绑定的数据源绑定到指定位置。
  4. 可以很方便的定义类实例,不需要显示的初始化。例如定义<fx:Binding/>后,应用程序运行时会自动初始化该实例。

MXML 语言的缺点:

  1. 没有 ActionScript 的辅助,无法完成复杂的逻辑。
  2. MXML 组件默认都是 public 的,没有访问限制。
  3. MXML 标签中定义的事件是不可以被移除的。如<mx:Button click="doClick()"/>,click 事件是不可以被移除的。
  4. 不能自定义构造函数。

使用 Flash Builder 4 和 MXML 语言开发组件的步骤:

  1. 新建 Flex 库项目。Flex 库项目编译后可以产生 swc 文件,可以作为组件发布的主要形式。
  2. 新建 MXML 组件文件,文件名即为组件名。
  3. 继承现有组件。文件内部第一级标签即为所继承组件名。
  4. 在 MXML 文件主标签里加 implements 属性可以继承接口。
  5. 在 MXML 文件里添加各种标签,包括可视组件,数据服务,验证,特效等。如果是非可视的标签,如特效等要加到 <fx:Declarations> 标签里,这是 Flex 4 的新特点。
  6. 可以在 <fx:Script> 标签里添加 ActionScript 代码,以实现逻辑,比如事件处理等。代码要被 <![CDATA[ 和 ]]> 包围。
  7. 可以在 <fx:Style> 标签里添加CSS样式代码,以设置组件样式。
  8. 可以添加 <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 做逻辑运算,各得其所,方便高效。

      开发时,经常要自定义组件,这时可以充分利用已有的组件,减少重复开发带来的浪费。所以,要多多关注全世界的类库,同时,也可以把自己的成果贡献给世界。

posted on 2011-04-07 10:11  帅死活该  阅读(1422)  评论(0编辑  收藏  举报