使用 Bolt 实现 GridView 表格控件

用 Bolt 实现了一个表格控件:

1. 提供 Insert,Remove,Get,Set 接口,可以为表格增删数据;

2. 通过  ItemClass, ItemSetDataFunc 属性来指定显示数据所用的 itemObj;

3. 不会每个 data 都创建 itemObj 来显示, 只为需要显示的数据创建 itemObj;

4. 提供 AttachItemEvent 接口,可以监听 itemObj 向外发出的事件;

5. 根据屏幕大小,列数可以自适应;

6. 根据屏幕大小,每一列的宽度可以自适应;


gridview.xml

<xlue>
    <control class="GridView">
        <attr_def>
            <attr name="ItemClass" type="string" />            <!-- 子控件类名 -->
            <attr name="ItemWidth" type="int" />               <!-- 子控件宽度 -->
            <attr name="ItemHeight" type="int" />              <!-- 子控件高度 -->
            <attr name="ItemSetDataFunc" type="string" />      <!-- 子控件 SetData 函数名,GridView 自动调用子控件的 SetData 函数来为子控件设定数据 -->
        
            <attr name="ColumnNum" type="int">
                <default>1</default>
            </attr>
            <attr name="AutoColumnCount" type="bool">          <!-- 列数根据 GridView 控件宽度自适应, 为 true 则 ColumnNum, AutoColumnWidth 属性失效 -->
                <default>false</default>
            </attr>
            <attr name="AutoColumnWidth" type="bool">          <!-- 子控件的宽度根据 GridView 控件宽度自适应, 为 true 则 ItemWidth 属性失效 -->
                <default>false</default>
            </attr>
            
            <attr name="ColumnSpace" type="int">               <!-- 列与列之间的间隙 -->
                <default>0</default>
            </attr>
            <attr name="RowSpace" type="int">                  <!-- 行与行之间的间隙 -->
                <default>0</default>
            </attr>
            
            <attr name="ScrollBarBkg" type="string" />                 <!-- 滚动条背景 texture id -->
            <attr name="ScrollBarSliderNormal" type="string" />        <!-- 滚动条滑块 normal 态 texture id -->
            <attr name="ScrollBarSliderHover" type="string" />         <!-- 滚动条滑块 hover 态 texture id -->
            <attr name="ScrollBarSliderDown" type="string" />          <!-- 滚动条滑块 down 态 texture id -->
        </attr_def>
        <method_def>
            <ResetData  file="GridView.xml.lua" func="ResetData" />
            <InsertData file="GridView.xml.lua" func="InsertData" />
            <RemoveData file="GridView.xml.lua" func="RemoveData" />
            
            <SetData file="GridView.xml.lua" func="SetData" />
            <GetData file="GridView.xml.lua" func="GetData" />
            
            <AttachItemEvent file="GridView.xml.lua" func="AttachItemEvent" /> <!-- 监听子控件的事件 -->
            <DetachItemEvent file="GridView.xml.lua" func="DetachItemEvent" /> <!-- 取消监听子控件的事件 -->
        </method_def>
        <event_def>
        </event_def>
        <objtemplate>
            <children>
                <obj id="container" class="LayoutObject">
                    <attr>
                        <left>0</left>
                        <top>0</top>
                        <width>father.width</width>
                        <height>father.height</height>
                        <limitchild>1</limitchild>
                    </attr>
                    <eventlist>
                        <event name="OnPosChange" file="GridView.xml.lua" func="Container_OnPosChange" />
                        <event name="OnMouseWheel" file="GridView.xml.lua" func="Container_OnMouseWheel" />
                    </eventlist>
                </obj>
                <obj id="vscrollbar.bkg" class="TextureObject">
                    <attr>
                        <left>father.width-12</left>
                        <top>0</top>
                        <width>12</width>
                        <height>father.height</height>
                        <zorder>100</zorder>
                    </attr>
                    <children>
                        <obj id="vscrollbar.slider" class="TextureObject">
                            <attr>
                                <left>2</left>
                                <top>0</top>
                                <width>8</width>
                                <height>0</height>
                                <zorder>1000</zorder>
                            </attr>
                            <eventlist>
                                <event name="OnLButtonDown" file="GridView.xml.lua" func="VScrollBar_OnLButtonDown" />
                                <event name="OnLButtonUp"   file="GridView.xml.lua" func="VScrollBar_OnLButtonUp" />
                                <event name="OnMouseMove"   file="GridView.xml.lua" func="VScrollBar_OnMouseMove" />
                                <event name="OnMouseLeave" file="GridView.xml.lua"  func="VScrollBar_OnMouseLeave" />
                                <event name="OnMouseWheel" file="GridView.xml.lua" func="Container_OnMouseWheel" redirect="father:container" />
                            </eventlist>
                        </obj>
                    </children>
                </obj>
            </children>
            <eventlist>
                <event name="OnInitControl" file="GridView.xml.lua" func="OnInitControl" />
                <event name="OnDestroy" file="GridView.xml.lua" func="OnDestroy" />
            </eventlist>
        </objtemplate>
    </control>
</xlue>

gridview.xml.lua

------------------------------- 以下是外部函数        --------------------------------

-- 重置列表
function ResetData(ctrlObj, dataList)
    local attr = ctrlObj:GetAttribute()
    
    -- 列表置顶
    attr.VirtualTop = 0
    
    -- 先清空,再添加新数据
    ctrlObj:RemoveData(1, #attr.IdexToDataMap)
    ctrlObj:InsertData(1, dataList)
end

-- 在列表指定位置插入数据
function InsertData(ctrlObj, insertIndex, dataList)
    local attr = ctrlObj:GetAttribute()
    
    if insertIndex == nil or dataList == nil then
        return
    end
    if type(dataList) ~= "table" or #dataList == 0 then
        return
    end
    if insertIndex < 1 or insertIndex > #attr.IdexToDataMap + 1 then
        return
    end
    
    -- 更新 IdexToDataMap 表
    for i, data in ipairs(dataList) do
        local dataInfo = {}
        dataInfo.data = data   -- data 可能重复,所以不能当作 key, 这里把 data 放到 dataInfo 里面,就可以用 dataInfo 作 key 了
        table.insert(attr.IdexToDataMap, insertIndex+i-1, dataInfo)
    end
    
    -- 更新 ItemToIdexMap 表
    -- 因为插入了新数据, insertIndex 后面的 dataIndex 增加了 #dataList
    for itemId, dataIndex in pairs(attr.ItemToIdexMap) do
        if dataIndex >= insertIndex then
            attr.ItemToIdexMap[itemId] = dataIndex + #dataList
        end
    end
    
    RefreshUI(ctrlObj)
end

-- 删除数据
function RemoveData(ctrlObj, beginIndex, endIndex)
    local attr = ctrlObj:GetAttribute()
    
    if beginIndex == nil or endIndex == nil then
        return
    end
    if beginIndex < 1 or beginIndex > #attr.IdexToDataMap then
        return
    end
    if endIndex < beginIndex or endIndex > #attr.IdexToDataMap then
        return
    end
    
    -- 更新 IdexToDataMap 表
    local removedDataList = {}
    for i=beginIndex, endIndex do
        table.insert(removedDataList, attr.IdexToDataMap[beginIndex])
        table.remove(attr.IdexToDataMap, beginIndex)
    end
    
    -- 更新 DataToItemMap 表
    local removedItemList = {}
    for _, removedDataInfo in ipairs(removedDataList) do
        if attr.DataToItemMap[removedDataInfo] then
            table.insert(removedItemList, attr.DataToItemMap[removedDataInfo])
            attr.DataToItemMap[removedDataInfo] = nil
        end
    end
    
    -- 更新 ItemToIdexMap 表
    for _, removedItemId in ipairs(removedItemList) do
        if attr.ItemToIdexMap[removedItemId] then
            -- 删掉不用的项
            attr.ItemToIdexMap[removedItemId] = nil
            RecycleItemObj(ctrlObj, removedItemId)
        end
    end
    for itemId, dataIndex in pairs(attr.ItemToIdexMap) do
        if dataIndex >= endIndex then
            -- 因为删除了旧数据, endIndex 后面的 dataIndex 减少了 endIndex-beginIndex+1
            attr.ItemToIdexMap[itemId] = dataIndex - (endIndex-beginIndex+1)
        end
    end
    
    RefreshUI(ctrlObj)
end

-- 设置数据
function SetData(ctrlObj, dataIndex, data)
    local attr = ctrlObj:GetAttribute()
    
    -- 更新数据
    if attr.IdexToDataMap[dataIndex] == nil then
        return
    end
    attr.IdexToDataMap[dataIndex].data = data
    
    -- 更新界面
    local dataInfo = attr.IdexToDataMap[dataIndex]
    local itemId = attr.DataToItemMap[dataInfo]
    if itemId == nil then
        return
    end
    SetItemData(ctrlObj, itemId, data)
end

-- 获取数据
function GetData(ctrlObj, dataIndex)
    local attr = ctrlObj:GetAttribute()
    
    local dataInfo = attr.IdexToDataMap[dataIndex]
    if dataInfo == nil then
        return
    end
    
    return dataInfo.data
end

-- 监听 itemObj 发出的事件
function AttachItemEvent(ctrlObj, eventName, callback)
    local attr = ctrlObj:GetAttribute()
    
    if eventName == nil or callback == nil then
        return
    end
    
    if attr.EventCookieMap[eventName] == nil then
        attr.EventCookieMap[eventName] = {}
    end
    
    -- 分配 cookie
    local cookie = attr.EventCookieMap.CookieNum
    attr.EventCookieMap.CookieNum = cookie + 1
    -- 记录到 EventCookieMap 中
    attr.EventCookieMap[eventName][cookie] = callback
    
    if attr.ItemCookieMap[eventName] ~= nil then
        -- ItemCookieMap[eventName] 不为空,说明 itemObj 已经监听了这个事件
        return cookie
    end
    
    -- 为每个 itemObj 设置 eventName 的监听器
    attr.ItemCookieMap[eventName] = {}
    for _, itemId in pairs(attr.DataToItemMap) do
        local itemObj = ctrlObj:GetControlObject(itemId)
        local itemCookie = itemObj:AttachListener(eventName, true, function(...)
            OnItemEvent(eventName, select(1, ...))
        end)
        
        -- 把 itemId 和 cookie 的对应关系记到表里面
        attr.ItemCookieMap[eventName][itemId] = itemCookie
    end
    
    return cookie
end

-- 取消监听器
function DetachItemEvent(ctrlObj, eventName, cookie)
    local attr = ctrlObj:GetAttribute()
    
    if eventName == nil or cookie == nil then
        return
    end
    if attr.EventCookieMap[eventName] == nil then
        return
    end
    
    -- 去除 cookie 对应的监听器
    attr.EventCookieMap[eventName][cookie] = nil
    
    local count = 0
    for cookie, callback in pairs(attr.EventCookieMap[eventName]) do
        count = count + 1
    end
    if count > 0 then
        return
    end
    
    -- 如果已经没有人监听这个事件了,那么 itemObj 也不用监听这个事件了
    if attr.ItemCookieMap[eventName] == nil then
        return
    end
    for itemId, itemCookie in pairs(attr.ItemCookieMap[eventName]) do
        local itemObj = ctrlObj:GetControlObject(itemId)
        itemObj:RemoveListener(eventName, itemCookie)
    end
    
    attr.ItemCookieMap[eventName] = nil
    attr.EventCookieMap[eventName] = nil
end

------------------------------- 以下是事件函数        --------------------------------

function OnInitControl(ctrlObj)
    local attr = ctrlObj:GetAttribute()
    
    -- 用三个 map 来记录 dataIndex, dataInfo, itemId 三者的关联,便于互相之间快速索引
    attr.IdexToDataMap = {}
    attr.DataToItemMap = {}
    attr.ItemToIdexMap = {}

    -- 虚拟 Top ,指的是列表的第一个 dataInfo 在界面中应该处于的位置,依据这个来计算每个 dataInfo 的 top
    attr.VirtualTop = 0
    
    -- 回收不用的 itemObj
    attr.RecycleItemList = {}
    attr.ItemIdCache = {}
    attr.ItemIdMax = 0
    
    -- 记录外部调用 AttachItemEvent 时分配的 cookie 和 callback
    attr.EventCookieMap = {}
    attr.EventCookieMap.CookieNum = 0  -- cookie 的分配就简单地 +1 好了
    -- 记录每个 itemObj 关注事件所返回的 cookie
    attr.ItemCookieMap = {}
    
    if attr.ScrollBarBkg ~= nil then
        local vsbarBkgObj = ctrlObj:GetControlObject("vscrollbar.bkg")
        vsbarBkgObj:SetTextureID(attr.ScrollBarBkg)
    end
    if attr.ScrollBarSliderNormal ~= nil then
        local vsbarObj = ctrlObj:GetControlObject("vscrollbar.slider")
        vsbarObj:SetTextureID(attr.ScrollBarSliderNormal)
    end
end

function OnDestroy(ctrlObj)

end

function Container_OnPosChange(containerObj, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight, newBottom)
    local ctrlObj = containerObj:GetOwnerControl()
    
    local oldWidth, oldHeight = oldRight-oldLeft, oldBottom-oldTop
    local newWidth, newHeight = newRight-newLeft, newBottom-newTop
    if newWidth == oldWidth and newHeight == oldHeight then
        -- 控件大小未改变,不需要需要刷新
        return
    end
    
    RefreshUI(ctrlObj)
end

function Container_OnMouseWheel(containerObj, x, y, distance, flags)
    local ctrlObj = containerObj:GetOwnerControl()
    local attr = ctrlObj:GetAttribute()
    
    local moveDistance = distance / 5
    attr.VirtualTop = attr.VirtualTop + moveDistance
    
    RefreshUI(ctrlObj)
end

function VScrollBar_OnLButtonDown(vsbarObj, x, y, flags)
    local ctrlObj = vsbarObj:GetOwnerControl()
    local attr = ctrlObj:GetAttribute()
    
    vsbarObj:SetCaptureMouse(true)
    
    if attr.ScrollBarSliderDown then
        vsbarObj:SetTextureID(attr.ScrollBarSliderDown)
    end
    
    attr.VScrollBar_LButtonDown_PosY = y
end

function VScrollBar_OnLButtonUp(vsbarObj, x, y, flags)
    local ctrlObj = vsbarObj:GetOwnerControl()
    local attr = ctrlObj:GetAttribute()
    
    vsbarObj:SetCaptureMouse(false)
    
    if attr.ScrollBarSliderNormal then
        vsbarObj:SetTextureID(attr.ScrollBarSliderNormal)
    end
    
    attr.VScrollBar_LButtonDown_PosY = 0
end

function VScrollBar_OnMouseMove(vsbarObj, x, y, flags)
    local ctrlObj = vsbarObj:GetOwnerControl()
    local attr = ctrlObj:GetAttribute()
    
    if flags == 1 then         -- 鼠标左键被按下
        local moveDistance = y - attr.VScrollBar_LButtonDown_PosY
        moveDistance = - moveDistance
        attr.VirtualTop = attr.VirtualTop + moveDistance
        
        RefreshUI(ctrlObj)
    else
        if attr.ScrollBarSliderHover then
            vsbarObj:SetTextureID(attr.ScrollBarSliderHover)
        end
    end
end

function VScrollBar_OnMouseLeave(vsbarObj, x, y)
    local ctrlObj = vsbarObj:GetOwnerControl()
    local attr = ctrlObj:GetAttribute()
    
    if attr.ScrollBarSliderNormal then
        vsbarObj:SetTextureID(attr.ScrollBarSliderNormal)
    end
end

------------------------------- 以下是私有函数        --------------------------------

function RefreshUI(ctrlObj)
    -- 调整 列数
    AdjustColumnNum(ctrlObj)
    
    -- 调整 列宽度
    AdjustColumnWidth(ctrlObj)
    
    -- 调整 VirtualTop
    AdjustVirtualTop(ctrlObj)

    -- 重新绑定 data 到 itemObj
    RebindDataToItem(ctrlObj)
    
    -- 调整 itemObj 的位置
    AdjustItemPos(ctrlObj)
    
    -- 调整滚动条位置
    AdjustVScrollBarPos(ctrlObj)
end

function AdjustColumnNum(ctrlObj)
    local attr = ctrlObj:GetAttribute()
    local containerObj = ctrlObj:GetControlObject("container")
    
    local l,t,r,b = containerObj:GetObjPos()
    local containerWidth, containerHeight = r-l, b-t
    
    if attr.AutoColumnCount == true then
        attr.ColumnNum = math.floor(containerWidth / (attr.ItemWidth+attr.ColumnSpace))
    end
end

function AdjustColumnWidth(ctrlObj)
    local attr = ctrlObj:GetAttribute()
    local containerObj = ctrlObj:GetControlObject("container")
    
    local l,t,r,b = containerObj:GetObjPos()
    local containerWidth = r-l
    
    if attr.AutoColumnCount == false then
        if attr.AutoColumnWidth == true then
            attr.ItemWidth = math.floor((containerWidth+attr.ColumnSpace) / attr.ColumnNum)
        end
    end
end

function AdjustVirtualTop(ctrlObj)
    local attr = ctrlObj:GetAttribute()
    local containerObj = ctrlObj:GetControlObject("container")

    local virtualBottom = attr.VirtualTop + math.ceil(#attr.IdexToDataMap / attr.ColumnNum) * (attr.ItemHeight+attr.RowSpace)
    local l,t,r,b = containerObj:GetObjPos()
    local containerHeight = b-t
    
    -- 不要让列表底部有间隙
    if virtualBottom < containerHeight then
        attr.VirtualTop = attr.VirtualTop + containerHeight - virtualBottom
    end
    -- 不要让列表顶部有间隙
    if attr.VirtualTop > 0 then
        attr.VirtualTop = 0
    end
end

function RebindDataToItem(ctrlObj)
    local attr = ctrlObj:GetAttribute()
    local containerObj = ctrlObj:GetControlObject("container")
    
    -- 1. 计算哪些 dataInfo 处于可视范围
    local visibleDataMap = {}
    local l,t,r,b = containerObj:GetObjPos()
    local containerHeight = b-t
    local beginRow = math.floor( (0-attr.VirtualTop) / (attr.ItemHeight+attr.RowSpace) )
    local endRow = math.ceil( (containerHeight-attr.VirtualTop) / (attr.ItemHeight+attr.RowSpace) ) + 1
    local beginIndex = beginRow * attr.ColumnNum + 1
    local endIndex = endRow * attr.ColumnNum
    beginIndex = math.min(beginIndex, #attr.IdexToDataMap)
    endIndex = math.min(endIndex, #attr.IdexToDataMap)

    if beginIndex > 0 and endIndex >= beginIndex then
        for dataIndex=beginIndex, endIndex do
            local dataInfo = attr.IdexToDataMap[dataIndex]
            visibleDataMap[dataInfo] = dataIndex
        end
    end
    
    -- 2. 回收不用的 itemObj
    local inVisibleDataMap = {}
    for dataInfo, itemId in pairs(attr.DataToItemMap) do
        if visibleDataMap[dataInfo] == nil then
            inVisibleDataMap[dataInfo] = itemId
        end
    end
    for dataInfo, itemId in pairs(inVisibleDataMap) do
        attr.DataToItemMap[dataInfo] = nil
        attr.ItemToIdexMap[itemId] = nil
        RecycleItemObj(ctrlObj, itemId)
    end
    
    -- 3. 为没有绑定 itemObj 的 dataInfo 进行绑定
    for dataInfo, dataIndex in pairs(visibleDataMap) do
        if attr.DataToItemMap[dataInfo] == nil then
            local itemObj = CreateItemObj(ctrlObj)
            local itemId = itemObj:GetID()
            
            attr.DataToItemMap[dataInfo] = itemId
            attr.ItemToIdexMap[itemId] = dataIndex
            
            -- 初次绑定的 itemObj, 要调用 SetDataFunc
            SetItemData(ctrlObj, itemId, dataInfo.data)
        end
    end
    
    -- 4. 清空不用的 itemObj
    CleanItemObj(ctrlObj)
end

function AdjustItemPos(ctrlObj)
    local attr = ctrlObj:GetAttribute()
    
    local function CalculateItemPos(dataIndex)
        local containerObj = ctrlObj:GetControlObject("container")
        
        local left = math.floor((dataIndex-1) % attr.ColumnNum) * (attr.ItemWidth + attr.ColumnSpace)
        local top = math.floor((dataIndex-1) / attr.ColumnNum) * (attr.ItemHeight + attr.RowSpace) + attr.VirtualTop
        local right = left + attr.ItemWidth
        local bottom = top + attr.ItemHeight
        
        return left, top, right, bottom
    end
    
    for dataInfo, itemId in pairs(attr.DataToItemMap) do
        local itemObj = ctrlObj:GetControlObject(itemId)
        local dataIndex = attr.ItemToIdexMap[itemId]
        local left, top, right, bottom = CalculateItemPos(dataIndex)
        local l,t,r,b = itemObj:GetObjPos()
        if left ~= l or top ~= t or right ~= r or bottom ~= b then
            itemObj:SetObjPos(left, top, right, bottom)
        end
    end
end

function AdjustVScrollBarPos(ctrlObj)
    local attr = ctrlObj:GetAttribute()
    local vsbarObj = ctrlObj:GetControlObject("vscrollbar.slider")
    local vsbarBkgObj = ctrlObj:GetControlObject("vscrollbar.bkg")
    local containerObj = ctrlObj:GetControlObject("container")
    
    local listTop = attr.VirtualTop
    local listBottom = attr.VirtualTop + math.ceil(#attr.IdexToDataMap / attr.ColumnNum) * (attr.ItemHeight+attr.RowSpace)
    local listHeight = listBottom - listTop
    local l,t,r,b = containerObj:GetObjPos()
    local containerHeight = b-t
    local l,t,r,b = vsbarBkgObj:GetObjPos()
    local vsbarBkgHeight = b-t
    
    -- 大小
    local vsbHeight = 0
    if listHeight > containerHeight then
        vsbHeight = vsbarBkgHeight * containerHeight / listHeight
    end
    
    -- 位置
    local vsbTop = 0
    if listHeight > containerHeight then
        vsbTop = vsbarBkgHeight * (-listTop) / listHeight
    end
    
    local l,t,r,b = vsbarObj:GetObjPos()
    if t ~= vsbTop or b-t ~= vsbHeight then
        vsbarObj:SetObjPos(l, vsbTop, r, vsbTop + vsbHeight)
    end
    
    -- 滚动调消失时, 背景也要消失
    if vsbHeight == 0 then
        vsbarBkgObj:SetVisible(false)
    else
        vsbarBkgObj:SetVisible(true)
    end
end

function CreateItemObj(ctrlObj)
    local attr = ctrlObj:GetAttribute()
    local containerObj = ctrlObj:GetControlObject("container")
    local factory = XLGetObject("Xunlei.UIEngine.ObjectFactory")
    
    -- 回收站里有 itemObj, 直接返回
    if #attr.RecycleItemList > 0 then
        local itemObj = attr.RecycleItemList[#attr.RecycleItemList]
        table.remove(attr.RecycleItemList)
        return itemObj
    end
    
    -- 分配 itemId
    local itemId = nil
    if #attr.ItemIdCache > 0 then
        itemId = attr.ItemIdCache[#attr.ItemIdCache]
        table.remove(attr.ItemIdCache)
    else
        itemId = "item" .. attr.ItemIdMax
        attr.ItemIdMax = attr.ItemIdMax + 1
    end
    
    -- 创建 itemObj
    local itemObj = factory:CreateUIObject(itemId, attr.ItemClass)
    containerObj:AddChild(itemObj)
    
    -- 设置监听器
    for eventName, _ in pairs(attr.ItemCookieMap) do
        local itemCookie = itemObj:AttachListener(eventName, true, function(...)
            OnItemEvent(eventName, select(1, ...))
        end)
        
        -- 把 itemObj 和 cookie 的关系记到表里面
        attr.ItemCookieMap[eventName][itemObj] = itemCookie
    end
    
    return itemObj
end

function RecycleItemObj(ctrlObj, itemId)
    local attr = ctrlObj:GetAttribute()
    local itemObj = ctrlObj:GetControlObject(itemId)
    
    if itemObj == nil then
        return
    end
    table.insert(attr.RecycleItemList, itemObj)
end

function CleanItemObj(ctrlObj)
    local attr = ctrlObj:GetAttribute()
    local containerObj = ctrlObj:GetControlObject("container")
    
    for i,itemObj in ipairs(attr.RecycleItemList) do
        -- itemId 用不着了,回收到 ItemIdCache 里下次使用
        local itemId = itemObj:GetID()
        table.insert(attr.ItemIdCache, itemId)
        
        if attr.ItemCookieMap[eventName] then
            attr.ItemCookieMap[eventName][itemObj] = nil
        end
        
        containerObj:RemoveChild(itemObj)
    end
    
    attr.RecycleItemList = {}
end

function SetItemData(ctrlObj, itemId, data)
    local attr = ctrlObj:GetAttribute()
    local itemObj = ctrlObj:GetControlObject(itemId)
    
    if itemObj == nil then
        return
    end
    itemObj[attr.ItemSetDataFunc](itemObj, data)
end

-- itemObj 发出事件时的回调函数
function OnItemEvent(eventName, ...)
    local itemObj = select(1, ...)
    local ctrlObj = itemObj:GetOwnerControl()
    local attr = ctrlObj:GetAttribute()
    local itemId = itemObj:GetID()
    local dataIndex = attr.ItemToIdexMap[itemId]
    
    if attr.EventCookieMap[eventName] == nil then
        return
    end
    
    for cookie, callback in pairs(attr.EventCookieMap[eventName]) do
        callback(ctrlObj, dataIndex, select(1, ...))
    end
end
原文地址:https://www.cnblogs.com/zuibunan/p/4511323.html