开发工具 (FlashBuilder4.7)

程序类型(Adobe Air)

Flex Air做的桌面程序,效果还挺好看的。最主要是Socket这一块,它也是异步的,而且在Flex中的事件机制比較强大(个人觉得)

有改一些样式,又一次看看新的效果吧:

大致的实现方式:

在WindowedApplication中包括登陆窗体和主界面,用Flex中的状态来切换,聊天窗体时Window组件。好友列表用树菜单

实现好友分组。好友上线时改成在线图标,收到消息时头像抖动,聊天显示实现图文混排,系统托盘。其他貌似没了。

看下client的详细实现吧:

首先我们把Flex air的默认窗体样式改一改

打开 (项目名称-app.xml)文件,把凝视去掉相应改成例如以下
<systemChrome>none</systemChrome>
<transparent>true</transparent>
上面是把默认的样式去掉了,然后新建一个WindowedApplication的皮肤。例如以下

WindowAppSkin.xml

<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009"   xmlns:s="library://ns.adobe.com/flex/spark" xmlns:fb="http://ns.adobe.com/flashbuilder/2009"alpha.disabledGroup="0.5" ><fx:Metadata>[HostComponent("spark.components.WindowedApplication")]</fx:Metadata><s:states><s:State name="normal" /><s:State name="disabled" stateGroups="disabledGroup" /><s:State name="normalAndInactive" stateGroups="inactiveGroup" /><s:State name="disabledAndInactive" stateGroups="disabledGroup, inactiveGroup" /></s:states><s:Rect width="100%" height="100%" alpha="0"></s:Rect><s:Rect horizontalCenter="0" verticalCenter="0" width="{this.width-20}" height="{this.height-20}"topLeftRadiusX="6" topRightRadiusX="6"><s:fill><s:SolidColor color="#EBF2F9"/></s:fill><!-- 边框发光效果 --><s:filters><s:GlowFilter color="0x000000" alpha="0.6" blurX="7" blurY="7" strength="1" inner="false" quality="3" knockout="false"/></s:filters></s:Rect><!-- 主题内容 --><s:Group id="contentGroup" top="10" left="10" right="10" bottom="10"/>
</s:SparkSkin>

然后在主程序中引用这个皮肤 skinClass=“WindowAppSkin” ,这个皮肤文件我们在聊天窗体文件也能够用到

接下来看看主程序(WindowedApplication)的代码。它包括了登陆窗体和主界面,然后通过状态来切换界面显示

chat.mxml

<?

xml version="1.0" encoding="utf-8"?

> <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" skinClass="com.bufoon.skins.WindowAppSkin" width="450" height="350" initialize="init(event)" xmlns:components="com.bufoon.components.*"> <fx:Style> @namespace s "library://ns.adobe.com/flex/spark"; @namespace mx "library://ns.adobe.com/flex/mx"; .treeStyle{ /*去掉默认目录图标*/ folderClosedIcon: ClassReference(null); folderOpenIcon: ClassReference(null); /*去掉叶子节点图标*/ defaultLeafIcon: ClassReference(null); textIndent:5; leading:5; /* defaultLeafIcon 指定叶图标 disclosureClosedIcon 指定的图标旁边显示一个封闭的分支节点。

默认的图标是一个黑色三角形。 disclosureOpenIcon 指定的图标旁边显示一个开放的分支节点。默认的图标是一个黑色三角形。 folderClosedIcon 关闭指定的目录图标的一个分支节点。 folderOpenIcon 指定打开的目录图标的一个分支节点。 例:三角图标改动例如以下代码使用就可以换成自己的了: disclosureOpenIcon:Embed(source='images/a.png'); disclosureClosedIcon:Embed(source='images/b.png'); */ } </fx:Style> <fx:Script> <![CDATA[ import com.adobe.serialization.json.JSON; import com.bufoon.components.chatWindow; import com.bufoon.socket.CustomeSocket; import com.bufoon.socket.base.MessageType; import com.bufoon.socket.command.ParamHandle; import com.bufoon.socket.event.SocketEvent; import com.bufoon.socket.model.PackageHead; import com.bufoon.socket.util.DispatchEvent; import com.bufoon.util.StringUtils; import mx.events.FlexEvent; public var Stageheight:Number = flash.system.Capabilities.screenResolutionY; public var Stagewidth:Number = flash.system.Capabilities.screenResolutionX; public var clientSocket:CustomeSocket; public var selfUserName:String; public var selfUserNum:String; [Bindable] [Embed(source="assets/images/mhead_online.png")] public var manHeadOnline:Class; [Bindable] [Embed(source="assets/images/mhead_offline.png")] public var manHeadOffline:Class; [Bindable] [Embed(source="assets/images/whead_online.png")] public var womanHeadOnline:Class; [Bindable] [Embed(source="assets/images/whead_offline.png")] public var womanHeadOffline:Class; [Bindable] [Embed(source="assets/swf/mhead_active.swf")] public var manHeadActive:Class; [Bindable] [Embed(source="assets/swf/whead_active.swf")] public var womanHeadActive:Class; public var openWinUserNum:Array = new Array(); [Bindable] public var friendXML:XML = null; private function init(e:FlexEvent):void { this.move(Stagewidth/2-this.width/2,Stageheight/2-this.height/2); //(this当前窗口) this.showStatusBar = false; this.addEventListener(Event.ADDED_TO_STAGE, function(e:*):void { bgLogin.addEventListener(MouseEvent.MOUSE_DOWN, winMove); }); clientSocket = new CustomeSocket("127.0.0.1", 7073); DispatchEvent.getInstance().addEventListener(SocketEvent.CONNECT, socketHandle); //监听Socket因错误连接失败事件 DispatchEvent.getInstance().addEventListener(SocketEvent.IO_ERROR, socketHandle); DispatchEvent.getInstance().addEventListener(SocketEvent.NOTICE_RECEIVE , serverNotice); } private function serverNotice(event:SocketEvent):void{ var ph:PackageHead = PackageHead(event.data.ph); var jsonObj:Object = com.adobe.serialization.json.JSON.decode(ph.packageBodyContent); if(ph.messageCommand == MessageType.SEND_MESSAGE_ACK_NOTICE){ var messageXML:XML = friendXML.node.node.(@userNum == jsonObj.senderNum)[0]; if(messageXML != null && !this.isExitArray(jsonObj.senderNum)){ messageXML.@message = jsonObj.sendInfo; } } if(ph.messageCommand == MessageType.USER_ON_OFF_LINE_NOTICE){ var userNum:String = jsonObj.userNum; var status:String = jsonObj.status; var xml:XML = friendXML.node.node.(@userNum == userNum)[0]; if(xml != null){ xml.@isOnline = status; } } } private function isExitArray(str:String):Boolean{ var flag:Boolean = false; for(var i:int = 0; i < openWinUserNum.length; i++){ if(openWinUserNum[i] == str){ flag = true; break; } } return flag; } private function winMove(e:MouseEvent):void { nativeWindow.startMove(); } protected function loginHandle(event:MouseEvent):void { this.initSocketConnect(); } /** 初始化连接socket **/ public function initSocketConnect():void{ //初始化socket连接 if(!clientSocket.connected){ clientSocket.connect(); } } private function socketHandle(e:com.bufoon.socket.event.SocketEvent):void{ if(!clientSocket.connected){ return; } var username:String = userTI.text; var password:String = passTI.text; var paramObj:Object = new Object(); paramObj.username = username; paramObj.password = password; var param:String = com.adobe.serialization.json.JSON.encode(paramObj); trace(param); var byteArray:ByteArray = ParamHandle.setSendData(MessageType.LOGIN_VERIFY, param); clientSocket.send(byteArray, doLoginReceive); } private function doLoginReceive(obj:Object):void{ var ph:PackageHead = PackageHead(obj); if(ph.messageCommand == MessageType.LOGIN_VERIFY_ACK){ var jsonObj:Object = com.adobe.serialization.json.JSON.decode(ph.packageBodyContent); if(jsonObj.status == "0"){ this.currentState = "stateMain"; this.move(Stagewidth-300,50); //(this当前窗口) this.width = 280; this.height = 550; username.text = jsonObj.username; selfUserNum = jsonObj.userNum selfUserName = jsonObj.username; trace(jsonObj.userVO); trace(jsonObj.userVO.username); //请求用户列表 var paramObj:Object = new Object(); paramObj.userId = jsonObj.userVO.id; var param:String = com.adobe.serialization.json.JSON.encode(paramObj); var byteArray:ByteArray = ParamHandle.setSendData(MessageType.FRIEND_LIST, param); clientSocket.send(byteArray, doLoginReceive); } else { this.clientSocket.close(); } } //请求用户列表响应 if(ph.messageCommand == MessageType.FRIEND_LIST_ACK){ var jsonArr:Array = com.adobe.serialization.json.JSON.decode(ph.packageBodyContent) as Array; var friendStr:String = "<root label='好友列表'> " for(var i:int = 0; i < jsonArr.length; i++){ var friend:Object = jsonArr[i]; friendStr += "<node id='" + friend.id + "' label='" + friend.name + "' isBranch='true'>" var list:Array = friend.list as Array; for(var j:int = 0; j < list.length; j++){ friendStr += "<node id='" + list[j].id + "' message='' label='" + list[j].username + "' userNum='" + list[j].userNum + "' categoryId='" + friend.id + "' sex='" + list[j].sex + "' isOnline='" + list[j].isOnline + "' signature='" + list[j].signature + "' />"; } friendStr += "</node>"; } friendStr += "</root>"; friendXML = new XML(friendStr); } } protected function tree_doubleClickHandler(event:MouseEvent):void { var node:XML = tree.selectedItem as XML; if(node == null || node.@isBranch == "true"){ return; } var cw:chatWindow = new chatWindow(); cw.chatName = node.@label; cw.chatNum = node.@userNum; cw.open(true); cw.initChatWIN(node.@message, node.@sex); openWinUserNum.push(node.@userNum); node.@message = ""; } //列表树图标处理函数 private function treeIconHandle(item:Object):Class{ var iconClass:Class; var xml:XML= XML(item); if(xml.@isBranch == "true"){ return null; } if(!StringUtils.getInstance().isEmpty(xml.@message)){ if(xml.@sex == "男"){ iconClass = manHeadActive; }else{ iconClass = womanHeadActive; } return iconClass; } if(xml.@sex == "男"){ if(xml.@isOnline == "0"){ iconClass = manHeadOnline; }else { iconClass = manHeadOffline; } } else{ if(xml.@isOnline == "0"){ iconClass = womanHeadOnline; }else { iconClass = womanHeadOffline; } } return iconClass; } ]]> </fx:Script> <s:states> <s:State name="stateLogin"/> <s:State name="stateMain"/> </s:states> <fx:Declarations> <!-- 将非可视元素(比如服务、值对象)放在此处 --> </fx:Declarations> <s:Group width="100%" height="100%" includeIn="stateLogin"> <s:Group width="100%" height="150" id="bgLogin"> <s:Rect id="background" left="0" right="0" top="0" bottom="0" topLeftRadiusX="4" topRightRadiusX="4"> <s:fill> <s:LinearGradient rotation="135"> <s:entries> <s:GradientEntry alpha="1" color="#09C6E6" ratio="0" /> <s:GradientEntry alpha="1" color="#069DD6" ratio="0.77"/> </s:entries> </s:LinearGradient> </s:fill> </s:Rect> <s:HGroup left="6" top="5" gap="4" verticalAlign="middle"> <s:Image source="assets/images/logo.png"/> <s:Label color="#464646" fontWeight="bold" text="BufoonChat"/> </s:HGroup> <s:HGroup right="0" top="0" gap="0"> <components:LocalButton click="minimize()" upIcon="@Embed(source='assets/images/normal_minimize.png')" downIcon="@Embed(source='assets/images/click_minimize.jpg')" overIcon="@Embed(source='assets/images/over_minimize.png')" disabledIcon="@Embed(source='assets/images/click_minimize.jpg')"/> <components:LocalButton click="exit()" upIcon="@Embed(source='assets/images/normal_close.png')" downIcon="@Embed(source='assets/images/close_down.jpg')" overIcon="@Embed(source='assets/images/close.jpg')" disabledIcon="@Embed(source='assets/images/click_minimize.jpg')"/> </s:HGroup> <s:Image source="assets/images/qq_login_head.png" verticalCenter="15" horizontalCenter="0"/> </s:Group> <s:HGroup top="170" horizontalCenter="0" gap="8"> <s:Image source="assets/images/qq.jpg"/> <s:VGroup gap="6" horizontalAlign="right"> <s:TextInput id="userTI" width="194" text="12345" borderAlpha="0.7" height="25" borderColor="#D1D1D1"/> <s:TextInput id="passTI" text="12345" borderAlpha="0.7" height="25" borderColor="#D1D1D1" width="194" displayAsPassword="true"/> <s:HGroup gap="0" verticalAlign="middle"> <components:LocalButton upIcon="@Embed(source='assets/images/check_normal.jpg')" downIcon="@Embed(source='assets/images/check_click.jpg')" overIcon="@Embed(source='assets/images/check_over.jpg')" disabledIcon="@Embed(source='assets/images/up.jpg')"/> <s:Label text="记住密码" color="0x666666" fontSize="13" fontFamily="SimSun"/> </s:HGroup> <s:Group height="60"> <components:LocalButton upIcon="@Embed(source='assets/images/btn_login_normal.jpg')" downIcon="@Embed(source='assets/images/btn_login_click.jpg')" overIcon="@Embed(source='assets/images/btn_login_over.jpg')" disabledIcon="@Embed(source='assets/images/up.jpg')" verticalCenter="0" click="loginHandle(event)" top="10"/> </s:Group> </s:VGroup> </s:HGroup> <s:HGroup bottom="-2" height="40" verticalAlign="middle" left="10"> <s:Label text="注冊" color="#999999" fontFamily="Microsoft YaHei" fontSize="12"/> <s:Label text="|" color="#999999" fontFamily="Microsoft YaHei" fontSize="12"/> <s:Label text="关于" color="#999999" fontFamily="Microsoft YaHei" fontSize="12"/> </s:HGroup> </s:Group> <s:Group width="100%" id="bgMain" height="100%" includeIn="stateMain"> <s:Group id="mainGroup" mouseDown="nativeWindow.startMove();" width="100%" height="110"> <s:Rect left="0" right="0" top="0" bottom="0" topLeftRadiusX="4" topRightRadiusX="4"> <s:fill> <s:LinearGradient rotation="135"> <s:entries> <s:GradientEntry alpha="1" color="#09C6E6" ratio="0" /> <s:GradientEntry alpha="1" color="#069DD6" ratio="0.77"/> </s:entries> </s:LinearGradient> </s:fill> </s:Rect> <s:HGroup left="6" top="5" gap="4" verticalAlign="middle"> <s:Image source="assets/images/logo.png"/> <s:Label color="#464646" fontWeight="bold" text="AirQQ"/> </s:HGroup> <s:HGroup right="0" top="0" gap="0"> <components:LocalButton click="minimize()" upIcon="@Embed(source='assets/images/normal_minimize.png')" downIcon="@Embed(source='assets/images/click_minimize.jpg')" overIcon="@Embed(source='assets/images/over_minimize.png')" disabledIcon="@Embed(source='assets/images/click_minimize.jpg')"/> <components:LocalButton click="exit()" upIcon="@Embed(source='assets/images/normal_close.png')" downIcon="@Embed(source='assets/images/close_down.jpg')" overIcon="@Embed(source='assets/images/close.jpg')" disabledIcon="@Embed(source='assets/images/click_minimize.jpg')"/> </s:HGroup> <s:Image source="@Embed('assets/images/mhead_online.png')" bottom="20" left="5" width="50" height="50" scaleMode="zoom"/> <s:VGroup height="50" left="65" bottom="20" paddingTop="3"> <s:Label id="username" fontFamily="Microsoft YaHei" fontSize="14" fontWeight="bold"/> <s:Label text="不要轻言放弃。否则对不起自己" fontFamily="Microsoft YaHei" fontSize="13"/> </s:VGroup> </s:Group> <mx:Tree id="tree" top="110" bottom="50" width="100%" height="100%" borderVisible="false" contentBackgroundColor="#FEFEFE" iconFunction="treeIconHandle" dataProvider="{friendXML}" focusColor="#6EF5ED" selectedItem="{}" styleName="treeStyle" variableRowHeight="true" wordWrap="true" itemRenderer="com.bufoon.render.MyTreeItemRenderer" labelField="@label" selectionColor="#3DDDDB" showRoot="false" doubleClickEnabled="true" doubleClick="tree_doubleClickHandler(event)"> </mx:Tree> <s:BorderContainer height="50" width="100%" bottom="0" backgroundColor="0xCFE5F8" borderVisible="false"> <components:LocalButton upIcon="@Embed(source='assets/images/search_normal.jpg')" downIcon="@Embed(source='assets/images/search_click.jpg')" overIcon="@Embed(source='assets/images/search_over.jpg')" disabledIcon="@Embed(source='assets/images/click_minimize.jpg')" toolTip="加入好友" verticalCenter="0" left="15"/> </s:BorderContainer> </s:Group> </s:WindowedApplication>

再开聊天窗体(chatWindow.mxml)

<?xml version="1.0" encoding="utf-8"?

> <s:Window xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" systemChrome="none" transparent="true" resizable="false" showStatusBar="false" xmlns:mx="library://ns.adobe.com/flex/mx" width="470" height="490" close="window1_closeHandler(event)" initialize="window1_initializeHandler(event)" skinClass="com.bufoon.skins.WindowAppSkin" xmlns:components="com.bufoon.components.*"> <fx:Style> @namespace s "library://ns.adobe.com/flex/spark"; @namespace mx "library://ns.adobe.com/flex/mx"; .receiveTaStyle{ color:#575AEC; fontSize: 14; fontWeight:bold; } .sendTaStyle{ color:#157528; fontSize: 14; fontWeight:bold; } </fx:Style> <fx:Script> <![CDATA[ import com.adobe.serialization.json.JSON; import com.bufoon.socket.base.MessageType; import com.bufoon.socket.command.ParamHandle; import com.bufoon.socket.event.SocketEvent; import com.bufoon.socket.model.PackageHead; import com.bufoon.socket.util.DispatchEvent; import com.bufoon.util.CustomEvent; import com.bufoon.util.DateUtil; import mx.core.FlexGlobals; import mx.events.FlexEvent; import mx.managers.PopUpManager; import spark.components.RichEditableText; import spark.layouts.VerticalAlign; import flashx.textLayout.elements.FlowLeafElement; import flashx.textLayout.elements.InlineGraphicElement; import flashx.textLayout.elements.ParagraphElement; import flashx.textLayout.elements.SpanElement; import flashx.textLayout.elements.TextFlow; [Bindable] public var chatName:String; [Bindable] public var chatNum:String; private var _chatSendTA:TextFlow; private var _chatReceiveTA:TextFlow; private function get chatSendTA():TextFlow { return RichEditableText(sendTA.textDisplay).textFlow; } private function get chatReceiveTA():TextFlow { return RichEditableText(receiveTA.textDisplay).textFlow; } protected function window1_initializeHandler(event:FlexEvent):void { // TODO Auto-generated method stub groupWin.addEventListener(MouseEvent.MOUSE_DOWN, function move(e:MouseEvent):void{ nativeWindow.startMove(); }); //监听服务器通知 DispatchEvent.getInstance().addEventListener(SocketEvent.NOTICE_RECEIVE , serverNotice); sendTA.addEventListener(FocusEvent.FOCUS_IN, focusInHandlers); } private function focusInHandlers(event:FocusEvent):void { IME.enabled = true; } private function serverNotice(event:SocketEvent):void{ var ph:PackageHead = PackageHead(event.data.ph); if(ph.messageCommand == MessageType.SEND_MESSAGE_ACK_NOTICE){ var jsonObj:Object = com.adobe.serialization.json.JSON.decode(ph.packageBodyContent); if(jsonObj.senderNum != chatNum){ return; } var p:ParagraphElement = new ParagraphElement(); var span:SpanElement = new SpanElement(); p.lineHeight = 30; span.text = jsonObj.sender + "(" + jsonObj.senderNum + ") " + DateUtil.formatDate(); span.setStyle("color", 0x575AEC); span.setStyle("fontWeight", "bold"); span.setStyle("fontSize", "15"); p.addChild(span); chatReceiveTA.addChild(p); var sendInfo:String = jsonObj.sendInfo; var re:RegExp = /\/\d{1,3}\.swf/g; //加上g表示找到全部匹配的字符串 var receivePE:ParagraphElement = new ParagraphElement(); receivePE.lineHeight = 30; receivePE.textIndent = 25; if(re.test(sendInfo)){ //有图片,进行解析 var faceArr:Array = sendInfo.match(re); for(var i:int = 0; i <faceArr.length; i++){ //先替换。好做切割 sendInfo = sendInfo.replace(faceArr[i], "ā" + faceArr[i] + "ā"); } var sendInfoArr:Array = sendInfo.split("ā"); for(var j:int = 0; j <sendInfoArr.length; j++){ var temp:String = sendInfoArr[j]; if(re.test(temp)){ //图片 var img:InlineGraphicElement = new InlineGraphicElement(); img.source = "assets/swf/face" + temp; receivePE.addChild(img); } else{ //文字 var text:SpanElement = new SpanElement(); text.text = temp; text.setStyle("fontSize", 13); receivePE.addChild(text); } } } else { //没图片 var text1:SpanElement = new SpanElement(); text1.text = sendInfo; text1.setStyle("fontSize", 13); receivePE.addChild(text1); } chatReceiveTA.addChild(receivePE); } receiveTA.validateNow(); receiveTA.scroller.verticalScrollBar.value = receiveTA.scroller.verticalScrollBar.maximum; receiveTA.validateNow(); receiveTA.scroller.verticalScrollBar.value = receiveTA.scroller.verticalScrollBar.maximum; } protected function sendMessageHandle(event:MouseEvent):void { var re:RegExp = /^\s*$/; var msg:String = sendTA.text; if(re.test(msg)){//假设输入的字符串仅包括空格、回车或者空。就不能发送信息 sendTA.setFocus(); return; } var p:ParagraphElement = input.deepCopy() as ParagraphElement; var arr:Array = p.mxmlChildren; var sendInfo:String = ""; for(var i:int = 0; i < arr.length; i++){ if(arr[i] is SpanElement){ sendInfo += (arr[i] as SpanElement).text; } if(arr[i] is InlineGraphicElement){ var temp:String = String((arr[i] as InlineGraphicElement).source); temp = temp.substr(temp.lastIndexOf("/")); sendInfo += temp; } } trace(sendInfo); //p.format = chatSendTA.hostFormat; p.textIndent = 25; p.setStyle("fontSize", 13); p.lineHeight=30; p.verticalAlign = VerticalAlign.MIDDLE; var p1:ParagraphElement = new ParagraphElement(); var span:SpanElement = new SpanElement(); span.text = "我(" + chatNum + ") " + DateUtil.formatDate(); span.setStyle("color", 0x157528); span.setStyle("fontWeight", "bold"); span.setStyle("fontSize", "15"); p1.lineHeight = 30; p1.addChild(span); chatReceiveTA.addChild(p1); chatReceiveTA.addChild(p); sendTA.text = ""; sendTA.setFocus(); var obj:Object = new Object(); obj.sender = FlexGlobals.topLevelApplication.selfUserName; obj.receiver = chatName; obj.sendInfo = sendInfo; obj.receiverNum = chatNum; obj.senderNum = FlexGlobals.topLevelApplication.selfUserNum var param:String = com.adobe.serialization.json.JSON.encode(obj); var byteArrays:ByteArray = ParamHandle.setSendData(MessageType.SEND_MESSAGE, param); trace(MessageType.SEND_MESSAGE); FlexGlobals.topLevelApplication.clientSocket.send(byteArrays, receiveHandle); if(receiveTA != null){ receiveTA.validateNow(); receiveTA.scroller.verticalScrollBar.value = receiveTA.scroller.verticalScrollBar.maximum; receiveTA.validateNow(); receiveTA.scroller.verticalScrollBar.value = receiveTA.scroller.verticalScrollBar.maximum; } } private function receiveHandle(obj:Object):void{ var ph:PackageHead = PackageHead(obj); if(ph.messageCommand == MessageType.SEND_MESSAGE_ACK){ trace("发送成功"); } } protected function emotionSelected(event:MouseEvent):void { var ep:FaceWindow = new FaceWindow(); ep.addEventListener(CustomEvent.EMOTION_INSERT, selectEmotionHandler); var point:Point = event.currentTarget.localToGlobal(new Point(ep.x, ep.y-190)); ep.x = point.x; ep.y = point.y; PopUpManager.addPopUp(ep, this); } private function selectEmotionHandler(evt:CustomEvent):void { var img:InlineGraphicElement = new InlineGraphicElement(); img.source = evt.data; var anchor:int = sendTA.selectionAnchorPosition; anchor = (anchor == -1) ?

0 : anchor; var leaf:FlowLeafElement = input.findLeaf(anchor); //获取被拆分的span的子索引。并将表情图标插入到本child后方 var index:int = input.getChildIndex(leaf); input.addChildAt(index + 1, img); //设置光标 sendTA.textDisplay.selectRange(anchor + 1, anchor + 1); } private function get input():ParagraphElement { return chatSendTA.getChildAt(0) as ParagraphElement; } protected function window1_closeHandler(event:Event):void { var arr:Array = FlexGlobals.topLevelApplication.openWinUserNum; for(var i:int; i < arr.length; i++){ if(arr[i] == chatNum){ arr.splice(i-1, 1); } } } public function initChatWIN(sendInfo:String, headType:String):void{ if(headType == "男"){ winHeadImg.source = FlexGlobals.topLevelApplication.manHeadOnline; }else{ winHeadImg.source = FlexGlobals.topLevelApplication.womanHeadOnline; } if(sendInfo == ""){ return; } var p:ParagraphElement = new ParagraphElement(); var span:SpanElement = new SpanElement(); p.lineHeight = 30; span.text = chatName + "(" + chatNum + ") " + DateUtil.formatDate(); span.setStyle("color", 0x575AEC); span.setStyle("fontWeight", "bold"); span.setStyle("fontSize", "15"); p.addChild(span); chatReceiveTA.addChild(p); var sendInfo:String = sendInfo; var re:RegExp = /\/\d{1,3}\.swf/g; //加上g表示找到全部匹配的字符串 var receivePE:ParagraphElement = new ParagraphElement(); receivePE.lineHeight = 30; receivePE.textIndent = 25; if(re.test(sendInfo)){ //有图片,进行解析 var faceArr:Array = sendInfo.match(re); for(var i:int = 0; i <faceArr.length; i++){ //先替换,好做切割 sendInfo = sendInfo.replace(faceArr[i], "ā" + faceArr[i] + "ā"); } var sendInfoArr:Array = sendInfo.split("ā"); for(var j:int = 0; j <sendInfoArr.length; j++){ var temp:String = sendInfoArr[j]; if(re.test(temp)){ //图片 var img:InlineGraphicElement = new InlineGraphicElement(); img.source = "assets/swf/face" + temp; receivePE.addChild(img); } else{ //文字 var text:SpanElement = new SpanElement(); text.text = temp; text.setStyle("fontSize", 13); receivePE.addChild(text); } } } else { //没图片 var text1:SpanElement = new SpanElement(); text1.text = sendInfo; text1.setStyle("fontSize", 13); receivePE.addChild(text1); } chatReceiveTA.addChild(receivePE); } ]]> </fx:Script> <fx:Declarations> <!-- 将非可视元素(比如服务、值对象)放在此处 --> </fx:Declarations> <s:Group id="groupWin" width="100%" height="70"> <s:Rect id="background1" left="0" right="0" top="0" bottom="0" topLeftRadiusX="4" topRightRadiusX="4"> <s:fill> <s:LinearGradient rotation="135"> <s:entries> <s:GradientEntry alpha="1" color="#09C6E6" ratio="0" /> <s:GradientEntry alpha="1" color="#069DD6" ratio="0.77"/> </s:entries> </s:LinearGradient> </s:fill> </s:Rect> <s:HGroup left="6" top="5" gap="4" verticalAlign="middle"> <s:Image id="winHeadImg"/> <s:Label color="#464646" fontWeight="bold" fontFamily="Microsoft YaHei" fontSize="14" text="与 {chatName}({chatNum}) 聊天中"/> </s:HGroup> <s:HGroup right="0" top="0" gap="0"> <components:LocalButton click="minimize()" upIcon="@Embed(source='assets/images/normal_minimize.png')" downIcon="@Embed(source='assets/images/click_minimize.jpg')" overIcon="@Embed(source='assets/images/over_minimize.png')" disabledIcon="@Embed(source='assets/images/click_minimize.jpg')"/> <components:LocalButton click="close()" upIcon="@Embed(source='assets/images/normal_close.png')" downIcon="@Embed(source='assets/images/close_down.jpg')" overIcon="@Embed(source='assets/images/close.jpg')" disabledIcon="@Embed(source='assets/images/click_minimize.jpg')"/> </s:HGroup> </s:Group> <s:VGroup width="100%" top="70"> <s:TextArea id="receiveTA" width="100%" height="240" editable="false" borderVisible="false"/> <s:BorderContainer width="100%" borderVisible="false" height="30" backgroundColor="0xD2D79A"> <s:Image source="@Embed('/assets/emotions/grin.png')" left="10" verticalCenter="0" click="emotionSelected(event)" buttonMode="true"/> <mx:LinkButton right="10" click="receiveTA.text=''" verticalCenter="0" label="清除消息" fontFamily="Microsoft YaHei" fontSize="14" color="0x3A11E1"/> </s:BorderContainer> <s:TextArea id="sendTA" fontSize="13" height="80" width="100%" borderVisible="false" focusAlpha="0"/> <s:BorderContainer borderVisible="false" backgroundColor="0xD2D79A" width="100%" height="32"> <components:LocalButton upIcon="@Embed(source='assets/images/send_normal.png')" downIcon="@Embed(source='assets/images/send_click.png')" overIcon="@Embed(source='assets/images/send_over.png')" disabledIcon="@Embed(source='assets/images/send_over.png')" right="10" verticalCenter="0" click="sendMessageHandle(event)" /> </s:BorderContainer> </s:VGroup> </s:Window>

另一个标胶基本的Socket类

CustomeSocket.as

package com.bufoon.socket
{import com.bufoon.socket.base.BaseSocket;import com.bufoon.socket.command.ParamHandle;import com.bufoon.socket.event.SocketEvent;import com.bufoon.socket.model.PackageHead;import com.bufoon.socket.util.DispatchEvent;import flash.utils.ByteArray;import flash.utils.Endian;public class CustomeSocket{/**Socket实例**/private var socket:BaseSocket;/**主机地址的私有变量**/private var _host:String;/**端口的私有变量**/private var _port:int;/**接收到socket进行回调处理**/private var resultFunction:Function;/**是否已经读取了消息头**/private var isReadHead:Boolean = false;/**消息的最大长度**/private var msgMaxLength:int = 2048;/**消息头长度**/private var headLength:int = 10;/**包体长度**/private var dataLength:int;/**暂时存储byte数组的对象**/private var byteArray:ByteArray ;public function CustomeSocket(host:String , port:int){this._host = host;this._port = port;socket = new BaseSocket();byteArray = new ByteArray();DispatchEvent.getInstance().addEventListener(SocketEvent.READY , onResultFunction);DispatchEvent.getInstance().addEventListener(SocketEvent.RECEIEVED , onReceieveByteArray);}/**建立Socket连接**/public function connect():void{trace(_host + "," + _port)socket.connect(_host, _port);}/**获取当前socket的连接状态**/public function get connected():Boolean{return socket.connected;}/**断开Socket连接**/public function close():void{socket.close();}/**发送clientSocket消息**通过ByteArray,启用*/public function send(byteArray:ByteArray , rsFunction:Function):void{//假设未连接,则返回if(!socket.connected){return;}resultFunction = rsFunction;socket.writeBytes(byteArray);socket.flush();}/**处理指令工厂的回调数据**/private function onResultFunction(event:SocketEvent):void{var cmd:int = event.data.msgReceiveType;//将数据传到resultFunctionresultFunction.call(resultFunction, event.data.ph);}/**接收server端的byteArray信息*用于和C的直接交互*/private function onReceieveByteArray(event:SocketEvent = null):void{//暂时用不到这个byteArray.clear();var ph:PackageHead; //包头if(!isReadHead){ //是否已经读取了消息头if(socket.bytesAvailable >= headLength){//读取包头信息socket.endian = Endian.LITTLE_ENDIAN;ph = new PackageHead();ph.packageHeadLength = socket.readShort();ph.messageType = socket.readByte();ph.contentType = socket.readByte();ph.messageCommand = socket.readShort();ph.packageBodyLength = socket.readInt();dataLength = ph.packageBodyLength;isReadHead = true;}}if(dataLength <= msgMaxLength && isReadHead){if(ph == null || ph.packageHeadLength != 10){return; //包头信息有误,直接不处理}if(socket.bytesAvailable >= dataLength){   //读取包体内容ph.packageBodyContent = socket.readUTFBytes(dataLength);//派发内容if(ph.messageType == 0){ //server推送通知ParamHandle.boardNotice(ph);}else{ //普通的基于请求响应ParamHandle.parseSocketData(ph);}isReadHead = false;}}//假设还有可读字节,递归if(socket.connected){if(socket.bytesAvailable >= headLength){onReceieveByteArray();}}}}}

BaseSocket.as

/*** 自己定义一个主要的Socket继承系统Socket*内部仅重写和对server的响应做了简单的推断*@author bufoon**/
package com.bufoon.socket.base
{import com.bufoon.socket.event.SocketEvent;import com.bufoon.socket.util.DispatchEvent;import flash.events.Event;import flash.events.IOErrorEvent;import flash.events.ProgressEvent;import flash.events.SecurityErrorEvent;import flash.net.ObjectEncoding;import flash.net.Socket;import flash.system.Security;import mx.core.FlexGlobals;import mx.logging.LogEventLevel;public class BaseSocket extends Socket{public function BaseSocket(){/**AMF3仅仅在写Object时才会用到,用于约定通信协议**/this.objectEncoding = ObjectEncoding.AMF3;/**监听Socket载入过程中的全部事件**/this.addEventListener(Event.CONNECT, onConnect);this.addEventListener(Event.CLOSE, onClose);this.addEventListener(IOErrorEvent.IO_ERROR, onIOError);this.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);this.addEventListener(ProgressEvent.SOCKET_DATA, onData);}/**重写connect方法,当中增加策略文件的载入**/override public function connect(host:String , port:int):void{Security.loadPolicyFile("xmlsocket://" + host + ":" + 843);super.connect(host, port);}/**关闭当前socket连接**/override public function close():void{if(this.connected){super.close();}}/**对socket数据的更新进行监听操作**/private function onData(e:ProgressEvent):void{DispatchEvent.getInstance().dispatchEvent(new SocketEvent(SocketEvent.RECEIEVED));}private function onConnect(e:Event):void{trace('连接socketserver成功!

'); DispatchEvent.getInstance().dispatchEvent(new SocketEvent(SocketEvent.CONNECT)); } private function onClose(e:Event):void { trace('断开了和socketserver的连接。'); //Alert.show("已经断开与Socketserver的连接"); DispatchEvent.getInstance().dispatchEvent(new SocketEvent(SocketEvent.CLOSE)); } private function onIOError(e:IOErrorEvent):void { trace('连接server失败。请稍候操作'); //Alert.show("连接Socketserver失败"); DispatchEvent.getInstance().dispatchEvent(new SocketEvent(SocketEvent.IO_ERROR)); } private function onSecurityError(e:SecurityErrorEvent):void { trace('发生沙箱安全错误!'); DispatchEvent.getInstance().dispatchEvent(new SocketEvent(SocketEvent.SECURITY_ERROR_EVENT)); } } }

ParamHandle.as

package com.bufoon.socket.command
{import com.bufoon.socket.base.MessageType;import com.bufoon.socket.event.SocketEvent;import com.bufoon.socket.model.PackageHead;import com.bufoon.socket.util.DispatchEvent;import com.bufoon.util.StringUtils;import flash.events.EventDispatcher;import flash.utils.ByteArray;import flash.utils.Endian;/*** socket发送和接收參数处理类**/public class ParamHandle extends EventDispatcher{private var byteArray:ByteArray;/*** 设置发送的数据包**/public static function setSendData(messageCommand:int = -1, message:String = ""):ByteArray{var byteArray:ByteArray;byteArray = new ByteArray();byteArray.endian = Endian.LITTLE_ENDIAN; //字节序byteArray.writeShort(MessageType.HEAD_LENGTH); //包头大小byteArray.writeByte(MessageType.MESSAGE_TYPE);    //消息类型byteArray.writeByte(MessageType.CONTENT_TYPE);    //内容类型byteArray.writeShort(messageCommand); //消息命令byteArray.writeInt(StringUtils.getInstance().convertStringToByteArray(message).length);   //包体长度if(message.length != 0){byteArray.writeUTFBytes(message); //包体内容}return byteArray;}/*** 派发事件**/ public static function parseSocketData(ph:PackageHead):void{var type:int = ph.messageCommand;DispatchEvent.getInstance().dispatchEvent(new SocketEvent(SocketEvent.READY, {msgReceiveType:type, ph:ph}));}public static function boardNotice(ph:PackageHead):void{DispatchEvent.getInstance().dispatchEvent(new SocketEvent(SocketEvent.NOTICE_RECEIVE, {ph:ph}));}}
}

注:主要就是这几个界面组件和Socket的操作类,还有就是Flex air同一个应用程序不能开多个窗体,这点不知道为什么。要开多个窗体就是新建几个项目名称不一样即可,还有就是导出air安装包的时候将项目-app.xml文件里的ID。name。filename改掉,再安装就能够。

Mina airQQ聊天 client篇(三)相关推荐

  1. 【投屏】Scrcpy源码分析三(Client篇-投屏阶段)

    Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...

  2. 转载 ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(三) 激动人心的时刻到啦,实现1v1聊天...

    ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(三) 激动人心的时刻到啦,实现1v1聊天 看起来挺简单,细节还是很多的,好,接上一篇,我们已经成功连接singalR服务器了, ...

  3. FMS案例开发--视频聊天室(三)

    本文要介绍的内容主要有利用SharedObject来实现聊天文字聊天和在线用户的列表,以及实现语音视频聊天等. 前一篇文章介绍了实现用户注册和登录的功能,本文接着介绍用户注册并成功登录后的相关功能开发 ...

  4. 将台式机组成云服务器_如何用parsec软件搭建自己的云游戏平台 篇三:自己搭建云服务器,一次折腾,全家/全国收益...

    如何用parsec软件搭建自己的云游戏平台 篇三:自己搭建云服务器,一次折腾,全家/全国收益 2020-06-23 19:42:21 6点赞 41收藏 5评论 小编注:此篇文章来自即可瓜分10万金币, ...

  5. unraid应用_unraid 篇三:unraid docker之网页文件管理,强迫症的福音

    unraid 篇三:unraid docker之网页文件管理,强迫症的福音 用久了unraid觉得哪哪都舒服,唯一一点就是文件管理真难受,在列阵里你不能添加删除任何文件/文件夹, 这就显得很蛋疼,我要 ...

  6. 【投屏】Scrcpy源码分析二(Client篇-连接阶段)

    Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...

  7. 用Flutter实现小Q聊天机器人(三)

    用Flutter实现小Q聊天机器人(一) 用Flutter实现小Q聊天机器人(二) 用Flutter实现小Q聊天机器人(三) 用Flutter实现小Q聊天机器人(四) 用Flutter实现小Q聊天机器 ...

  8. 从零搭建直播聊天平台(三.video.js)

    从零搭建直播聊天平台(三.video.js) video.js可以自动在flash和html5播放器之间进行切换,可以兼容到IE8,由于是开源的所以还可已进行二次开发,是一个不错的选择. video. ...

  9. docker omv 防火墙_我的软路由折腾之旅 篇三:在openmediavault上通过Docker实现OPENWRT旁路由功能...

    我的软路由折腾之旅 篇三:在openmediavault上通过Docker实现OPENWRT旁路由功能 2020-06-20 11:28:38 57点赞 415收藏 74评论 你是AMD Yes党?还 ...

最新文章

  1. ASP.NET返回上一页面的实现方法
  2. [编程题]字符串最后一个单词的长度
  3. dell笔记本耳机怎么设置_win10笔记本怎么设置合上盖子不休眠
  4. Django系列之启动入口源码分析
  5. python小城市创业好项目_小城市创业好项目有哪些?
  6. 干活的不如写ppt的吗_干活不如写PPT 这话太真实
  7. IceE-1.3.0的移植过程及错误Time.h:36: error: expected type-specifier before ‘time-转
  8. 椭圆基本概念、定理及性质
  9. 正规word文档文件字体排版格式要求(标准)
  10. python实训报告5000字_实训总结5000字
  11. Socket套接字,一个简单的聊天室案例!
  12. UOS安装 MySQL5.7
  13. 调节阀振动原因分析及解决方案
  14. 华为网络设备查询系统时间及修改系统时间命令
  15. 手游测试之《弱网测试》
  16. ArcGIS按像元栅格值提取栅格
  17. 实施 ORM 的两项要旨:泛型和反射
  18. 【深度学习】池化 (pooling)
  19. 十五天学会Autodesk Inventor,看完这一系列就够了(十一),放样和螺旋扫掠(绘弹簧)
  20. pycharm调试代码139错误的可能解决办法

热门文章

  1. Java基础与提高干货系列——Java反射机制
  2. 精简自己20%的代码
  3. OpenCV之Python学习笔记(1)(2): 图像的载入、显示和保存 图像元素的访问、通道分离与合并
  4. Python变量和对象类型速记手册
  5. Coursera课程Python for everyone:chapter6
  6. 机器学习算法与Python实践之(四)支持向量机(SVM)实现
  7. 四大发明之活字印刷——面向对象思想的胜利
  8. C++运算符重载(成员函数方式)
  9. Android API Level对应Android版本一览表
  10. NetScaler SDWAN 详细配置手册