RichLabel基於Cocos2dx+Lua v3.x
解析字符串方面使用了labelparser,它能夠將必定格式的字符串,轉換爲lua中的表結構
擴展標籤極其簡單,只需添加一個遵照規則的標籤插件便可,無需改動已存在代碼!!!html
(標籤插件都在labels文件夾下)node
labelparser的詳解
labelparser在github上的源碼
RichLabel在github上的源碼git
效果:github
------------------------------------------------------ ------------ TEST RICH-LABEL ------------------------------------------------------ local test_text = { "<div fontcolor=#ff0000>hello</div><div fontcolor=#00ff00>hello</div><div fontsize=12>你</div><div fontSize=26 fontcolor=#ff00bb>好</div>ok", "<div outline=1,#ff0000 >hello</div>", "<div glow=#ff0000 >hello</div>", "<div shadow=2,-2,0.5,#ff0000 >hello</div>", "hello<img src='res/test.png' scale=0.5 rotate=90 visible=true />world", } for i=1, #test_text do local RichLabel = require("richlabel.RichLabel") local label = RichLabel.new { fontName = "res/msyh.ttf", fontSize = 20, fontColor = cc.c3b(255, 255, 255), maxWidth=200, lineSpace=0, charSpace=0, } label:setString(test_text[i]) label:setPosition(cc.p(380,500-i*30)) label:playAnimation() sceneGame:addChild(label) label:debugDraw() end
因爲解析字符串使用了labelparser,那麼咱們先簡單瞭解一下它,詳細瞭解 傳送門緩存
下面就是使用labelparser解析富文本串用法和產生的table格式,大概瞭解一下curl
local text1 = "hello worldd <div>hello world</div> 你好 <div fontName='nihao' fontColore=#ff33ee>hello,world</div><div></div>" local parsedtable = labelparser.parse(text1) -- output: <parsedtable> = { { content = "hello worldd ", labelname = "div", }, { content = "hello world", labelname = "div", }, { content = " 你好 ", labelname = "div", }, { content = "hello,world", fontname = "nihao", fontsize = "#123456", labelname = "div", }, }
這種格式十分方便咱們處理,它將每一個文本片斷及其屬性分拆成table
,而後按順序組織好返回給咱們
這樣咱們處理就能夠簡單多了函數
首先要說一下,這位前輩靈動君心他也有一個RichLabel,可是沒法知足個人需求,大致思路和他相似。
大致思路:工具
1.解析字符串佈局
2.解析出來的表中元素的處理post
3.將建立好的node佈局便可
第一步
解析字符串,解析字符串使用了個人另外一個開源工具labelparser,直接解析就能夠返回table
第二步
咱們按照順序從表第一項開始處理
每一項均可以得到對應的標籤名,根據標籤名調用對應的標籤的處理函數,同時要將表項中的屬性傳入標籤處理函數
(這個處理函數是以插件形式提供的,很便於擴展)
下面是img標籤解析的的代碼(label_img.lua)
-- -- <img/> 標籤解析 -- return function (self, params, default) if not params.src then return end -- 建立精靈,自動在幀緩存中查找,屏蔽了圖集中加載和直接加載的區別 local sprite = self:getSprite(params.src) if not sprite then self:printf("<img> - create sprite failde") return end if params.scale then sprite:setScale(params.scale) end if params.rotate then sprite:setRotation(params.rotate) end if params.visible ~= nil then sprite:setVisible(params.visible) end return {sprite} end
第三步
此時咱們就得到了設置好屬性的node,咱們要作的就是佈局文本
首先咱們處理換行,何時換行呢?
+ 若是設置了MaxWidth
那麼,每行最大寬度不能超過MaxWidth
,不然就換行
+ 若是文本內容中存在換行符\n
,則直接換行
咱們遍歷全部的node(node存在順序)而後檢測是否爲Label,爲Label則檢測內容是否爲\n
,而後檢測此時累加寬度若超過了最大寬度,則將當前的node直接放到下一行
代碼
-- 自動適應換行處理方法,內部會根據最大寬度設置和'\n'自動換行 -- 若無最大寬度設置則不會自動換行 function RichLabel:adjustLineBreak_(allnodelist, charspace) -- 若是maxwidth等於0則不自動換行 local maxwidth = self._maxWidth if maxwidth <= 0 then maxwidth = 999999999999 end -- 存放每一行的nodes local alllines = {{}, {}, {}} -- 當前行的累加的寬度 local addwidth = 0 local rowindex = 1 local colindex = 0 for _, node in pairs(allnodelist) do colindex = colindex + 1 -- 爲了防止存在縮放後的node local box = node:getBoundingBox() addwidth = addwidth + box.width local totalwidth = addwidth + (colindex - 1) * charspace local breakline = false -- 若累加寬度大於最大寬度 -- 則當前元素爲下一行第一個元素 if totalwidth > maxwidth then rowindex = rowindex + 1 addwidth = box.width -- 累加數值置當前node寬度(爲下一行第一個) colindex = 1 breakline = true end -- 在當前行插入node local curline = alllines[rowindex] or {} alllines[rowindex] = curline table.insert(curline, node) -- 若尚未換行,而且換行符存在,則下一個node直接轉爲下一行 if not breakline and self:adjustContentLinebreak_(node) then rowindex = rowindex + 1 colindex = 0 addwidth = 0 -- 累加數值置0 end end return alllines end -- 判斷是否爲文本換行符 function RichLabel:adjustContentLinebreak_(node) -- 若爲Label則有此方法 if node.getString then local str = node:getString() -- 查看是否爲換行符 if str == "\n" then return true end end return false end
這樣咱們就將混在一塊的node拆分紅一個table中存一行
雖然咱們知道哪些node在第一行,哪些在第二行... ...
可是咱們尚未佈局呢!!!
下面咱們就遍歷每一行,而後調用行佈局函數layoutLine_, 行累加函數還返回行的真實寬度和高度,這樣咱們就能夠計算出最寬的一行,即爲RichLabel的寬度
精簡後代碼
for index, line in pairs(alllines) do local linewidth, lineheight = self:layoutLine_(basepos, line, 1, charspace) -- todo end
行佈局函數(精簡後)
-- 佈局單行中的節點的位置,並返回行寬和行高 function RichLabel:layoutLine_(basepos, line, anchorpy, charspace) local pos_x = basepos.x local pos_y = basepos.y local lineheight = 0 local linewidth = 0 for index, node in pairs(line) do local box = node:getBoundingBox() -- 設置位置 node:setPosition((pos_x + linewidth + box.width/2), pos_y) -- 累加行寬度 linewidth = linewidth + box.width + charspace -- 查找最高的元素,爲行高 if lineheight < box.height then lineheight = box.height end end return linewidth, lineheight end
這樣咱們就一行行佈局好了
問題:文本標籤處理函數,首先要先將字符串拆分紅一個個字符,若是字符串中存在中文那麼直接拆分確定是不行的
拆分字符串,支持Unicode編碼
function RichLabel:stringToChars(str) -- 主要用了Unicode(UTF-8)編碼的原理分隔字符串 -- 簡單來講就是每一個字符的第一位定義了該字符佔據了多少字節 -- UTF-8的編碼:它是一種變長的編碼方式 -- 對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。所以對於英語字母,UTF-8編碼和ASCII碼是相同的。 -- 對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一概設爲10。 -- 剩下的沒有說起的二進制位,所有爲這個符號的unicode碼。 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 end
問題:處理顏色也要說一下,因爲使用HTML方式標記顏色,因此要解析#FF0099這種類型的顏色
這裏須要注意返回的是cc.c4b ,由於咱們可能使用顏色設置Label陰影,而Label的陰影函數要求cc.c4b,可是若是傳入cc.c3b的話,alpha值會爲0,結果就是沒效果!!!
對於這種狀況的詳細討論見 Cocos2dx+lua中Color參數的坑
-- 解析16進制顏色rgb值 function RichLabel:convertColor(xstr) if not xstr then return end local toTen = function (v) return tonumber("0x" .. v) end local b = string.sub(xstr, -2, -1) local g = string.sub(xstr, -4, -3) local r = string.sub(xstr, -6, -5) local red = toTen(r) local green = toTen(g) local blue = toTen(b) if red and green and blue then return cc.c4b(red, green, blue, 255) end end
問題:由於也支持了圖片,因此圖片的加載必需要考慮,不管是從圖集中加載仍是碎圖加載都應該正常
-- 建立精靈,如今幀緩存中找,沒有則直接加載 -- 屏蔽了使用圖集和直接使用碎圖建立精靈的不一樣 function RichLabel:getSprite(filename) local spriteFrameCache = cc.SpriteFrameCache:getInstance() local spriteFrame = spriteFrameCache:getSpriteFrameByName(filename) if spriteFrame then return cc.Sprite:createWithSpriteFrame(spriteFrame) end return cc.Sprite:create(filename) end
要詳細瞭解仍是去看看代碼吧!!!