[Quick-x]cocos2dx下的彩色文本显示--RichLabel
部分关键代码与思路参考 http://www.cocoachina.com/bbs/read.php?tid=218977&page=1
感谢原作者 i7909
代码下载地址:https://github.com/chenquanjun/Quick-x-RichLabel
----------------------
cocos2dx支持的文本显示模式比较单一,不支持图文混排与彩色文本。刚好项目要用到彩色文本,所以写了一个简单的类来实现
- 一、介绍
支持功能
1、图文混排
2、多彩文字混排,支持定义颜色,大小,字体等等属性
3、支持标签内嵌
4、支持自动换行
5、文字fadeIn动画效果(因为是单个字符创建成精灵,可扩展成各种动画效果)
6、支持改变文字,改变文字整体尺寸(其实是宽度)
用于聊天系统、公告或装备描述性文本块(抄原作者的话啦)
还可以用作人物对话,类似Galgame的人物对话(咳咳)
- 二、原理
1、字符串定义/规则
(1)彩色文本以[fontColor=xx]开头,[/fontColor]结尾,若要改变字体大小,字体类型等等,在开头框中加入对应的关键字(不需要加入关键字结尾),例如:
local str = "[fontColor=ff7f00 fontName=ArialRoundedMTBold fontSize = 30]测试[/fontColor]" --创建颜色为ff7f00,字体名为ArialRoundedMTBold,大小为30的测试 label
文本支持参数 fontColor, fontSize, fontName等等
(2)图片以[image=xx.png]开头,[/image]结尾,例如图片支持参数 image(必须), scale
local imageStr = "[image=test.png scale = 1.2][/image]" --创建文件名为test.png的精灵,大小为1.2
(3)支持图文混排,例如
local multiStr = "[fontColor=42426f]哈哈哈哈哈哈!![/fontColor][image=wsk1.png scale=1.3][/image]"
2、实现原理
(1)字符串解析
1.将字符串以标签头[]为关键字分隔字符串
local clumpheadTab = {} -- 标签头 --作用,取出所有格式为[xxxx]的标签头 for w in string.gfind(str, "%b[]") do if string.sub(w,2,2) ~= "/" then-- 去尾 table.insert(clumpheadTab, w) end end
2.标签解析
原理就是将标签的定义属性一个个分离出来然后以table来储存
-- 解析标签 local totalTab = {} for k,ns in pairs(clumpheadTab) do local tab = {} local tStr -- 第一个等号前为块标签名 string.gsub(ns, string.sub(ns, 2, #ns-1), function (w) local n = string.find(w, "=") if n then local temTab = self:stringSplit_(w, " ") -- 支持标签内嵌 for k,pstr in pairs(temTab) do local temtab1 = self:stringSplit_(pstr, "=") local pname = temtab1[1] if k == 1 then tStr = pname end -- 标签头 local js = temtab1[2] local p = string.find(js, "[^%d.]") if not p then js = tonumber(js) end local switchState = { ["fontColor"] = function() tab["fontColor"] = self:convertColor_(js) end, } --switch end local fSwitch = switchState[pname] --switch 方法 --存在switch if fSwitch then --目前只是颜色需要转换 local result = fSwitch() --执行function else --没有枚举 tab[pname] = js return end end end end) if tStr then -- 取出文本 local beginFind,endFind = string.find(str, "%[%/"..tStr.."%]") local endNumber = beginFind-1 local gs = string.sub(str, #ns+1, endNumber) if string.find(gs, "%[") then tab["text"] = gs else string.gsub(str, gs, function (w) tab["text"] = w end) end -- 截掉已经解析的字符 str = string.sub(str, endFind+1, #str) table.insert(totalTab, tab) end end
(2)字符分隔
主要用了Unicode编码的原理分隔字符串,此处就不展开了
简单来说就是每个字符的第一位定义了该字符占据了多少字节。这个可以用排队来理解,如果每个人都一样体型的话,n个人的队列长度是一定的,但是如果有些人长得胖有些人长得瘦,那么队列的长度就不确定了,于是乎我们定了一个规则
最瘦的占1个空间,比较瘦的占2个空间,如此类推,只要在范围内的都固定相同空间,然后在头上贴个标签说明他是哪个分段的。这样的话我们只要不断读取他们的头(-,-),就可以把他们分出来了。
local list = {} local len = string.len(str) local i = 1 while i <= len do local c = string.byte(str, i) local shift = 1 if c > 0 and c <= 127 then shift = 1 elseif (c >= 192 and c <= 223) then shift = 2 elseif (c >= 224 and c <= 239) then shift = 3 elseif (c >= 240 and c <= 247) then shift = 4 end local char = string.sub(str, i, i+shift-1) i = i + shift table.insert(list, char) end return list, len
(3)精灵创建
前面已经把字符串都分割成单个字符了,这里就是简单的创建精灵了,因为只有两种,所以区分一下用label还是sprite来创建即可
--创建精灵 function RichLabel:createSprite_(parseArray) local spriteArray = {} for i, dic in ipairs(parseArray) do local textArr = dic.textArray if #textArr > 0 then --创建文字 local fontName = dic.fontName or self._fontName local fontSize = dic.fontSize or self._fontSize local fontColor = dic.fontColor or self._fontColor for j, word in ipairs(textArr) do local label = CCLabelTTF:create(word, fontName, fontSize) label:setColor(fontColor) spriteArray[#spriteArray + 1] = label self._containLayer:addChild(label) end elseif dic.image then local sprite = CCSprite:create(dic.image) local scale = dic.scale or 1 sprite:setScale(scale) spriteArray[#spriteArray + 1] = sprite self._containLayer:addChild(sprite) else error("not define") end end return spriteArray end
(4)位置调整
字符串解析和位置调整是richlabel实现的关键,主要是通过实际创建精灵然后获得精灵的大小,然后按顺序"填"到指定的区域之中,遇到边界则换行
--获得每个精灵的位置 function RichLabel:getPointOfSprite_(widthArr, heightArr, dimensions) local totalWidth = dimensions.width local totalHight = dimensions.height local maxWidth = 0 local maxHeight = 0 local spriteNum = #widthArr --从左往右,从上往下拓展 local curX = 0 --当前x坐标偏移 local curIndexX = 1 --当前横轴index local curIndexY = 1 --当前纵轴index local pointArrX = {} --每个精灵的x坐标 local rowIndexArr = {} --行数组,以行为index储存精灵组 local indexArrY = {} --每个精灵的行index --计算宽度,并自动换行 for i, spriteWidth in ipairs(widthArr) do local nexX = curX + spriteWidth local pointX local rowIndex = curIndexY local halfWidth = spriteWidth * 0.5 if nexX > totalWidth and totalWidth ~= 0 then --超出界限了 pointX = halfWidth if curIndexX == 1 then --当前是第一个, curX = 0-- 重置x else --不是第一个,当前行已经不足容纳 rowIndex = curIndexY + 1 --换行 curX = spriteWidth end curIndexX = 1 --x坐标重置 curIndexY = curIndexY + 1 --y坐标自增 else pointX = curX + halfWidth --精灵坐标x curX = pointX + halfWidth --精灵最右侧坐标 curIndexX = curIndexX + 1 end pointArrX[i] = pointX --保存每个精灵的x坐标 indexArrY[i] = rowIndex --保存每个精灵的行 local tmpIndexArr = rowIndexArr[rowIndex] if not tmpIndexArr then --没有就创建 tmpIndexArr = {} rowIndexArr[rowIndex] = tmpIndexArr end tmpIndexArr[#tmpIndexArr + 1] = i --保存相同行对应的精灵 if curX > maxWidth then maxWidth = curX end end local curY = 0 local rowHeightArr = {} --每一行的y坐标 --计算每一行的高度 for i, rowInfo in ipairs(rowIndexArr) do local rowHeight = 0 for j, index in ipairs(rowInfo) do --计算最高的精灵 local height = heightArr[index] if height > rowHeight then rowHeight = height end end local pointY = curY + rowHeight * 0.5 --当前行所有精灵的y坐标(正数,未取反) rowHeightArr[#rowHeightArr + 1] = - pointY --从左往右,从上到下扩展,所以是负数 curY = curY + rowHeight --当前行的边缘坐标(正数) if curY > maxHeight then maxHeight = curY end end self._maxWidth = maxWidth self._maxHeight = maxHeight local pointArrY = {} for i = 1, spriteNum do local indexY = indexArrY[i] --y坐标是先读取精灵的行,然后再找出该行对应的坐标 local pointY = rowHeightArr[indexY] pointArrY[i] = pointY end return pointArrX, pointArrY end
- 三、测试
测试(1):改变大小 (浅灰色的是设置的尺寸,深灰色的是文字实际的尺寸)
目前仅实现了宽度适应
测试(2)设置文字测试
测试(3)动画测试