Mina airQQ聊天 客户端篇(三)

开发工具 (FlashBuilder4.7)

程序类型(Adobe Air)

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

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

大致的实现方式:

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

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

看下客户端的具体实现吧:

首先我们把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)){
						[email protected] = 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){
						[email protected] = 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 || [email protected] == "true"){
					return;
				}
				var cw:chatWindow = new chatWindow();
				cw.chatName = [email protected];
				cw.chatNum = [email protected];
				cw.open(true);
				cw.initChatWIN([email protected], [email protected]);
				openWinUserNum.push([email protected]);
				[email protected] = "";
			}

			//列表树图标处理函数
			private function treeIconHandle(item:Object):Class{
				var iconClass:Class;
				var xml:XML= XML(item);
				if([email protected] == "true"){
					return null;
				}
				if(!StringUtils.getInstance().isEmpty([email protected])){
					if([email protected] == "男"){
						iconClass = manHeadActive;
					}else{
						iconClass = womanHeadActive;
					}
					return iconClass;
				}
				if([email protected] == "男"){
					if([email protected] == "0"){
						iconClass = manHeadOnline;
					}else {
						iconClass = manHeadOffline;
					}
				} else{
					if([email protected] == "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();
		}

		/**发送客户端Socket消息
		 **通过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;
			//将数据传到resultFunction
			resultFunction.call(resultFunction, event.data.ph);
		}

		/**接收服务器端的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){ //服务器推送通知
						ParamHandle.boardNotice(ph);
					}else{ //普通的基于请求响应
						ParamHandle.parseSocketData(ph);
					}
					isReadHead = false;
				}
			}

			//如果还有可读字节,递归
			if(socket.connected){
				if(socket.bytesAvailable >= headLength){
					onReceieveByteArray();
				}
			}

		}
	}

}

BaseSocket.as

/**
 * 自定义一个基本的Socket继承系统Socket
 *内部仅重写和对服务器的响应做了简单的判断
 *@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('连接socket服务器成功!');
			DispatchEvent.getInstance().dispatchEvent(new SocketEvent(SocketEvent.CONNECT));
		}

		private function onClose(e:Event):void
		{
			trace('断开了和socket服务器的连接!');
			//Alert.show("已经断开与Socket服务器的连接");
			DispatchEvent.getInstance().dispatchEvent(new SocketEvent(SocketEvent.CLOSE));
		}

		private function onIOError(e:IOErrorEvent):void
		{
			trace('连接服务器失败,请稍候操作');
			//Alert.show("连接Socket服务器失败");
			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聊天 客户端篇(三),布布扣,bubuko.com

时间: 2024-10-11 13:26:15

Mina airQQ聊天 客户端篇(三)的相关文章

Mina airQQ聊天 服务端篇(二)

Mina聊天服务端实现思路:在用户登录的时候.连接服务端而且验证登录用户,假设成功,则将IoSession保存到map<账号,IoSession>中,而且通知该用户的好友上线,然 后再请求好友列表:若不成功,则断开连接. 自己定义协议格式:包头+包体 包头(10字节):包头长度(short)+ 消息类型(byte)+ 内容类型(byte) +  消息命令(short)+ 包体长度(int) 包体:JSON字符串 自己定义编码解码:因为数据在网络传输过程中都是以二进制传输的,所以我们能够自己定义

【局域网聊天客户端篇】基于socket与Qt

前言 暑假把linux下的高级编程和网络编程学习了一遍,学习很重要,但是也得有个练手的地方,所以必须做做项目来认识下自己所学习的知识. 能够找到小伙伴一起做项目也是一件很快乐的事情的,很幸运的有两个小伙伴一起做这个项目,而我正好负责整个客户端模块,她两负责编写服务器的模块. 开始吧: 项目具体描述:做一个可以实现单个服务器响应多客户端私聊和群聊的聊天工具.具体功能越丰富越好. 下面我以我们做项目的形式来讲讲我们的项目的实现. 项目策划 虽然只是私底下做项目,但是我们还是做了个小小的项目时间和项目

搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 (1)

搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 原文地址(英文):http://www.networkcomms.net/creating-a-wpf-chat-client-server-application/ 注意:本教程是相当广泛的,如果你是在短请也看到我们的东西 开始和 如何在几分钟内创建一个客户端服务器应用程序教程. 注2:本例中包括,明显延长进一步证明功能,在包中包含的示例 包下载. 在我们开始之前确保您已经安装了Vis

ArcGIS网络分析之Silverlight客户端路径分析(三)

原文:ArcGIS网络分析之Silverlight客户端路径分析(三) 首先贴上最终的效果图: a.路径查询 2.最近设施点查询 3.服务区分析 说明: 1.以上的示例使用的数据是随意在ArcMap中创建的数据,也就是之前博文新建的数据,这里的单位和比例尺并不是实际的单位和比例尺.所以和底图的显示不一致,这里的底图只是为了增加显示的效果. 2.以上所以的实现基于之前的两篇关于网络分析的博文,在此推荐看一看. 3.以上示例的具体细节将会分别为大家讲解,欢迎大家相互交流,批评指正. 一.路径分析服务

【基于WinForm+Access局域网共享数据库的项目总结】之篇三:Access远程连接数据库和窗体打包部署

篇一:WinForm开发总体概述与技术实现 篇二:WinForm开发扇形图统计和Excel数据导出 篇三:Access远程连接数据库和窗体打包部署 [小记]:最近基于WinForm+Access数据库完成一个法律咨询管理系统.本系统要求类似网页后台管理效果,并且基于局域网内,完成多客户端操作同一数据库,根据权限不同分别执行不同功能模块.核心模块为级联统计类型管理.数据库咨询数据扇形统计.树的操作.咨询数据的管理.手写分页.Excel数据的导出.多用户操作服务器数据等.并支持多用户同时操作,远程连

【Socket编程】篇三

下面我们实现下回声客户端. 所谓"回声",是指客户端向服务器发送一条数据,服务器再将数据原样返回给客户端. 代码相对于 篇一 与 篇二 并没有太多变化.如下所示: 服务器端: #include <cstdio> #include <unistd.h> #include <stdlib.h> #include <cstring> #include <cassert> #include <sys/types.h> #i

Android聊天客户端Demo开源了.基本的聊天功能基本上都有了,数据库也已搭建,服务器用的baiduPush。可以直接拿来用!!。(希望两个手机通信的话,改一下pushid就可以)

Hello: 我是在飞,最近写了个聊天的Android客户端.今天将此demo分享出来.原澳门大家可以到github直接下载.有问题可以联系我. 几点说明: 1:android聊天客户端的demo,包含了im的基本功能. 1.1比如gif动态表情展示.语音.聊天表情.拍照.多图的发送.大图片的处理.listview缓存的处理等. 1.2数据库也已经搭载好,算是个完整项目,可以直接拿来用. 1.3服务器使用的是baidu push服务.(图片暂时没有处理上传服务器,只是上传了本地sdcard的pa

Dollars即时聊天客户端应用源码

这个源码项目是一款Dollars即时聊天客户端应用源码,源码也比较简单的,希望这个案例能够帮到大家的学习和使用. 源码下载: http://code.662p.com/view/6725.html     An Instant Message Client by XMPP on iPhone使用XMPP实现的iPhone上的聊天工具.只完成了一小部分功能. 登录和注册; 获取联系人列表; 添加好友; 接受好友; 与好友聊天; 获取多人聊天房间列表; 加入房间; 房间内多人聊天; 修改个人状态;目

Python基础篇(三)

元组是序列的一种,与列表的区别是,元组是不能修改的. 元组一般是用圆括号括起来进行定义,如下: >>> (1,2,3)[1:2]     (2,) 如果元组中只有一个元素,元组的表示有些奇怪,末尾需要加上一个逗号: >>> (1,2,3)[1:2]     (2,) >>> 3*(3)      9      >>> 3*(3,)      (3, 3, 3) tuple函数 tuple函数用于将任意类型的序列转换为元组: >&