[转]自制Flash表情聊天
http://blog.163.com/caty_nuaa/blog/static/90390720103811359879/
因为项目需要,自己仿照其他flash游戏中的聊天写了一个带表情的聊天,思路还是基于网上的使用多个TextField来组织聊天信息的显示,每个TextField上再摆放一层用于显示表情的图层,这样做效率还是不错的,不过不可以一次选择多行了,目前也没有做表情的复制功能,所以选中复制的时候只能复制占位符
点此下载工程
ChatTextField.as
package com.lx.chat{ import fl.containers.ScrollPane; import fl.controls.ScrollBarDirection; import fl.controls.ScrollPolicy; import fl.events.ScrollEvent; import flash.display.DisplayObject; import flash.display.Sprite; import flash.events.TextEvent; public class ChatTextField extends Sprite { private var _textContainer:Sprite; private var _scroll:ScrollPane; private var _maxLines:uint; private var _msgArr:Array; public function ChatTextField(w:uint = 300, h:uint = 200):void { _msgArr = new Array(); _textContainer = new Sprite(); _scroll = new ScrollPane(); //_scroll.verticalScrollPolicy = ScrollPolicy.ON; _scroll.horizontalScrollPolicy = ScrollPolicy.OFF; _scroll.focusEnabled = false; // 此设置可以解决text不能选择的问题,也可以解决多个选择的问题 _scroll.source = _textContainer; _scroll.setSize(w, h); _scroll.addEventListener(ScrollEvent.SCROLL, onScroll); //_scroll.useBitmapScrolling = true; addChild(_scroll); maxLines = 30; } public function appendMessage(channel:String, srcName:String, dstName:String, id:uint, content:String, bSelf:Boolean):void { var tf:FaceTextField = new FaceTextField(_scroll.width - _scroll.verticalScrollBar.width); tf.appendChatText(channel, srcName, dstName, id, content, bSelf); _msgArr.push(tf); tf.x = 0; tf.y = _textContainer.height; _textContainer.addChild(tf); if (_msgArr.length > _maxLines) { var obj:DisplayObject = _msgArr.shift(); if (obj) { _textContainer.removeChild(obj); for each (var item:FaceTextField in _msgArr) { item.y = item.y - obj.height; } } } if (_textContainer.height > _scroll.height) { _scroll.update(); _scroll.verticalScrollPosition = _scroll.maxVerticalScrollPosition; } else _scroll.refreshPane(); tf._txtField.addEventListener(TextEvent.LINK, onLink); } private function onScroll(e:ScrollEvent):void { if (e.direction == ScrollBarDirection.VERTICAL) { for each(var item:FaceTextField in _msgArr) { if (item.y + item.height < _scroll.verticalScrollPosition || item.y > _scroll.verticalScrollPosition + this.height) { item.visible = false; } else { item.visible = true; } } } } private function onLink(e:TextEvent):void { this.dispatchEvent(e); } public function set maxLines(n:uint):void { _maxLines = n; } public function get maxLines():uint { return _maxLines; } }}package com.lx.chat{ import com.lx.sprites.Animation; import flash.display.Bitmap; import flash.display.Sprite; import flash.geom.Rectangle; import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextFormatAlign; import flash.utils.*; public class FaceTextField extends Sprite { //the text format private var _fmtName:TextFormat; private var _fmtMsg:TextFormat; //the instance of textfield public var _txtField:TextField; //indicates line height. public var lineHeight:Number; //only contain sprites which are inserted in _textfield public var _spriteContainer:Sprite; //the default textformat of _textfield private var _defaultTextFormat:TextFormat; //the length of the _textfield.text private var _length:int; //specify the sprite&39;s vspace/hspace in _textfield private var _spriteVspace:int; private var _spriteHspace:int; //save the selection begin/end indexes of _textfield private var _selectBegin:int; private var _selectEnd:int; //use it to mark the TextField.replaceText() time during addSprite private var _replacing:Boolean; //regular expression for the chat face private static var _reg:RegExp; //width of the textfield private var _widthTxt:uint; //the text align is center? private var _bCenter:Boolean; /** * trick, a sprite&39;s placeholder * special character: ﹒ unicode is 65106 * special font: Arial */ private var PLACEHOLDER:String = "○"; private var PLACEHOLDER_FONT:String = "Arial"; private var PLACEHOLDER_COLOR:uint = 0x000000; private static const _lt:String="/:"; //dictionary of the emote public static var _dict:Dictionary; public function FaceTextField(width:Number, center:Boolean = false) { _fmtName = new TextFormat("Arial", 12, 0xFF0000, true); _fmtMsg = new TextFormat("Courier New", 12, 0xFFFFFF, false); _bCenter = center; if (center) { _fmtName.align = TextFormatAlign.CENTER; _fmtMsg.align = TextFormatAlign.CENTER; } //initTextField(width); _spriteContainer = new Sprite(); //default lineHeight is 0(ignore) lineHeight = 0; //default sprite vspace/hspace is 2 (changes to 1, 2009-3-3) _spriteHspace = _spriteVspace = 1; _widthTxt = width; } public static function buildRegExp(arrExpress:Array):void { var strReg:String = ""; var arrReg:Array = new Array(); var last:String = arrExpress[arrExpress.length - 1]; for each(var item:* in arrExpress) { arrReg.push(_lt); arrReg.push(item); if (item != last) arrReg.push("|"); } strReg = arrReg.join(""); _reg = new RegExp(strReg, "g"); } /** * initialize the _textfield * @param width */ private function initTextField(width:Number):void { _txtField = new TextField(); _txtField.width = width; _txtField.height = 400; _txtField.multiline = true; _txtField.wordWrap = true; //_txtField.filters = [GlobalFunction.getGlowFilter()]; } /** * 将加工过的字符串转换为包含表情的内容. * @param str * */ public function convertStringToRich(str:String):Object { var array:Array = []; var arrExp:Array = str.match(_reg); var faceObj:Object = {src:null, index:null}; for (var i:int = 0; i < arrExp.length; ++i) { try { var className:String = arrExp[i].slice(_lt.length, arrExp[i].length); var idx:int = str.indexOf(arrExp[i]); faceObj = {src:className, index:idx}; str = str.substring(0, idx) + str.substring(idx + arrExp[i].length, str.length); } catch (err:Error) { continue; } array.push(faceObj); } var result:Object = {mess:str, faces:array}; return result; } public function get numSprite():int { return _spriteContainer.numChildren; } public function getSpriteIndexAt(depth:int):int { var sprite:Bitmap = getSpriteAt(depth); if (sprite) return int(sprite.name); else return -1; } public function getSpriteAt(depth:int):Bitmap { if (depth >= _spriteContainer.numChildren) return null; return _spriteContainer.getChildAt(depth) as Bitmap; } public function getSpriteByName(name:String):Bitmap { return _spriteContainer.getChildByName(name) as Bitmap; } public function removeSpriteByName(name:String):void { var sp:Bitmap = _spriteContainer.getChildByName(name) as Bitmap; if (sp) { _spriteContainer.removeChild(sp); } } public function set spriteVspace(value:int):void { _spriteVspace = value; } public function set spriteHspace(value:int):void { _spriteHspace = value; } public function set defaultTextFormat(format:TextFormat):void { //set the default textformat and effect immediately if (format.letterSpacing == null) format.letterSpacing = 0; _defaultTextFormat = format; _txtField.defaultTextFormat = format; } public function get defaultTextFormat():TextFormat { return _defaultTextFormat; } public function set placeholderColor(value:uint):void { PLACEHOLDER_COLOR = value; } /** * clear all properties, back to original status. */ public function clear():void { _txtField.text = ""; recoverDefaultTextFormat(); _spriteContainer.y = 0; while (_spriteContainer.numChildren > 0) _spriteContainer.removeChildAt(0); } public function appendChatText(channel:String, srcName:String, dstName:String, id:uint, content:String, bSelf:Boolean = false, autoWordWrap:Boolean = true):void { if (_txtField) { clear(); removeChild(_txtField); } initTextField(_widthTxt); var obj:Object = convertStringToRich(content); appendText(channel, srcName, dstName, id, obj.mess, bSelf, _fmtName, _fmtMsg); if (obj.faces) { var _arrEmote:Array = new Array(); for (var i:int = 0; i < obj.faces.length; i++) { var index:int = obj.faces[i].index; if (index == -1) index = obj.mess.length; else if (autoWordWrap) index -= 1; index += _txtField.length - obj.mess.length; //trace("addSprite", object[i].src, index, _textfield.length); //the last sprite should be added before newline character(\n). //if (autoWordWrap && index == _textfield.length) index --; //modified at 12-05-2008 if (autoWordWrap && index >= _txtField.length) index = _txtField.length - 1; //if specify lineHeight(>0), all lines will be same height. index = addPlaceHolder(index); _arrEmote.push({cname:obj.faces[i].src, idx:index}); //addSprite(obj.faces[i].src, index, 0, lineHeight); } for each(var item:Object in _arrEmote) { addSprite(item.cname, item.idx, 0, lineHeight); } } _txtField.height = (int)(_txtField.textHeight) + 5; addChild(_txtField); addChild(_spriteContainer); } private function addPlaceHolder(caretIndex:int):int { //insert a placeholder for target and format it if (caretIndex == -1) caretIndex = _txtField.caretIndex; //fix the bug that supplied index is out of bound, 11-24-2008 //if caretIndex is out of bound, add sprite to the end. if (caretIndex > _txtField.length) { caretIndex = _txtField.length; } var format:TextFormat = getPlaceholderFormat(); _txtField.replaceText(caretIndex, caretIndex, PLACEHOLDER); _txtField.setTextFormat(format, caretIndex); return caretIndex; } /** * add a sprite with a placeholder to the right place * @param target Class or Sprite instance * @param width * @param height * @param caretIndex */ private function addSprite(target:String, caretIndex:int = -1, width:Number = -1, height:Number = -1):void { //create a target sprite var targetClass:Class; var ani:Animation; if (target != null && target != "") { var arrBmp:Array; var str:String = target.toUpperCase(); arrBmp = _dict[str]; ani = new Animation(); ani.fps = 250; var rectPlaceholder:Rectangle = getCharBoundaries(caretIndex); //var h:int = target.height; var x:int = _spriteContainer.x + rectPlaceholder.left - _spriteHspace; var y:int = rectPlaceholder.top + rectPlaceholder.height - arrBmp[0].height - _spriteVspace; for each (var item:* in arrBmp) { ani.push(new Bitmap(item), x, y); } ani.name = String(caretIndex); _spriteContainer.addChild(ani); } } private function appendText(channel:String, srcName:String, dstName:String, id:uint, text:String, bSelf:Boolean, formatName:TextFormat = null, formatText:TextFormat = null):void { recoverDefaultTextFormat(); if (channel) { channel = channel.replace(/</g, "<"); channel = channel.replace(/>/g, ">"); } if (srcName) { srcName = srcName.replace(/</g, "<"); srcName = srcName.replace(/>/g, ">"); } if (dstName) { dstName = dstName.replace(/</g, "<"); dstName = dstName.replace(/>/g, ">"); } if (text) { text = text.replace(/</g, "<"); text = text.replace(/>/g, ">"); } var arrStr:Array = new Array(); if (_bCenter) arrStr.push("<P ALIGN="CENTER">"); else arrStr.push("<P ALIGN="LEFT">"); var addText:String = ""; if ((channel && channel.length > 0) || (srcName && srcName.length > 0)) { if (formatName) { arrStr.push("<FONT FACE=""); arrStr.push(formatName.font as String); arrStr.push("" SIZE=""); arrStr.push(formatName.size.toString()); arrStr.push("" COLOR="#"); arrStr.push((formatName.color as int).toString(16)); arrStr.push("">"); } if (channel && channel.length > 0) arrStr.push(channel); if (srcName && srcName.length > 0) { if (!bSelf) { arrStr.push("<A HREF="event:"); arrStr.push(srcName + "$" + id); arrStr.push("">"); arrStr.push(srcName); arrStr.push("</A>"); } else { arrStr.push(srcName); } } if (dstName && dstName.length > 0) { arrStr.push("悄悄地对"); if (dstName != "你") { arrStr.push("<A HREF="event:"); arrStr.push(dstName + "$" + id); arrStr.push("">"); arrStr.push(dstName); arrStr.push("</A>"); } else arrStr.push(dstName); arrStr.push("说"); } if (srcName && srcName.length > 0) arrStr.push(":"); if (formatName) arrStr.push("</FONT>"); } if (text) { if (formatText) { arrStr.push("<FONT FACE=""); arrStr.push(formatText.font as String); arrStr.push("" SIZE=""); arrStr.push(formatText.size.toString()); arrStr.push("" COLOR="#"); arrStr.push((formatText.color as int).toString(16)); arrStr.push("">"); } arrStr.push(text); if (formatText) arrStr.push("</FONT>"); } arrStr.push("</P>"); //because the carriage return escape character(\r) has some bug in text copying //so change all of them to newline escape character(\n) addText = arrStr.join(""); //addText = addText.split("\r").join("\n"); var arrHtml:Array = new Array(); arrHtml.push(_txtField.htmlText); arrHtml.push(addText); _txtField.htmlText = arrHtml.join(""); } /** * replace the textfield&39;s getCharBoundaries method * get char boundaries in the specify char index * @param charIndex * @return */ private function getCharBoundaries(charIndex:int):Rectangle { var rect:Rectangle = _txtField.getCharBoundaries(charIndex); return rect; } /** * recover the default textformat */ private function recoverDefaultTextFormat():void { if (_defaultTextFormat) defaultTextFormat = _defaultTextFormat; } /** * return a textformat for placeholder corresponding to the given width and height. * @param width * @param height * @return */ private function getPlaceholderFormat():TextFormat { var format:TextFormat = new TextFormat(); format.font = PLACEHOLDER_FONT; format.color = PLACEHOLDER_COLOR; //format.size = height + 2 * _spriteVspace; format.size = 22; format.underline = false; if (_bCenter) format.align = TextFormatAlign.CENTER; //format.letterSpacing = 1; //format.letterSpacing = width - height - _spriteVspace + _spriteHspace; return format; } }}