Cocos2dx-lua ScrollView[三]高级篇

一.概述

本文缩写说明:sv = ScrollView, cell代表ScrollView的一个子节点

本文介绍sv的一种封装类库,来实现快速创建sv,有如下几个优点:

1.item的位置通过参数控制,提高开发效率

2.免去了调用sv的API,提高开发效率

3.分帧创建,提高性能

4.可通过参数控制,复用item类似tableview,提高性能

本文和上一篇Cocos2dx-lua ScrollView[二]进阶篇-CSDN博客

对比有一定相似之处,但也有较多不同,读者可仔细对比代码实现,详细品读,有所取舍和偏爱

二.效果演示

三.代码实现

3.1 说明

a.下面2个模块需要require

b.svCmn是比较复杂的,有必要阅读代码掌握运行原理

c.代码原封不动搬到工程里基本可以正常运行(当然哪里出了问题读者得会排查,本文基本喂饭喂到嘴里了)

d.svCmn经过上线项目验证,可放心使用,在项目中大量推广

3.2 辅助定时器模块:GlobalTimeTicket

Lua 复制代码
GlobalTimeTicket = GlobalTimeTicket or {}
 
auto_id = auto_id or 0
function autoId()
    auto_id = auto_id + 1
    return auto_id
end
-- 获取单例
-- New和不New只是一层一层调用__init和__delete,对于单例没有影响
function GlobalTimeTicket:getInstance()
    if not self.is_init then 
        self.scheduler = cc.Director:getInstance():getScheduler()
        self.schedulers = {}
        self.is_init = true
        self.is_stop = nil
    end
    return self
end
 
-- 定时回调 通用版
-- call_back : function     回调函数    必填
-- interval  : int          时间间隔    默认1 秒
-- limit_time: int          限制次数    默认0 无限
-- with_name : any          定时器标识  默认自增id
-- 返回用于删除的标识
-- simple    : local id = GlobalTimeTicket:getInstance():add(fun) ; GlobalTimeTicket:getInstance():remove(id)
--           : GlobalTimeTicket:getInstance():add(fun, 0.1, 1)              -- 次数达到自动删除
--           : GlobalTimeTicket:getInstance():add(fun, 0.1, 3, "name")      
function GlobalTimeTicket:add(call_back, interval, limit_time, with_name)
    if self.is_stop then return end
    with_name = with_name or autoId()
    if nil == call_back or self.schedulers == nil or nil ~= self.schedulers[with_name] then return end    -- 已经有定义了,不能重复
 
    limit_time = limit_time or 0
    interval = interval or 1
    local schedul_hander = self.scheduler:scheduleScriptFunc(function(dt)
        if self.is_stop then return end
        if call_back ~= nil then
            if limit_time == 1 then 
                self:remove(with_name)
            elseif limit_time > 1 then 
                limit_time = limit_time - 1
            end
            call_back(dt)
        end
    end, interval, false)
    self.schedulers[with_name] = schedul_hander
    return with_name
end
 
-- 删除一个定时器
function GlobalTimeTicket:remove(with_name)
    if with_name == nil then return end
    local schedul_hander = self.schedulers[with_name] 
    if schedul_hander ~= nil then
        self.scheduler:unscheduleScriptEntry(schedul_hander)
        self.schedulers[with_name] = nil 
    end
end
 
-- 清除所有定时器
function GlobalTimeTicket:removeAll()
    for _, v in pairs(self.schedulers) do 
        self.scheduler:unscheduleScriptEntry(v)
    end
    self.schedulers = {}
end
 
function GlobalTimeTicket:hasTicket(with_name)
    local schedul_hander = self.schedulers[with_name] 
    if schedul_hander ~= nil then
        return true
    end
    return false
end
 
function GlobalTimeTicket:getSchedulers()
    return self.schedulers
end
 
-- 停止定时器
function GlobalTimeTicket:stop()
    self.is_stop = true
    self:removeAll()
end

3.3 sv封装模块:svCmn

Lua 复制代码
--[[使用例子
    if not self.svCmn then
        local setting = {
            start_x = 18, space_x = 0,
            start_y = 26, space_y = 6,
            item_width = 686, item_height = 172,
            row = 1, col = 1,
            delay = 4, once_num = 1,
        }
        self.svCmn = svCmn.new(self.scr_con, cc.p(0,0) , ScrollViewDir.vertical, ScrollViewStartPos.top, self.scr_con:getContentSize(), setting, cc.p(0, 0))
        self.svCmn:registerScriptHandlerSingle(handler(self,self.createNewCell), ScrollViewFuncType.CreateNewCell)
        self.svCmn:registerScriptHandlerSingle(handler(self,self.numberOfCells), ScrollViewFuncType.NumberOfCells)
        self.svCmn:registerScriptHandlerSingle(handler(self,self.updateCellByIndex), ScrollViewFuncType.UpdateCellByIndex)
    end
    self.svCmn:reloadData()
]]--

--ScrollView的方法类型
ScrollViewFuncType = {
    UpdateCellByIndex = 1,      -- 更新cell体
    CreateNewCell  =  2,        -- 创建 新的cell 
    NumberOfCells = 3,          -- 返回 数据的数量
    OnCellTouched = 4,          -- 点击cell回调方法
}

svCmn = class("svCmn", function()
    return ccui.Layout:create()
end)

function svCmn:ctor(parent, pos, dir, start_pos, size, setting, ap)
    self.parent = parent
    self.pos = pos or cc.p(0, 0)
    self.dir = dir or ScrollViewDir.vertical
    self.start_pos = start_pos or ScrollViewStartPos.top
    self.size = size or cc.size(100, 100)
    self.ap = ap or cc.p(0, 0)

    self.allCellList = {}       --存放cell对象和其坐标,结构:{x, y, cell}, cell存在重复对象, 长度=cell总数量
    self.cacheList = {}         --保存所有实际创建的cell, 长度=cell最大显示数量
    self.activeCellIdx = {}     --保存每个位置的cell当前是否处于显示状态, 长度=cell总数量
    self.handler = {}           --回调方法
    self.time_show_index = 0    --到时间显示的索引
    self.is_first_init = true   --是否初始化

    self:analysisSetting(setting)
    self:createRootWnd()
end

--要求规定setting的所有变量 都应该在这里定义出来
function svCmn:analysisSetting(setting)
    self.setting        = setting or {}
    self.start_x        = self.setting.start_x or 0             -- 第一个单元的起点X
    self.end_x          = self.setting.end_x or self.start_x    -- 最后一个单元结束X间隔 如果是nil 默认 和 start_x一致
    self.start_y        = self.setting.start_y or 0             -- 第一个单元的起点Y
    self.end_y          = self.setting.end_y or self.start_y    -- 最后一个单元结束Y间隔 如果是nil 默认 和 start_y一致
    self.space_x        = self.setting.space_x or 3             -- 横向间隔空间
    self.space_y        = self.setting.space_y or 3             -- 竖向间隔空间
    self.item_width     = self.setting.item_width or 115        -- 单元的宽度
    self.item_height    = self.setting.item_height or 115       -- 单元的高度

    self.row            = self.setting.row or 5                 -- 行数,作用于水平方向的滚动
    self.col            = self.setting.col or 5                 -- 列数,作用于垂直方向的滚动
    self.delay          = 1 --self.setting.delay or 4           -- 创建延迟时间 强制改为1 
    self.once_num       = self.setting.once_num or 1            -- 每次创建的数量
    self.need_dynamic   = true  -- 默认是无限的
    self.checkovercallback = self.setting.checkovercallback     --滑动回调函数
    self.is_auto_scroll = setting.is_auto_scroll or false       --是否自动判断是否能滚动..个数小于一屏大小时候scroll 不能滚动

    --位置列表 
    self.position_data_list = self.setting.position_data_list

    --固定容器大小 如果有值.将不运算容器大小
    self.container_width = setting.container_width
    self.container_height = setting.container_height

    self.inner_hight_offset = setting.inner_hight_offset or 0 -- 内容高度偏移值(仅对纵向有效)

    --横向的只支持一行的..
    if self.dir == ScrollViewDir.horizontal then
        self.row  = 1
    end
    self:calculationMaxSum()
end

function svCmn:updateSetting(setting)
    if not setting then return end
    for k,v in pairs(setting) do
        self[k] = v
    end
end

--desc:计算一下一屏最多创建的个数
function svCmn:calculationMaxSum()
    local max_sum
    if self.dir == ScrollViewDir.horizontal then 
        max_sum = (math.ceil(self.size.width / (self.item_width + self.space_x)) + 1) * self.row
    else
        max_sum = (math.ceil(self.size.height / (self.item_height + self.space_y)) + 1) * self.col
    end
    self.cacheMaxSize = max_sum
end

function svCmn:createRootWnd()
    self:setContentSize(self.size)
    if not tolua.isnull(self.parent) then
        self.parent:addChild(self)
    end
    self:setPosition(self.pos)
    self:setAnchorPoint(self.ap)
    
    self.scroll_view = createScrollView(self.size.width, self.size.height, 0, 0, self, self.dir) 
    self.container = self.scroll_view:getInnerContainer() 
    self:registerEvent()
end

function svCmn:registerEvent()
    if self.need_dynamic == true then
        self.scroll_view:addEventListener(function(sender, eventType)
            if eventType == ccui.ScrollviewEventType.containerMoved then
                self:checkRectIntersectsRect()
                if self.checkovercallback then
                    self.checkovercallback()
                end
            end
        end)
    end
end

function svCmn:registerScriptHandlerSingle(func, handlerId)
    self.handler[handlerId] = func
end

function svCmn:numberOfCells()
    local cells = ScrollViewFuncType.NumberOfCells
    if not self.handler or not self.handler[cells] then return end
    return self.handler[cells]()
end

--刷新每一个cell 
function svCmn:updateCellByIndex(cell, index)
    if not self.handler[ScrollViewFuncType.UpdateCellByIndex] then return end
    if not cell.index then
        cell.create_index = index
    end
    print("item刷新ing", "item创建时的索引:"..cell.create_index, "item数据索引变化:" .. (cell.index or "无") .. " -> " .. index)
    self.handler[ScrollViewFuncType.UpdateCellByIndex](cell, index)
end

--创建一个新cell
function svCmn:createNewCell(idx)
    if not self.handler[ScrollViewFuncType.CreateNewCell] then return end
    print("createNewCell", idx)
    return self.handler[ScrollViewFuncType.CreateNewCell](self.item_width, self.item_height, idx)
end

-- --点击cell --在createNewCell 自行实现
function svCmn:onCellTouched(cell, index)
    if not self.handler[ScrollViewFuncType.OnCellTouched] then return end
    self.handler[ScrollViewFuncType.OnCellTouched](cell, index)
end


--设置 scrollview 是否可点
function svCmn:setClickEnabled(status)
    self.scroll_view:setTouchEnabled(status)
end
--设置 是否吞噬点击
function svCmn:setSwallowTouches(status)
    self.scroll_view:setSwallowTouches(status)
end

function svCmn:setBounceEnabled( status )
    self.scroll_view:setBounceEnabled(status)
end

--desc:移动的过程中盘点是否不再可视范围,不再的时候移除掉,放到对象池,并且准备下一次创建
function svCmn:checkRectIntersectsRect()
    if self.dir == ScrollViewDir.vertical then
        if self.start_pos == ScrollViewStartPos.top then
            self:checkOverShowByVertical()
        else
            -- 支持ScrollViewStartPos.bottom的了 --by lwc
            self:checkOverShowByVerticalBottom()
        end
    elseif self.dir == ScrollViewDir.horizontal then
        self:checkOverShowByHorizontal()
    end
end

function svCmn:checkOverShowByVertical()
    if not self.allCellList then return end
    local container_y = self.container:getPositionY()
    --计算 视图的上部分和下部分在self.container 的位置
    local bot = -container_y
    local top = self.size.height + bot
    local col_count = math.ceil(#self.allCellList/self.col)
    --下面因为 self.allCellList 是一维数组 所以要换成二维来算
    --活跃cell开始行数
    local activeCellStartRow = 1
    for i=1, col_count do
        local index = 1 + (i-1)* self.col
        local cell = self.allCellList[index]
        activeCellStartRow = i
        if cell and cell.y - self.item_height * 0.5 <= top then
            break
        end
    end
    --活跃cell结束行数
    local activeCellEndRow = col_count
    if bot > 0 then
        for i = activeCellStartRow, col_count do
            local index = 1 + (i-1)* self.col
            local cell = self.allCellList[index]
            if cell and cell.y + self.item_height * 0.5 < bot then
                activeCellEndRow = i - 1
                break
            end
        end
    end
    -- print("@保留--> top --> :"..top .." self.col:"..self.col)
    -- print("@保留--> bot --> :"..bot )
    -- print("@保留--> 开始行: "..activeCellStartRow.."@结束行: "..activeCellEndRow)
    local max_count = self:numberOfCells()
    if max_count then
        for i=1, col_count do
            if i >= activeCellStartRow and i <= activeCellEndRow then
                for k=1, self.col do
                    local index = (i-1) * self.col + k
                    if not self.activeCellIdx[index] then
                        if index <= max_count then
                            self:updateCellAtIndex(index)
                            self.activeCellIdx[index] = true
                        end
                    end    
                end
            else
                for k=1, self.col do
                    local index = (i-1) * self.col + k
                    if index <= max_count then
                        self.activeCellIdx[index] = false
                    end
                end
            end
        end
    end
end

function svCmn:checkOverShowByVerticalBottom()
    if not self.allCellList then return end
    local container_y = self.container:getPositionY()
    --计算 视图的上部分和下部分在self.container 的位置
    local bot = -container_y
    local top = self.size.height + bot
    local col_count = math.ceil(#self.allCellList/self.col)
    --下面因为 self.allCellList 是一维数组 所以要换成二维来算
    --活跃cell开始行数
    local activeCellStartRow = col_count
    for i=col_count, 1,-1 do
        local index = 1 + (i-1)* self.col
        local cell = self.allCellList[index]
        activeCellStartRow = i
        if cell and cell.y - self.item_height * 0.5 <= top then
            break
        end
    end
    --活跃cell结束行数
    local activeCellEndRow = 1
    if bot > 0 then
        for i = activeCellStartRow, 1, -1 do
            local index = 1 + (i-1)* self.col
            local cell = self.allCellList[index]
            if cell and cell.y + self.item_height * 0.5 < bot then
                activeCellEndRow = i + 1
                break
            end
        end
    end
    -- print("@保留--> top --> :"..top .." self.col:"..self.col)
    -- print("@保留--> bot --> :"..bot )
    -- print("@保留--> 开始行: "..activeCellStartRow.."@结束行: "..activeCellEndRow)
    local max_count = self:numberOfCells()
    for i=1, col_count do
        if i <= activeCellStartRow and i >= activeCellEndRow then
            for k=1, self.col do
                local index = (i-1) * self.col + k
                if not self.activeCellIdx[index] then
                    if index <= max_count then
                        self:updateCellAtIndex(index)
                        self.activeCellIdx[index] = true
                    end
                end    
            end
        else
            for k=1, self.col do
                local index = (i-1) * self.col + k
                if index <= max_count then
                    self.activeCellIdx[index] = false
                end
            end
        end
    end
end

function svCmn:checkOverShowByHorizontal()
    if not self.allCellList then return end
    
    local container_x = self.container:getPositionX()
    --计算 视图的左部分和右部分在self.container 的位置
    local top = -container_x 
    local bot = top + self.size.width

    local row_count = #self.allCellList
    --横向的只支持一行
    --活跃cell开始行数
    local activeCellStartRow = 1
    if top > 0 then
        for index=1, row_count do
            local cell = self.allCellList[index]
            activeCellStartRow = index
            if cell and cell.x + self.item_width * 0.5 >= top then
                break
            end
        end
    end
    --活跃cell结束行数
    local activeCellEndRow = row_count
    for index = activeCellStartRow, row_count do
        local cell = self.allCellList[index]
        if cell and cell.x - self.item_width * 0.5 > bot then
            activeCellEndRow = index - 1
            break
        end
    end
    -- print("@保留--> top --> :"..top .." self.row:"..self.row)
    -- print("@保留--> bot --> :"..bot )
    -- print("@保留--> 开始行: "..activeCellStartRow.."@结束行: "..activeCellEndRow)
    local max_count = self:numberOfCells()
    if max_count then
        for index=1, row_count do
            if index >= activeCellStartRow and index <= activeCellEndRow then
                if not self.activeCellIdx[index] then
                    if index <= max_count then
                        self:updateCellAtIndex(index)
                        self.activeCellIdx[index] = true
                    end
                end 
            else
                if index <= max_count then
                    self.activeCellIdx[index] = false
                end
            end
        end
    end
end

--desc:滚动容器移动到指定位置
function svCmn:updateMove(pos)
    local target_pos = self:checkPosition(pos.x, pos.y)
    local move_to = cc.MoveTo:create(0.1, cc.p(target_pos.x, target_pos.y))
    local ease_out = cc.EaseSineOut:create(move_to)
    self.container:runAction(cc.Sequence:create(ease_out))
end

function svCmn:jumpToMove(pos, time, callback)
    local target_pos = self:checkPosition(pos.x, pos.y)
    time = time or 1
    local move_to = cc.MoveTo:create(time, cc.p(target_pos.x, target_pos.y))
    self.container:runAction(cc.Sequence:create(move_to, cc.CallFunc:create(function()
        if callback then
            callback()
        end
    end)))
end 

function svCmn:checkPosition(x, y)
    local _x, _y = self.container:getPositionX(), self.container:getPositionY()
    if self.dir == ScrollViewDir.horizontal then
        _x = _x + x
    elseif self.dir == ScrollViewDir.vertical then
        _y = _y + y
    end
    if _x > 0 then
        _x = 0
    elseif _x < (self.size.width - self.container_size.width) then
        _x = self.size.width - self.container_size.width
    end

    if _y > 0 then
        _y = 0
    elseif _y < (self.size.height - self.container_size.height) then
        _y = self.size.height - self.container_size.height
    end
    return cc.p(_x, _y)
end

--获取当前容器的y位置
function svCmn:getCurContainerPosY()
    if self.container and not tolua.isnull(self.container) then
        return self.container:getPositionY()
    end
end
--获取当前容器的x位置
function svCmn:getCurContainerPosX()
    if self.container and not tolua.isnull(self.container) then
        return self.container:getPositionX()
    end
end

function svCmn:setInnerContainer()
    local number = self:numberOfCells()
    local container_width = self.container_width or self.size.width
    local container_height = self.container_height or self.size.height
    if self.dir == ScrollViewDir.horizontal then  -- 水平
        if self.container_width == nil then
            local num = math.ceil(number / self.row)
            container_width = num * self.item_width + self.end_x + self.start_x + (num - 1) * self.space_x
        end
    else
        if self.container_height == nil then
            local num = math.ceil(number / self.col)
            container_height = num * self.item_height + self.end_y + self.start_y + (num - 1) * self.space_y + self.inner_hight_offset
        end
    end
    container_width = math.max(container_width, self.size.width)
    container_height = math.max(container_height, self.size.height)
    self.container_size = cc.size(container_width, container_height)
    --记录在当前的contariner位置..因为在 setInnerContainerSize 方法会被重置
    self.cur_container_x, self.cur_container_y = self.container:getPosition()
    
    self.scroll_view:setInnerContainerSize(self.container_size)
    if self.start_pos == ScrollViewStartPos.top then
        self.scroll_view:jumpToTop()
    elseif self.start_pos == ScrollViewStartPos.bottom then
        self.scroll_view:jumpToBottom()
    end
end

--刷新当前显示的item数据 (不改变任何位置的,前提数据数量没有改变如果有改变用 reload)
function svCmn:resetCurrentItems()
    for i,v in pairs(self.activeCellIdx) do
        if v then
            self:updateCellAtIndex(i)
        end
    end
end

--根据index 刷新对应索引..如果在显示视图内
function svCmn:resetItemByIndex(index)
    -- body
    if self.activeCellIdx[index] then
        self:updateCellAtIndex(index)
    end
end

--获取活跃中的cell对象
function svCmn:getActiveCellList()
    local list = {}
    for i,v in pairs(self.activeCellIdx) do
        if v and self.allCellList[i] and self.allCellList[i].cell then
            table.insert(list, self.allCellList[i].cell)
        end
    end
    return list
end

--获取index索引对应cell(不管是否活跃)
function svCmn:getCellByIndex(index)
    if  self.allCellList[index] and self.allCellList[index].cell then
        return self.allCellList[index].cell
    end
end

--获取index索引对应cellXY位置(不管是否活跃)
function svCmn:getCellXYByIndex(index)
    if  self.allCellList[index] then
        return self.allCellList[index].x, self.allCellList[index].y 
    end
end

--获取index索引对应cellXY位置(不活跃会返回空)
function svCmn:getActiveCellByIndex(index)
    if self.activeCellIdx[index] and self.allCellList[index] then
        return self.allCellList[index].cell
    end
end
--获取当前容器所在显示窗口的x y位置
function svCmn:getContainerXY()
    if self.container then
        local x, y = self.container:getPosition()
        return x, y
    end
end

--获取当前容器所在显示窗口的x y位置
function svCmn:setContainerXY(x, y)
    if self.container then
        if x and y then
            self.container:setPosition(x,y)
        else
            if x then
                self.container:setPositionX(x) 
            end
            if y then 
                self.container:setPositionY(y)  
            end    
        end
    end
end

--根据索引判断是否活跃中
function svCmn:isActiveByIndex(index)
    if self.activeCellIdx[index] then
        return true
    end
    return false
end
--移动到以选中idenx的位置作为在中间 显示 目前只支持y 方向的
function svCmn:jumpToMoveByIndex(index)

    if not self.allCellList[index] then return end
    local y = self.allCellList[index].y or 0
    local pos = self.container_size.height - (y + self.size.height * 0.5 )
    if pos < 0 then
        pos = 0
    end
    local pos_per = pos * 100 / (self.container_size.height - self.size.height)
    if pos_per ~= pos_per then
        pos_per = 0;
    end
    if pos_per > 100 then
        pos_per = 100
    end
    if pos_per == 100 then
        if self.start_pos == ScrollViewStartPos.top then
            self:checkOverShowByVertical()
        else
            self:checkOverShowByVerticalBottom()
        end
    end
    self.scroll_view:scrollToPercentVertical(pos_per, 0.8, true)
end

--desc:设置数据
--select_idnex 从第几个开始
--@setting: 如果有改变的话
--@is_keep_position 是否保持原来位置 --item数量有变化情况. 无变化请用resetCurrentItems
function svCmn:reloadData(select_index, setting, is_keep_position)
    if setting then
        self:updateSetting(setting)
    end
    local old_width , old_height = 0, 0
    if self.container_size then
        old_width = self.container_size.width
        old_height = self.container_size.height
    end
    self.allCellList = {}
    self.activeCellIdx = {}

    for k, v in ipairs(self.cacheList) do
        --相当于隐藏
        v:setPositionX(-10000)
    end
    --设置容器大小
    self:setInnerContainer()

    local number = self:numberOfCells()
    if number == 0 then
        return
    end

    for i = 1, number do
        local cell = nil 
        if i <= self.time_show_index then
            cell = self:getCacheCellByIndex(i)
        end
        local count = #self.allCellList
        local x, y
        if self.position_data_list then
            local pos = self.position_data_list[count + 1]
            if pos then
                x, y = pos.x, pos.y
            else
                x, y = self:getCellPosition(count + 1)    
            end
        else
            x, y = self:getCellPosition(count + 1)
        end
        local cellData = {cell = cell, x = x, y = y}
        table.insert(self.allCellList, cellData)
    end
    
    if self.is_first_init then
        self:startTimeTicket()
    else
        --如果时间显示索引小于总数 应该显示继续当前定时器 让下面的能显示出来
        if self.time_show_index <= number then
            self:startTimeTicket()
        end
    end

    if is_keep_position then
        --是否保持当前显示位置
        local cur_container_x =  self.cur_container_x or 0
        local cur_container_y =  self.cur_container_y or 0
        if self.dir == ScrollViewDir.vertical then --竖方向
            if self.start_pos == ScrollViewStartPos.top then
                local temp_height = self.container_size.height - old_height
                cur_container_y = cur_container_y -  temp_height
            end
            if cur_container_y > 0 then
                cur_container_y = 0
            elseif cur_container_y < (self.size.height - self.container_size.height) then
                cur_container_y = self.size.height - self.container_size.height
            end
        elseif self.dir == ScrollViewDir.horizontal then --横方向
            if cur_container_x > 0 then
                cur_container_x = 0
            elseif cur_container_x < (self.size.width - self.container_size.width) then
                cur_container_x = self.size.width - self.container_size.width
            end
        end
        self.container:setPosition(cur_container_x, cur_container_y)
        self:checkRectIntersectsRect()
    else
        if select_index == nil then
            local maxRefreshNum 
            if self.dir == ScrollViewDir.horizontal then  -- 水平
                maxRefreshNum = self.cacheMaxSize - self.row
            else
                maxRefreshNum = self.cacheMaxSize - self.col
            end
            local refreshNum = number < maxRefreshNum and number or maxRefreshNum

            for i = 1, refreshNum do
                if i <= self.time_show_index then
                    self:updateCellAtIndex(i)
                end
                self.activeCellIdx[i] = true
            end
        else
            self:selectCellByIndex(select_index)
        end
    end
    if self.is_auto_scroll then
        local cur_max_count = self.cacheMaxSize
        
        if self.dir == ScrollViewDir.horizontal then 
            cur_max_count = cur_max_count - 2 * self.row
        else
            cur_max_count = cur_max_count - 2 * self.col
        end
        if number <= cur_max_count then
            self:setClickEnabled(false)
        else
            self:setClickEnabled(true)
        end
    end
end

--选中index索引对象(如果列表允许 会排序在开始第一位)
function svCmn:selectCellByIndex(index)
    local index = index or 1
    if self.allCellList[index] == nil then
        index = 1
    end
    if self.allCellList[index] == nil then  return end
    --一屏幕显示的最大数量
    local maxRefreshNum 
    if self.dir == ScrollViewDir.horizontal then  -- 水平
        maxRefreshNum = self.cacheMaxSize - self.row
    else
        maxRefreshNum = self.cacheMaxSize - self.col
    end
    local number = self:numberOfCells()
    if number < maxRefreshNum then
        --不够显示一屏幕
        if self.time_show_index == 0 then
            self.time_show_index = index
        end
        for i = 1, number do
            if i <= self.time_show_index then
                self:updateCellAtIndex(i)
            end
            self.activeCellIdx[i] = true
        end
    else
        --列表允许 情况
        if self.dir == ScrollViewDir.horizontal then  -- 水平
            --容器x方向位置
            local container_x
            if index == 1 then
                container_x =  0
            else
                container_x =  -(self.allCellList[index].x - (self.item_width + self.space_x) * 0.5 )
            end
            --容器x方向最大位置
            local max_contariner_x = -(self.container_size.width - self.size.width)

            --这两个值都是负数
            if container_x < max_contariner_x then
                container_x = max_contariner_x
            end
            local show_index = math.floor(math.abs(container_x) / self.item_width) + 1
            if self.time_show_index < show_index then
                self.time_show_index = show_index
            end
            self.container:setPositionX(container_x)
            self:checkRectIntersectsRect()
        else -- 垂直
            local container_y
            if index == 1 then
                container_y = (self.start_y + self.allCellList[index].y + self.item_height * 0.5) - self.size.height 
            else
                container_y = (self.allCellList[index].y + (self.item_height + self.space_y) * 0.5) - self.size.height 
            end
            if container_y < 0 then
                container_y = 0
            end
            local index_1 = math.floor( (self.container_size.height - (container_y + self.size.height)) / self.item_height) + 1
            local show_index = (index_1 - 1) * self.col + 1
            if self.time_show_index < show_index then
                self.time_show_index = show_index
            end
            self.container:setPositionY(- container_y)
            self:checkRectIntersectsRect()
        end
    end

    if index > 0 and index <= self:numberOfCells() then
        local cell = self:getCacheCellByIndex(index)
        cell.index = index
        self.allCellList[index].cell = cell
        self:onCellTouched(cell, index)
    end 
end

function svCmn:setOnCellTouched(index)
    local cell = self:getCacheCellByIndex(index)
    cell.index = index
    self.allCellList[index].cell = cell
    self:onCellTouched(cell, index)
end

function svCmn:startTimeTicket()
     if self.time_ticket == nil then
        if #self.allCellList == 0 then
            return
        end
        --到时间显示的索引
        local once_num = self.once_num or 1
        local _callback = function()
            if tolua.isnull(self.container) then return end
            local count = self.time_show_index + once_num
            local index = self.time_show_index + 1
            if index == 0 then
                index = 1
            end
            local size = #self.allCellList

            self.time_show_index = self.time_show_index + once_num
            for i = index, count do
                if i > size then
                    --超过总数了
                    break
                end
                local cellData = self.allCellList[i]
                if cellData and cellData.cell == nil then
                    cellData.cell = self:getCacheCellByIndex(i)
                end
                if self.activeCellIdx[i] then
                    self:updateCellAtIndex(i)
                end
            end
            
            if self.time_show_index >= size then
                self:clearTimeTicket()
                self.is_first_init = false
            end
        end

        self.time_ticket = GlobalTimeTicket:getInstance():add(_callback, self.delay / display.DEFAULT_FPS)
    end
end

function svCmn:clearTimeTicket()
    if self.time_ticket ~= nil then
        GlobalTimeTicket:getInstance():remove(self.time_ticket)
        self.time_ticket = nil
    end
end 

function svCmn:getCellPosition(index)
    local cur_item_index = index
    local anchor_point = cc.p(0.5,0.5)
    local _x, _y = 0, 0
    if self.dir == ScrollViewDir.horizontal then
        _x = self.start_x + self.item_width * anchor_point.x +(self.item_width + self.space_x) *(math.floor((index - 1) / self.row))
        _y = self.container_size.height -(self.start_y + self.item_height *(1 - anchor_point.y) +((index - 1) % self.row) *(self.item_height + self.space_y))
    else
        if self.start_pos == ScrollViewStartPos.top then
            _x = self.start_x + self.item_width * anchor_point.x + (self.item_width + self.space_x) *((index - 1) % self.col)
            _y = self.container_size.height -(self.start_y + self.item_height *(1 - anchor_point.y) +(math.floor((index - 1) / self.col)) *(self.item_height + self.space_y))
        else
            _x = self.start_x + self.item_width * anchor_point.x +(self.item_width + self.space_x) *((index - 1) % self.col)
            _y = self.start_y + self.item_height * anchor_point.y +(math.floor((index - 1) / self.col)) *(self.item_height + self.space_y)
        end
    end
    return _x, _y
end

--通过创建或复用的方式,获取index对应的cell对象
function svCmn:getCacheCellByIndex(index)
    local cacheIndex = (index - 1) % self.cacheMaxSize + 1
    if not self.cacheList[cacheIndex] then
        local newCell = self:createNewCell(index)
        if newCell then
            newCell:setAnchorPoint(cc.p(0.5, 0.5))
            newCell:setPositionX(-10000)--隐藏
            self.cacheList[cacheIndex] = newCell
            self.scroll_view:addChild(newCell)
        end
        return newCell
    else
        return self.cacheList[cacheIndex]
    end
end

--cell设置位置,并刷新cell的UI
function svCmn:updateCellAtIndex(index)
    if index > self.time_show_index then
        return
    end
    if not self.allCellList[index] then return end
    local cellData = self.allCellList[index]
    if cellData.cell == nil then
        cellData.cell = self:getCacheCellByIndex(index)     --self.allCellList的cell赋值在这里
    end
    cellData.cell:setPosition(cellData.x, cellData.y)
    self:updateCellByIndex(cellData.cell, index)
end

function svCmn:clearTimeTicket()
    if self.time_ticket ~= nil then
        GlobalTimeTicket:getInstance():remove(self.time_ticket)
        self.time_ticket = nil
    end
end 

function svCmn:getMaxSize()
    return self.container_size
end

function svCmn:getContainer()
    return self.container
end

function svCmn:scrollToPercentVertical( percent, time )
    if percent ~= percent then percent = 0 end
    self.scroll_view:scrollToPercentVertical(percent, time, true)
end

function svCmn:DeleteMe()
    doStopAllActions(self.container)
    self:clearTimeTicket()
    for k, item in ipairs(self.cacheList) do
        if item.DeleteMe then
            item:DeleteMe()
        end
    end
    self.allCellList = nil
    self.activeCellIdx = nil
    self.cacheList = nil
    self:removeAllChildren()
    self:removeFromParent()
end

3.4 应用举例

Lua 复制代码
function ModuleTest:updateSVCmn()
    self.cellData = {
        [1] = {index = 1},
        [2] = {index = 2},
        [3] = {index = 3},
        [4] = {index = 4},
        [5] = {index = 5},
        [6] = {index = 6},
        [7] = {index = 7},
        [8] = {index = 8},
        [9] = {index = 9},
        [10] = {index = 10},
        [11] = {index = 11},
        [12] = {index = 12},
    }
    if not self.svCmn then
        local setting = {
            start_x = 18, space_x = 0,
            start_y = 26, space_y = 6,
            item_width = 686, item_height = 172,
            row = 1, col = 1,
            delay = 4, once_num = 1,
        }
        self.svCmn = svCmn.new(self.scr_con, cc.p(0,0) , ScrollViewDir.vertical, ScrollViewStartPos.top, self.scr_con:getContentSize(), setting, cc.p(0, 0))
        self.svCmn:registerScriptHandlerSingle(handler(self,self.createNewCell), ScrollViewFuncType.CreateNewCell)
        self.svCmn:registerScriptHandlerSingle(handler(self,self.numberOfCells), ScrollViewFuncType.NumberOfCells)
        self.svCmn:registerScriptHandlerSingle(handler(self,self.updateCellByIndex), ScrollViewFuncType.UpdateCellByIndex)
    end
    self.svCmn:reloadData()
end

function ModuleTest:createNewCell(width, height)
    local cell = ActionCommonItem.new()
	return cell
end

function ModuleTest:numberOfCells()
    if not self.cellData then return 0 end
    return #self.cellData
end

function ModuleTest:updateCellByIndex(cell, index)
    local onecellData = self.cellData[index]
    cell.root_wnd:getChildByName("label"):setString(onecellData.index)
end
相关推荐
Fuliy9622 天前
U2D【Move and Jump】
unity·c#·游戏程序·动画·cocos2d
csdn_li_121225 天前
cocos Creator + fairyGUI 快速入门
cocos2d
GameTomato1 个月前
【iOS原生代码-音频播放】AVAudioPlayer 本地音频设置姊妹篇:如何将多个音频分别指定设置为左、右声道
游戏·ios·音视频·xcode·游戏开发·cocos2d
我是ed.1 个月前
Cocos 2 使用 webview 嵌入页面,摄像头调用没权限问题
webview·cocos2d·摄像头
唐小旭1 个月前
Cocos_鼠标滚轮放缩地图
cocos2d
一口盐汽水呐1 个月前
cocos creator 集成ffmpeg
ffmpeg·cocos2d
vip4512 个月前
游戏开发2025年最新版——八股文面试题(unity,虚幻,cocos都适用)
unity·虚幻·cocos2d
goose leaves a mark2 个月前
Cocos 3.8.3 实现外描边效果(逃课玩法)
cocos2d
gameckisme2 个月前
Selfloss,官方中文,解压即玩,
游戏·unity·游戏程序·图形渲染·cocos2d·贴图·游戏策划
爱你的魔2 个月前
cocosCreator屏幕适配导致的获取node宽高不准问题分析
cocos2d·cocos