部分關鍵代碼與思路參考 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)動畫測試