Mina airQQ聊天 client篇(三)

时间:2023-03-10 07:24:21
Mina airQQ聊天 client篇(三)

开发工具 (FlashBuilder4.7)

程序类型(Adobe Air)

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

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

Mina airQQ聊天 client篇(三)Mina airQQ聊天 client篇(三)

Mina airQQ聊天 client篇(三)

大致的实现方式:

在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;
//将数据传到resultFunction
resultFunction.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改掉,再安装就能够。