RichLabel基於Cocos2dx+Lua v3.x


RichLabel

簡介


RichLabel基於Cocos2dx+Lua v3.x
解析字符串方面使用了labelparser,它可以將一定格式的字符串,轉換為lua中的表結構
擴展標簽極其簡單,只需添加一個遵守規則的標簽插件即可,無需改動已存在代碼!!!

(標簽插件都在labels文件夾下)

labelparser的詳解
labelparser在github上的源碼
RichLabel在github上的源碼

  • 支持圖片(縮放,旋轉,是否可見)
  • 支持文本屬性(字體,大小,顏色,陰影,描邊,發光)
  • 支持標簽嵌套修飾文本,但是內部標簽不會繼承嵌套標簽的屬性
  • 支持標簽擴展(labels文件夾中可添加標簽支持)
  • 支持漸入動畫,動畫逐字回調
  • 支持設置最大寬度,自動換行布局
  • 支持手動換行,使用'\n'換行
  • 支持設置行間距,字符間距
  • 支持添加debug繪制文字范圍和錨點
  • 支持獲得文字的精靈節點
  • 支持設置標簽錨點,透明度,顏色...
  • 支持遍歷字符,行等
  • 支持獲得任意行的行高

效果:

------------------------------------------------------
------------  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格式,大概了解一下

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.解析出來的表中元素的處理

  • 對於字符串的處理就是將字符串拆分成一個個字符,然后每個字符創建一個Label
  • 對於圖片的處理就是直接創建精靈

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

 

要詳細了解還是去看看代碼吧!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM