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;
}
}
}
FaceTextField.as
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;
}
}
}