FLEX-AIR实践之电视台手机号抽奖程序

这个小程序作为我使用FLEX AIR做的第一个正式的程序,虽然看起来很简单,但还是涵盖了很多关于FLEX及FLEX AIR开发的知识点和一些细节上的小难点,正所谓麻雀虽小,五脏俱全,本人觉得对于初学者来说还是很有用的。下面是这个程序涵盖的知识点:

1、Timer的应用。如果使用JAVA制作抽奖程序来实现屏幕手机号的滚动效果,一般人都会想到使用线程,因为线程的wait方法可以控制手机号滚动的速度,另外suspend和resume方法也可以实现抽奖的暂停和恢复。但是很遗憾,FLEX不支持多线程,就只能使用Timer类来模仿线程的效果。

2、对文件的读写。抽奖时需要读取抽奖文件中的手机号,在抽取之后将中奖号码输出到新的中奖文件中,并将中奖手机号从抽奖文件中删除。

3、一些基本类的使用,如字符串处理和随机数生成等,这里是核心业务逻辑。

4、事件处理。如Timer事件和按键事件。事件和事件处理是FLEX的核心。

5、简单的特效使用,可以让你的程序看起来更炫。

6、AIR程序UI的设置。包括全屏显示、去掉AIR默认边框、背景透明等。

7、AIR程序的属性设置、发布和安装。

需求:

1、一个全屏显示的抽奖程序,中间一个Label滚动手机号,按下空格或回车后label停止滚动,显示中奖号码,中间四位掩盖。

2、从指定位置读取手机号码(一行一个号码)文件中的手机号,中奖号码输出到指定文件中以方便查阅记录,号码不能重复中奖。

使用工具:

FLEX BUILDER或FLEX BUILDER ECLIPSE PLUGIN皆可。

实现步骤:

1、建立AIR工程,FILE->NEW->FLEX PROJECT,工程名称为lot,下面的APPLICATION TYPE选择DESKTOP APPLICATION->FINISH.

2、UI显示设置:

在lot-app中设置背景透明:

<systemChrome>none</systemChrome>

<transparent>true</transparent>

在lot.mxml中设置去掉FLEX默认边框:

在<mx:WindowedApplication/>中设置showFlexChrome="false",如果你想让程序一直在前台可以设置alwaysInFront="true".

设置全屏显示:

我们需要程序一开始就是全屏显示的,全屏显示可以使用如下语句:

systemManager.stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;

通常我们可能会想到在<mx:WindowedApplication/>中设置initialize或createComplete来调用上面的语句来实现开始全屏,但是我们实际操作发现会出错,提示空对象操作。这是为什么呢?因为在组件创建过程中stage并未被实例化,直到整个程序初始化结束后才被实例化,因此我们需要使用applicationComplete来调用。

设置面板大小为1024*768,这部看自己喜好来定。

设置面板不自动显示滚动条,因为这个程序是要在电视直播中使用,除了面板背景图片和显示号码的LABEL,其他的东西都不应该显示出来,因此我们要把滚动条全部去掉,通过在<mx:WindowedApplication/>中设置verticalScrollPolicy="off" horizontalScrollPolicy="off".

3、添加特效:

我们要给程序添加打开、关闭IRIS特效,在MXML中添加如下代码:

<mx:Iris id="irisIn" duration="500" showTarget="false"/>
<mx:Iris id="irisOut" duration="500" showTarget="true"/>

然后在<mx:WindowedApplication/>中设置closeEffect="irisIn" creationCompleteEffect="irisOut" unminimizeEffect="irisOut" minimizeEffect="irisIn"

4、业务逻辑:

整个lot.mxml代码如下,不懂的地方请看注释:

<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication showFlexChrome="false" alwaysInFront="false" xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init()" applicationComplete="initComplete()" layout="absolute" closeEffect="irisIn" creationCompleteEffect="irisOut" unminimizeEffect="irisOut" minimizeEffect="irisIn" width="1024" height="768" borderStyle="solid" verticalScrollPolicy="off" horizontalScrollPolicy="off"> <mx:Script> <!--[CDATA[ import mx.controls.Alert; import util.NumberUtilities; import mx.utils.StringUtil; private var fileStream:FileStream = new FileStream(); private var array:Array = new Array();//号码存储数组 private var flag:Boolean = false;//用来标识号码是滚动还是停止 private var timer:Timer = new Timer(20,0);//设置计时器每0.02秒响应一次,即号码滚动速度 private var result:String; //抽奖结果 //初始化程序,并读取E:/Txt/code.txt的手机号,一个手机号一行: private function init():void{ this.nativeApplication.addEventListener(KeyboardEvent.KEY_UP,keyHandler);//添加键盘事件监听器 this.timer.addEventListener(TimerEvent.TIMER,timerEventHandler);//添加计时器事件监听器 //读取数据: var file:File = new File(); try{ file.nativePath = 'E://Txt//code.txt'; if(!file.exists){//如果文件不存在 Alert.show("E://Txt//code.txt 查无此文件!","错误提示"); this.close(); }else{//文件存在 fileStream.open(file,FileMode.READ); var text:String = fileStream.readUTFBytes(fileStream.bytesAvailable) //只读方式打开文件,将内容放到text中 array = text.split('/r/n');//将文本按照回车分割出号码并存在数组中,XP记事本文件中一行结尾是/r/n,这点要特别注意 //以下为保险起见,为了防止文件出现空格等多余字符进行的处理 if(array.length==1){ array = text.split('/r'); } while(array.indexOf("",0)!=-1){//消除因文本输入错误而出现的空格 array.splice(array.indexOf("",0),1);//去掉空元素 } if(array.length==0){ Alert.show("文件中无号码!","错误提示"); } fileStream.close(); this.setFocus();//这句很重要,如果面板不设置成焦点是不能响应键盘事件的 } }catch(e:Error){ trace(e.toString()); trace("File Path:"+file.nativePath); } } //全屏显示程序: public function initComplete(){ systemManager.stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE; } //关闭程序 private function windowclose():void{ this.close(); } //键盘事件:只响应回车或空格键 public function keyHandler(event:KeyboardEvent):void{ if(event.keyCode==32||event.keyCode==13){//回车或空格 if(!flag){//如果号码尚未开始滚动 timer.start();//那就开始滚 flag = true; }else{//激动人心的一刻,字幕此时停止滚动!奇迹出现了!谁会获得这五块钱的现金大奖呢?? timer.stop();//停止滚动 flag = false; try{ //输出中奖号码到指定文件 var file:File = new File(); var date:Date = new Date(); var datestring:String = date.getFullYear()+"年"+(date.getMonth()+1)+"月"+date.getDate()+"日"; file.nativePath = "E://中奖号码//"+datestring+"中奖号码.txt"; fileStream.open(file,FileMode.APPEND); //APPEND方式打开 //var date:Date = new Date(); //var datestring:String = date.getFullYear()+"年"+(date.getMonth()+1)+"月"+date.getDate()+"日"; fileStream.writeUTFBytes("一等奖中奖号码:"+"/r/n"+this.result+"/r/n"); //将内容写入文件 fileStream.close(); //以下是将中奖号码从抽奖号码中删除,方便接下来的抽奖 var file1:File = new File(); file1.nativePath = "E://Txt//code.txt"; fileStream.open(file1,FileMode.WRITE); array.splice(array.indexOf(result,0),1); var temp:String = ""; for(var i:int=0;i<array.length;i++){ temp = temp+array[i]+"/r/n"; } fileStream.writeUTFBytes(temp); fileStream.close(); }catch(e:IOError){ trace(e); //Alert.show("出错了!","错误提示"); } } } } //计时器事件处理 public function timerEventHandler(event:TimerEvent):void{ if(array.length>0){ var ran:Number = NumberUtilities.random(0,array.length-1);//生成一个[0,length)的随机整数,这里NumberUtilities.as在util包中 var a:String = array[ran];//取出位置为上面随机数的元素 tel.text = a;//显示号码 result = a; }else{ flag = false; } } ]]--> </mx:Script> <mx:Style/> <mx:Iris id="irisIn" duration="500" showTarget="false"/> <mx:Iris id="irisOut" duration="500" showTarget="true"/> <mx:Image x="0" y="0" width="1030" height="780" source="image/1.jpg"/> <mx:Image x="999" y="10" height="13" width="13" source="image/windx_r1_c3.png" autoLoad="true" scaleContent="true" click="windowclose()"/> <mx:Label x="358" y="431" height="67" width="406" color="#D30000" fontSize="50" fontWeight="bold" enabled="true" text=" 抽奖开始" id="tel"/> </mx:WindowedApplication>

因为FLEX自带的随机数类并不能满足我们的要求,于是我们要自己写一个随机数类,用来取出指定范围的随机数:建立util包,创建NumberUtilities.as,代码如下:

package util { public class NumberUtilities { private static var _aUniqueIDs:Array; /** * Round a number. By default the number is rounded to the nearest * integer. Specifying a roundToInterval parameter allows you to round * to the nearest of a specified interval. * @param number The number you want to round. * @param nRoundToInterval (optional) The interval to which you want to * round the number. The default is 1. * @return The number rounded to the nearest interval. */ public static function round(nNumber:Number, nRoundToInterval:Number = 1):Number { // Return the result return Math.round(nNumber / nRoundToInterval) * nRoundToInterval; } /** * Get the floor part of a number. By default the integer part of the * number is returned just as if calling Math.floor( ). However, by specifying * a roundToInterval, you can get non-integer floor parts. * to the nearest of a specified interval. * @param number The number for which you want the floor part. * @param nRoundToInterval (optional) The interval to which you want to * get the floor part of the number. The default is 1. * @return The floor part of the number. */ public static function floor(nNumber:Number, nRoundToInterval:Number = 1):Number { // Return the result return Math.floor(nNumber / nRoundToInterval) * nRoundToInterval; } /** * Get the ceiling part of a number. By default the next highested integer * number is returned just as if calling Math.ceil( ). However, by specifying * a roundToInterval, you can get non-integer ceiling parts. * to the nearest of a specified interval. * @param number The number for which you want the ceiling part. * @param nRoundToInterval (optional) The interval to which you want to * get the ceiling part of the number. The default is 1. * @return The ceiling part of the number. */ public static function ceil(nNumber:Number, nRoundToInterval:Number = 1):Number { // Return the result return Math.ceil(nNumber / nRoundToInterval) * nRoundToInterval; } /** * Generate a random number within a specified range. By default the value * is rounded to the nearest integer. You can specify an interval to which * to round the value. * @param minimum The minimum value in the range. * @param maximum (optional) The maxium value in the range. If omitted, the minimum value is used as the maximum, and 0 is used as the minimum. * @param roundToInterval (optional) The interval to which to round. * @return The random number. */ public static function random(nMinimum:Number, nMaximum:Number = 0, nRoundToInterval:Number = 1):Number { // If the minimum is greater than the maximum, switch the two. if(nMinimum > nMaximum) { var nTemp:Number = nMinimum; nMinimum = nMaximum; nMaximum = nTemp; } // Calculate the range by subtracting the minimum from the maximum. Add // 1 times the round to interval to ensure even distribution. var nDeltaRange:Number = (nMaximum - nMinimum) + (1 * nRoundToInterval); // Multiply the range by Math.random(). This generates a random number // basically in the range, but it won't be offset properly, nor will it // necessarily be rounded to the correct number of places yet. var nRandomNumber:Number = Math.random() * nDeltaRange; // Add the minimum to the random offset to generate a random number in the correct range. nRandomNumber += nMinimum; // Return the random value. Use the custom floor( ) method to ensure the // result is rounded to the proper number of decimal places. return floor(nRandomNumber, nRoundToInterval); } /** * Generate a unique number. * @return The unique number */ public static function getUnique():Number { if(_aUniqueIDs == null) { _aUniqueIDs = new Array(); } // Create a number based on the current date and time. This will be unique // in most cases. var dCurrent:Date = new Date(); var nID:Number = dCurrent.getTime(); // It is possible that the value may not be unique if it was generated // within the same millisecond as a previous number. Therefore, check to // make sure. If it is not unique, then generate a random value and concatenate // it with the previous one. while(!isUnique(nID)) { nID += NumberUtilities.random(dCurrent.getTime(), 2 * dCurrent.getTime()); } _aUniqueIDs.push(nID); // Return the number. return nID; } /** * Check to see if a number is unique within the array of stored numbers. * @param number The number to compare. * @return True or false */ private static function isUnique(nNumber:Number):Boolean { for(var i:Number = 0; i < _aUniqueIDs.length; i++) { if(_aUniqueIDs[i] == nNumber) { return false; } } return true; } } }

至此,程序代码部分完成。

5、程序的发布配置:

如果经调试,程序可以正常运行了,我们就可以发布程序了。

在lot-app.xml中进行配置:

这个是用户安装完程序后,在桌面上显示的快捷方式的名称:

<filename>短信抽奖程序</filename>

这个是安装程序中显示的:

<name>XXX节目短信抽奖</name>

这个是安装过程中显示的程序版本号:

<version>v1.0.2</version>

所有权……:

<copyright>[email protected]</copyright>

在第一个ICON处设置程序图标,理论上设置一个就可以了,该图标文件与MXML文件在同一目录下:

<image48x48>icon.png</image48x48>

至此程序发布前的工作完成了,接下来就可以发布程序了:

1、右键点工程->EXPORT->FLEX BUILDER->RELEASE BUILD->下面选择发布位置,NEXT

2、这时候需要创建一个数字证书,点create,填好必填项,确定,填写好password,FINISH

这时候你刚才设置的发布位置会有一个.air格式的安装包,你需要安装AIRADOBEINSTALLER才能安装,下载地址:

http://airdownload.adobe.com/air/win/download/latest/AdobeAIRInstaller.exe

安装好之后就可以打开AIR格式的安装包了。

程序安装之后在桌面会有快捷方式,点击即可运行程序了。

附注:本程序在显示手机号时因实际需要未对手机中间号码进行遮盖,如果需要可以使用字符串处理的API很简单地实现遮盖。

实际运行效果(图片显示不完整……):

FLEX-AIR实践之电视台手机号抽奖程序