图文混排也是FLASH里一个很古老的话题了,我们不像美国佬那样游戏里面聊天框就是聊天框,全是文字干干净净,也不像日本人发明了并且频繁地使用颜文字。不管是做论坛、做游戏,必定要实现的一点就是带表情对话框。
闲话扯多了,直接进入正题把。
1、TextField
首先,使用TextField的情况下
要想在使用TextField的前提下实现图文混排,一般比较流行的有2种实现方法:
第一种是使用HTML标签,textfield支持简单的HTML标签,比如<img>、<a>等等,在设置了href的a标签的TextField上可以注册一个侦听器侦听TextEvent.LINK事件,其广播的event具有一个text属性,就是href=”event:aaaa”中冒号后的字符串。通过标记和判断字符串可以知道发生了哪个点击事件。而IMG标签的实用性却太差,几乎无法控制图片的定位,也做不到图文同行。
另一种方法是使用双层结构,就是自己搞一个包含了2层的对话框,下面一层是普通的TextField,上面一层是图片层,当遇到需要展示图片的时候,在TextField里用一个占位符代替图片,然后在图片层的相应位置addChild一张图片。网上有关这种解决办法的自制组件有很多,这里有一个比较好的,叫做RichTextField,这个类可以比较完美地实现一般的聊天框对表情的需要:
RichTextField有如下特性:
●在文本末尾追加文本和显示元素。
●在文本任何位置替换(删除)文本和显示元素。
●支持HTML文本和显示元素的混排。
●可动态设置RichTextField的尺寸大小。
●可导入和导出XML格式的文本框内容。
但是这种2层结构有个缺陷,由于表情图片不是处在文本框中,那么当滚动滚动条时,图片只能选择显示or不显示,那么当滚动到某一行中间时,就会出现下图的情况:
(最后一行的图片根据判断已经不显示了,但是文字还在。或者与之相反的情况)
另外如果我们需要聊天框可以自己拉宽、拉长时,也会出现同样的问题。无法像一个真正的文本框那样,处于边缘的文字会被遮住一部分,图片也被遮住一部分。(如下图的最后一行文本所示),排版也经常出现错位现象。
使用该组件还有一个需要注意的地方就是如果你需要改变输入文字的颜色,如果当前输入框中有表情,将会造成表情和光标的错位:
比如我侦听一个点击调色板发出的事件,使用如下侦听器函数
1 2 3 4 5 6 7 8 9 | private function changeInputColor(e:InfoEvent): void { i_txtFormat.color = e.info as uint ; input.textfield.setTextFormat(i_txtFormat); //var xml:XML = input.exportXML(); //input.clear(); //input.importXML(xml); stage.focus = input.textfield; } |
前两行代码用于改变输入框的format,如果将345行注释掉,就会出现表情重叠、光标错位的情况,所以需要每次都把输入框中的内容导出,然后clear掉,在重新导入进去。由于AS对XML的处理速度较慢,所以这也在一定程度上降低了效率。
总之,如果需求简单,那么RichTextField类是一个很好的选择。功能简便也对得起他轻量级的体积.
其次,使用FTE(Flash text engine)和TLF(TextLayout Framework)来实现。
Flash.text.engine包是fp10中新加入的内置包,也就是说如果你的flash使用了该文本引擎,10以下版本的flashplayer是找不到该类的。
2、flash.text.engine
其次,使用FTE(Flash text engine)和TLF(TextLayout Framework)来实现。
Flash.text.engine包是fp10中新加入的内置包,也就是说如果你的flash使用了该文本引擎,10以下版本的flashplayer是找不到该类的。
使用FTE在舞台上创建一段文本的基本流程如下图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //首先创建一个字符串 var txt: String = '我是一条字符串' ; //创建一个格式,并将字符串与格式传给文本元素(TextElement)的构造方法 var format:ElementFormat = new ElementFormat( new FontDescription( 'Verdana' , 12 )); var textEle:TextElement = new TextElement(txt,format); //创建一个文本块,文本块负责储存文本元素,并创建文本行(TextLine)用于显示文本元素 var inputBlock:TextBlock = new TextBlock(textEle); //使用文本块创建文本行,并将其显示在舞台或父容器中 var textline1:TextLine = inputBlock.createTextLine( null , 100 ); //第一个参数null表示该文本行没有上一行,是第一行 textline1.x = 100 ; textline1.y = 100 ; addChild(textline1); |
TextElement类就是一条带颜色、字体、大小的字符串。与它同等级别的类还有GraphicElement(一个图片元素)和GroupElement(一些文字元素和一些图片元素组成的元素),他们都是ContentElement的子类,ContentElement本身无法实例化。
TextBlock类是一个文本块,他是TextLine类的工厂,他创建这些line,并添加他们到舞台上,textBlock.content属性就是这个文本块中包含的内容元素,我们可以将上面提到的3种ContentElement的子类赋给文本块的content属性。由文本块生产的TextLine是继承自DisplayObjectContainer的,这就意味着我们可以把一句话分成不同的行,并且添加到舞台的任何地方,还可以进行事件侦听。
我们可以在flash.text.engine包中看到Adobe对FTE的介绍:
“FTE 提供对文本度量、格式和双向文本的复杂控制的低级别支持。尽管可以使用 FTE 创建和管理简单的文本元素,但设计 FTE 的主要目的在于为开发人员创建文本处理组件提供基础。”
如果我们需要显示一些简单的文本/图文,那么使用FTE是很好的选择,他甚至比传统的Textfield更加低级,更加节省内存。
为了方便使用,而ADOBE又使用FTE的API开发了一套框架,叫做TextLayoutFramework,它位于flashx.textlayout包中,它和其他API不同,是AS3写的一套类库而不是FLASH本地的原生API,因此存在运行效率和带来的文件体积增加的问题。例如TLF通过EditManager类将文本变成可编辑文本,是通过注册KeyboardEvent事件侦听的方式来获取用户按下的字符,然后添加到文本流中的方式实现的。事件侦听势必会带来效率的下降。 TLF整个类库的体积在200K不到,但是ADOBE提供了SWZ文件,这些文件会在第一次下载的时候储存到用户机器里免去二次下载,所以进行RSL的话也不会带来太大体积压力。
3、flashx.textlayout
使用TLF创建一个简单的文本流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 一行字: span:SpanElement --> span.text = “Hello world”; 一张图: g:InlineGraphicElement --> g.source = 路径/位图/内嵌图片等 一个段落:p: ParagraphElement --> p.fontSize = 16 ; p.addChild(span); p.addChild(g); 一个文本流:textFlow:TextFlow --> textFlow.addChild(p); 创建控制器: displayObject = new Sprite(); displayObject.x = _x; displayObject.y = _y myController = new ContainerController(displayObject,_width,_height); 为文本流添加控制器,并更新所有控制器 textFlow.flowComposer.addController(myController); textFlow.flowComposer.updateAllControllers(); |
上面代码中的_x,_y,_width,_height就是你可以控制的文本框位置的和大小。TLF中的updateAllControllers()方法非常重要,在你对文本流做出任何更改后都需要调用这个方法来让改动显示在屏幕上,包括你在可编辑文本中输入文字之后也需要调用这个方法,当然这里Adobe已经帮你在KeyboardEvent的侦听器中实现了。所以我们需要在改变文字颜色、插入表情、清空文本等等之后都调用这个方法来更新文本流的显示。
但是没有必要频繁地调用这个函数,为了提升效率我们只需要在flashplayer每次更新屏幕之前调用就可以了。所以我们可以在文本流发生改变后调用stage.invalidate()函数,这个函数起到一个标记的作用,让flashplayer在下一次屏幕更新的时候自动发送一个Event.RENDER事件,我们只需要侦听这个事件,统一对文本流的显示进行更新就可以了。
1 2 3 4 5 6 7 8 9 10 11 | stage.addEventListener(Event.RENDER,updateControllers); private function updateControllers(e:Event): void { outputFlow.flowComposer.updateAllControllers(); inputFlow.flowComposer.updateAllControllers(); } |
你当然可以直接使用TLF类库来工作,但是ADOBE又分别在FLEX和FLASH PRO中制作了基于TLF的文本框,这才是真正组件级别的使用,他们更加实用,但是会给你的文件带来更大的体积…我们现在是做游戏的聊天框,FLEX组件暂时不讨论,FLASH PRO中的组件位于fl.text.TLFTextField,如果使用FLASH PRO进行编程那么你可以直接看到这个类,如果使用flashBuilder或者fd,你可以在找到在如下路径找到包含了TLFTextField组件的swc文件
“你的Flash Professional安装目录\Adobe Flash CS5.5\ Common\ Configuration\ ActionScript 3.0\ libs\ tlfruntime.swc”
并把这个文件包含到工程库路径中。
该类有一个textFlow属性,我们可以把自己创建的一个TLF文本流赋给一个TLFTextField实例。然后添加这个实例到舞台上。
1 2 3 4 5 | outputFlow :TextFlow; textField = new TLFTextField(); textField.textFlow = outputFlow; |
除此之外,这个类的使用方法非常类似与普通TextField。
下面是使用TLFTextfield组件制作的聊天框:
总结与拓展
我们可以根据上面的讨论,根据不同的需求选择不同的文本结构:
1.简单的用于显示一段不可编辑的文本:使用FTE的TextLine类来完成
2.一个不可以被拉伸缩放的带表情聊天框:使用RichTextField或你自己实现的两层结构
3.支持拉伸缩放功以及更多功能(比如每一行字体都不同、类似古文的竖行排版、输入框撤销/重做等)的带表情聊天框:使用TLF框架。
4.
这里有两张图,上面是使用2层结构实现的,下面是使用TLF实现的。由于TLF是直接将我们输入的表情实例输出出来,每个表情的实例化都在点击插入表情按钮的时刻,所以这些表情都不是同步的,而上面一张的所有表情都是在我们按下回车发送之后同时实例化的,所以表现为同步。
实际使用中如果不在乎表情是否同步,我想应该可以进行一些优化,首先使用逐帧改变bitmapData的位图对象代替MovieClip,然后将同一种表情的所有bitmapData 指向同一个BitmapData实例。