基于 XLUE 实现的 listbox 控件
1. 提供增删查接口,将 obj 作为子控件添加到列表;
2. 提供 Attach/Detach 方法,可以将子控件的事件转发出来;
3. 支持滚动条;
4. 支持鼠标滚轮;
实现过程中的注意点:
1. 使用 ItemObjList 表存储 itemObj , ItemObjList 是一个数组,是 listbox 控件的数据模型;
2. 使用 EventCookieMap 表存储事件回调, Attach 时监听所有 itemObj 的事件,通过 Insert 添加的 itemObj,也要通过 EventCookieMap 表来确定需要监听哪些事件;
3. 鼠标滚轮的实现,子控件鼠标滚轮事件 RouteToFather ,滚动条鼠标滚轮事件重定向到 listbox 控件上;
4. 滚动条的实现,调整位置即可
代码:
simplelistbox.xml
<xlue> <control class="SimpleListBox"> <attr_def> <attr name="VScrollBarNormalBkgID" type="string" /> <attr name="VScrollBarHoverBkgID" type="string" /> <attr name="VScrollBarDownBkgID" type="string" /> </attr_def> <method_def> <AttachEvent file="simplelistbox.xml.lua" func="AttachEvent" /> <DetachEvent file="simplelistbox.xml.lua" func="DetachEvent" /> <InsertItem file="simplelistbox.xml.lua" func="InsertItem" /> <RemoveItem file="simplelistbox.xml.lua" func="RemoveItem" /> <ClearItem file="simplelistbox.xml.lua" func="ClearItem" /> <GetItem file="simplelistbox.xml.lua" func="GetItem" /> <GetItemNum file="simplelistbox.xml.lua" func="GetItemNum" /> </method_def> <event_def> </event_def> <objtemplate> <children> <obj id="list.layout" class="LayoutObject"> <attr> <left>0</left> <top>0</top> <width>father.width</width> <height>father.height</height> <limitchild>1</limitchild> </attr> <children> <obj id="listbox.layout" class="LayoutObject"> <attr> <left>0</left> <top>0</top> <width>father.width-4</width> <height>0</height> </attr> <eventlist> <event name="OnMouseWheel" file="simplelistbox.xml.lua" func="ListLayout_OnMouseWheel" /> </eventlist> </obj> <obj id="vscrollbar" class="TextureObject"> <attr> <left>father.width-4</left> <top>0</top> <width>4</width> <height>0</height> </attr> <eventlist> <event name="OnLButtonDown" file="simplelistbox.xml.lua" func="VScrollBar_OnLButtonDown" /> <event name="OnLButtonUp" file="simplelistbox.xml.lua" func="VScrollBar_OnLButtonUp" /> <event name="OnMouseMove" file="simplelistbox.xml.lua" func="VScrollBar_OnMouseMove" /> <event name="OnMouseLeave" file="simplelistbox.xml.lua" func="VScrollBar_OnMouseLeave" /> <event name="OnMouseWheel" file="simplelistbox.xml.lua" func="ListLayout_OnMouseWheel" redirect="father:listbox.layout"/> </eventlist> </obj> </children> </obj> </children> <eventlist> <event name="OnInitControl" file="simplelistbox.xml.lua" func="OnInitControl" /> <event name="OnDestroy" file="simplelistbox.xml.lua" func="OnDestroy" /> </eventlist> </objtemplate> </control> </xlue>
simplelistbox.xml.lua
------------------ 以下为外部函数 ------------------- function AttachEvent(ctrlObj, eventName, callback) if type(eventName) ~= "string" or type(callback) ~= "function" then return end local attr = ctrlObj:GetAttribute() if attr.EventCookieMap[eventName] == nil then attr.EventCookieMap[eventName] = {} end local cookie = table.maxn(attr.EventCookieMap[eventName]) + 1 attr.EventCookieMap[eventName][cookie] = callback for index,itemObj in ipairs(attr.ItemObjList) do local itemcookie,bRet = itemObj:AttachListener(eventName, true, function(...) for cookie,callback in pairs(attr.EventCookieMap[eventName]) do callback(index, ...) end end) end return cookie end function DetachEvent(ctrlObj, eventName, cookie) if type(eventName) ~= "string" or type(cookie) ~= "number" then return end local attr = ctrlObj:GetAttribute() if attr.EventCookieMap[eventName] == nil then return end attr.EventCookieMap[eventName][cookie] = nil end function InsertItem(ctrlObj, index, obj) if type(index) ~= "number" or obj == nil then return end local attr = ctrlObj:GetAttribute() local listLayout = ctrlObj:GetControlObject("listbox.layout") -- 插入item,调整位置 listLayout:AddChild(obj) local l,t,r,b = obj:GetObjPos() local width,height = r-l,b-t if index <= 1 or #attr.ItemObjList == 0 then -- 插入到最前 for i,itemObj in ipairs(attr.ItemObjList) do local l,t,r,b = itemObj:GetObjPos() itemObj:SetObjPos(l,t+height,r,b+height) end obj:SetObjPos(0,0,width,height) index = 1 elseif index > #attr.ItemObjList then -- 插入到最后 local lastObj = attr.ItemObjList[#attr.ItemObjList] local _,_,_,bottom = lastObj:GetObjPos() obj:SetObjPos(0,bottom,width,bottom+height) index = #attr.ItemObjList + 1 else -- 插入到中间 local preObj = attr.ItemObjList[index-1] local _,_,_,bottom = preObj:GetObjPos() for i=index, #attr.ItemObjList do local itemObj = attr.ItemObjList[i] local l,t,r,b = itemObj:GetObjPos() itemObj:SetObjPos(l,t+height,r,b+height) end obj:SetObjPos(0,bottom,width,bottom+height) end table.insert(attr.ItemObjList, index, obj) -- 调整 listLayout 大小 local lastObj = attr.ItemObjList[#attr.ItemObjList] local _,_,_,bottom = lastObj:GetObjPos() local l,t,r,b = listLayout:GetObjPos() listLayout:SetObjPos(l,t,r,t+bottom) -- 调整 VScrollBar 位置 __AdjustVScrollBarPos(ctrlObj) -- 监听新添加item的事件 for eventName,cookieTable in pairs(attr.EventCookieMap) do if table.maxn(cookieTable) > 0 then obj:AttachListener(eventName, true, function(...) for cookie, callback in pairs(cookieTable) do callback(index, ...) end end) end end -- 监听OnPosChange,当 obj height 改变时要调整其它 itemObj 的位置 obj:AttachListener("OnPosChange", true, function(obj,oldLeft,oldTop,oldRight,oldBottom,newLeft,newTop,newRight,newBottom) local oldHeight,newHeight = oldBottom-oldTop,newBottom-newTop if oldHeight == newHeight then return end local index = nil for i,itemObj in ipairs(attr.ItemObjList) do if itemObj:GetID() == obj:GetID() and obj:GetID() ~= nil then index = i break end end if not index then return end for i=index+1,#attr.ItemObjList do local itemObj = attr.ItemObjList[i] local l,t,r,b = itemObj:GetObjPos() itemObj:SetObjPos(l,t+newHeight-oldHeight,r,b+newHeight-oldHeight) end -- 调整 VScrollBar 位置 __AdjustVScrollBarPos(ctrlObj) end) -- 监听 OnMouseWheel ,在 itemObj 上的鼠标滚轮事件也要被响应 obj:AttachListener("OnMouseWheel", true, function(obj, x, y, distance, flags) obj:RouteToFather() end) end function RemoveItem(ctrlObj, index) if type(index) ~= "number" then return end local attr = ctrlObj:GetAttribute() local listLayout = ctrlObj:GetControlObject("listbox.layout") if #attr.ItemObjList == 0 then return end if index <= 1 then index = 1 elseif index > #attr.ItemObjList then index = #attr.ItemObjList end -- 删除 obj 调整其它 itemObj 位置 local obj = attr.ItemObjList[index] local l,t,r,b = obj:GetObjPos() local width,height = r-l,b-t for i=index+1, #attr.ItemObjList do local itemObj = attr.ItemObjList[i] local l,t,r,b = itemObj:GetObjPos() itemObj:SetObjPos(l,t-height,r,b-height) end listLayout:RemoveChild(obj) table.remove(attr.ItemObjList, index) -- 调整 listLayout 高度 local bottom = 0 local lastObj = attr.ItemObjList[#attr.ItemObjList] if lastObj then _,_,_,bottom = lastObj:GetObjPos() end local l,t,r,b = listLayout:GetObjPos() listLayout:SetObjPos(l,t,r,t+bottom) -- 调整 VScrollBar 位置 __AdjustVScrollBarPos(ctrlObj) end function ClearItem(ctrlObj) local attr = ctrlObj:GetAttribute() local listLayout = ctrlObj:GetControlObject("listbox.layout") listLayout:RemoveAllChild() attr.ItemObjList = {} end function GetItem(ctrlObj, index) if type(index) ~= "number" then return end local attr = ctrlObj:GetAttribute() return attr.ItemObjList[index] end function GetItemNum(ctrlObj) local attr = ctrlObj:GetAttribute() return #attr.ItemObjList end ----------------- 以下为事件处理函数 ------------------ function OnInitControl(ctrlObj) local attr = ctrlObj:GetAttribute() attr.ItemObjList = {} attr.EventCookieMap = {} local vsbarObj = ctrlObj:GetControlObject("vscrollbar") if attr.VScrollBarNormalBkgID then vsbarObj:SetTextureID(attr.VScrollBarNormalBkgID) end end function OnDestroy(ctrlObj) end function ListLayout_OnMouseWheel(obj, x, y, distance, flags) local ctrlObj = obj:GetOwnerControl() local listLayout = ctrlObj:GetControlObject("listbox.layout") local fatherLayout = listLayout:GetParent() local left,top,right,bottom = listLayout:GetObjPos() local _,fatherTop,_,fatherBottom = fatherLayout:GetObjPos() local fatherHeight = fatherBottom-fatherTop -- distance 为正数时, listLayout 向下移动,反之向上移动 local moveDistance = distance/10 if moveDistance > 0 and top + moveDistance > 0 then -- 向下移动到顶了 if top < 0 then listLayout:SetObjPos(left,0,right,bottom-top) end elseif moveDistance < 0 and bottom + moveDistance < fatherHeight then -- 向上移动到底了 if bottom > fatherHeight then listLayout:SetObjPos(left,fatherHeight-(bottom-top),right,fatherHeight) end else listLayout:SetObjPos(left,top+moveDistance,right,bottom+moveDistance) end __AdjustVScrollBarPos(ctrlObj) end function VScrollBar_OnLButtonDown(vsbarObj) vsbarObj:SetCaptureMouse(true) local ctrlObj = vsbarObj:GetOwnerControl() local attr = ctrlObj:GetAttribute() if attr.VScrollBarDownBkgID then vsbarObj:SetTextureID(attr.VScrollBarDownBkgID) end end function VScrollBar_OnLButtonUp(vsbarObj) vsbarObj:SetCaptureMouse(false) local ctrlObj = vsbarObj:GetOwnerControl() local attr = ctrlObj:GetAttribute() if attr.VScrollBarNormalBkgID then vsbarObj:SetTextureID(attr.VScrollBarNormalBkgID) end end function VScrollBar_OnMouseMove(vsbarObj, x, y, flags) if flags == 1 then -- 鼠标左键被按下 local listLayout = vsbarObj:GetOwnerControl():GetControlObject("listbox.layout") local fatherLayout = vsbarObj:GetParent() local _,listTop,_,listBottom = listLayout:GetObjPos() local _,fatherTop,_,fatherBottom = fatherLayout:GetObjPos() local listHeight,fatherHeight = listBottom-listTop,fatherBottom-fatherTop local _,vsbTop,_,vsbBottom = vsbarObj:GetObjPos() local vsbHeight = vsbBottom-vsbTop -- 调整 VScrollBar 的位置 local vsbTop = y if y < 0 then vsbTop = 0 elseif y > fatherHeight - vsbHeight then vsbTop = fatherHeight - vsbHeight end local l,t,r,b = vsbarObj:GetObjPos() if t ~= vsbTop then vsbarObj:SetObjPos(l,vsbTop,r,vsbTop+vsbHeight) end -- 调整 listLayout 的位置 local listTop = 0 listTop = -listHeight * vsbTop/fatherHeight local l,t,r,b = listLayout:GetObjPos() if t ~= listTop then listLayout:SetObjPos(l,listTop,r,listTop+listHeight) end end if flags ~= 1 then local ctrlObj = vsbarObj:GetOwnerControl() local attr = ctrlObj:GetAttribute() if attr.VScrollBarHoverBkgID then vsbarObj:SetTextureID(attr.VScrollBarHoverBkgID) end end end function VScrollBar_OnMouseLeave(vsbarObj) local ctrlObj = vsbarObj:GetOwnerControl() local attr = ctrlObj:GetAttribute() if attr.VScrollBarNormalBkgID then vsbarObj:SetTextureID(attr.VScrollBarNormalBkgID) end end -------------------- 以下为内部函数 ------------------- function __AdjustVScrollBarPos(ctrlObj) local listLayout = ctrlObj:GetControlObject("listbox.layout") local fatherLayout = listLayout:GetParent() local vsbarObj = ctrlObj:GetControlObject("vscrollbar") local _,listTop,_,listBottom = listLayout:GetObjPos() local _,fatherTop,_,fatherBottom = fatherLayout:GetObjPos() local listHeight,fatherHeight = listBottom-listTop,fatherBottom-fatherTop -- 大小 local vsbHeight = 0 if listHeight > fatherHeight then vsbHeight = fatherHeight * fatherHeight/listHeight end -- 位置 local vsbTop = 0 if listHeight > fatherHeight then vsbTop = fatherHeight * (-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 end