-- 智能滚动界面 --[[ initCount ? : number, // 初始加载initCount个UIItem loadCount ? : number, // 滚动到增长边界时则加载loadCount个item initIndex ? : number, // 初始化索引位置,从1开始 isPlayAnime ? : boolean, // 是否在第一次更新数据时播放动画 isWheelStyle ? : boolean, // 是否滚轮风格 focusPercent ? : number, // 从0到1,表示从下到上,从左到右,聚焦的百分比位置 radiusRate ? : number, // 滚轮效果显著程度参数,越大越不明显 coverRate ? : number, // ui条目覆盖效果,越大覆盖越明显 autoScrollToFocus ? : boolean, // 自动将最近的ui回滚到焦点 scrollDisScale ? : number, // --]] local SmartScrollView = class("SmartScrollView"); function SmartScrollView:ctor(scrollView, param) self.initCount = (param and param.initCount) or 8; self.loadCount = (param and param.loadCount) or 3; self.initIndex = (param and param.initIndex) or 1; self.isPlayAnime = param and param.isPlayAnime; self.isWheelStyle = param and param.isWheelStyle; self.focusPercent = (param and param.focusPercent) or 0.5; self.radiusRate = (param and param.radiusRate) or app.config.Rule.SCROLL_VIEW_WHEEL_RADIUS_RATE.value; self.coverRate = (param and param.coverRate) or app.config.Rule.SCROLL_VIEW_WHEEL_COVER_RATE.value; self.autoScrollToFocus = param and param.autoScrollToFocus; self.scrollDisScale = (param and param.scrollDisScale) or 1; self.FocusRadius = 5; -- 焦点范围误差距离 self.orderArray = {}; -- 数据必须连续有效,且每个数据皆可创建有效ui self.scrollItems = {}; -- Map(); 所有UIItem Map self.headDataIdx = 0; self.tailDataIdx = 1; self.dataChangeOnce = true; self.playAnimeOnce = true; self.scrollView = scrollView self:init(); end -- 数据数组 function SmartScrollView:getOrderArray() return self.orderArray; end -- 赋值触发初始化 function SmartScrollView:setOrderArray(arr) -- 初始化数据获取方法,若数据变化,则UI重新加载 local me = self me.orderArray = arr; if me.dataChangeOnce then me.dataChangeOnce = false; if not me.scrollView or tolua.isnull(me.scrollView) then print("SmartScrollView wasn't assigned UIScrollView!"); return end me.scrollView:runDelay(0, function() me.dataChangeOnce = true; me:onDataChanged(); end); end end function SmartScrollView:init() local me = self; local innerContainer = me.scrollView:getInnerContainer(); innerContainer:setAutoSize(true); -- me.scrollView:setScrollDisScale(me.scrollDisScale); -- 隐藏进度条 me.scrollView:getHBar():setVisible(false); me.scrollView:getVBar():setVisible(false); -- 滚轮风格初始化 if me.isWheelStyle then local oldDoLayout = innerContainer.doLayout; innerContainer.doLayout = function() for k,v in ipairs(me.scrollItems) do v.layoutNode.LocalZOrder = k; end oldDoLayout(innerContainer); me.onScrollWheel(); end -- 解决分辨率变化等导致的界面混乱问题 me.scrollView:setWidgetEventListener(function(node, eventType) if eventType == cc.WidgetEvent.SIZE_CHANGED then me.scrollView.runDelay(0 , function() me.doLayout(); end); end end); end -- 监听滚到顶部和底部(LocalZOrder大的,布局时排在下面) me.scrollView:addEventListener(function(target , eventType) if eventType == cc.ScrollviewEventType.scrollToTop or eventType == cc.ScrollviewEventType.scrollToLeft then if me.headDataIdx < 1 then return; end for i = 1 , me.loadCount do me:addItem(true); end if me.headDataIdx < 1 and me.isWheelStyle and #me.scrollItems > 0 then me:fillStart(); end if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then if not cc.pEqual(innerContainer:getAnchorPoint() , cc.p(1 , 0)) then innerContainer:setPositionX(innerContainer:getPositionX() + (1 - innerContainer:getAnchorPoint().x) * innerContainer:getSize().width); innerContainer:setPositionY(innerContainer:getPositionY() - innerContainer:getAnchorPoint().y * innerContainer:getSize().height); innerContainer:setAnchorPoint(cc.p(1 , 0)); end else if not cc.pEqual(innerContainer:getAnchorPoint() , cc.p(0 , 0)) then innerContainer:setPositionX(innerContainer:getPositionX() - innerContainer:getAnchorPoint().x * innerContainer:getSize().width); innerContainer:setPositionY(innerContainer:getPositionY() - innerContainer:getAnchorPoint().y * innerContainer:getSize().height); innerContainer:setAnchorPoint(cc.p(0 , 0)); end end me:doLayout(); elseif eventType == cc.ScrollviewEventType.scrollToBottom or eventType == cc.ScrollviewEventType.scrollToRight then if me.tailDataIdx > #me.orderArray then return; end for i = 1 , me.loadCount do me:addItem(false); end if me.tailDataIdx > #me.orderArray and me.isWheelStyle and #me.scrollItems > 0 then me:fillEnd(); end if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then if not cc.pEqual(innerContainer:getAnchorPoint() , cc.p(0 , 0)) then innerContainer:setPositionX(innerContainer:getPositionX() - innerContainer:getAnchorPoint().x * innerContainer:getSize().width); innerContainer:setPositionY(innerContainer:getPositionY() - innerContainer:getAnchorPoint().y * innerContainer:getSize().height); innerContainer:setAnchorPoint(cc.p(0 , 0)); end else if not cc.pEqual(innerContainer:getAnchorPoint() , cc.p(0 , 1)) then innerContainer:setPositionX(innerContainer:getPositionX() - innerContainer:getAnchorPoint().x * innerContainer:getSize().width); innerContainer:setPositionY(innerContainer:getPositionY() + (1 - innerContainer:getAnchorPoint().y) * innerContainer:getSize().height); innerContainer:setAnchorPoint(cc.p(0 , 1)); end end me:doLayout(); end if eventType == cc.ScrollviewEventType.scrolling then if me.isWheelStyle then me:onScrollWheel(); end --[[ elseif eventType == cc.ScrollviewEventType.scrollingEnded then -- 自动回滚 if me.isWheelStyle and me.autoScrollToFocus then local minDis; local minOffset for i,v in ipairs(me.scrollItems) do local node = v.layoutNode; local pos = me.scrollView:convertToNodeSpace2D(node:getWorldPosition()); -- ui相对于scrollView的center坐标 local center; local offset; local dis = 0; if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then center = pos.x + node:getContentSize().width * (0.5 - node:getAnchorPoint().x); if center > me.scrollView:getSize().width or center < 0 then return; end offset = center - me.scrollView:getSize().width * me.focusPercent; dis = math.abs(offset); else center = pos.y + node:getContentSize().height * (0.5 - node:getAnchorPoint().y); if center > me.scrollView:getSize().height or center < 0 then return; end offset = center - me.scrollView:getSize().height * me.focusPercent; dis = math.abs(offset); end if not minDis then minDis = dis; minOffset = offset; return; end if dis < minDis then minDis = dis; minOffset = offset; end end; if #me.scrollItems > 0 and minDis > me.FocusRadius then local vec2 = cc.p(0 , 0); if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then vec2.x = -minOffset / me.scrollDisScale; else vec2.y = -minOffset / me.scrollDisScale; end me.scrollView:scrollBy(vec2 , 0.5 / me.scrollDisScale , true); me:onScrollWheel(); end end --]] end end); end function SmartScrollView:doLayout() self.scrollView:getInnerContainer():requestDoLayout(); self.scrollView:getInnerContainer():doLayout(); end function SmartScrollView:removeAllChildren() self.scrollView:removeAllChildren(); self.scrollItems = {}; end -- @param index 对应数组下标,从1开始 function SmartScrollView:getUIItem(index) return self.scrollItems[index].showObj; end -- 将index 对应的ui 节点滚动到focus处,从1开始 function SmartScrollView:scrollToIndex(index , onEnd) local me = self; if index < 1 or index > #me.orderArray then return; end if #me.scrollItems <= 0 then return; end while index <= me.headDataIdx or index >= me.tailDataIdx do me:addItem(index <= me.headDataIdx); end me:doLayout(); local item = me.scrollItems[index].layoutNode; if not item then return; end local pos = me.scrollView:convertToNodeSpace2D(item:getWorldPosition()); local center; local offset; local speed = 1800; if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then center = pos.x + item:getContentSize().width * (0.5 - item:getAnchorPoint().x); offset = center - me.scrollView:getSize().width * me.focusPercent; me.scrollView:scrollBy(cc.p(-offset , 0) , math.max(offset / speed , 0.5) , true); else center = pos.y + item:getContentSize().height * (0.5 - item:getAnchorPoint().y); offset = center - me.scrollView:getSize().height * me.focusPercent; me.scrollView:scrollBy(cc.p(0 , -offset) , math.max(offset / speed , 0.5) , true); end if onEnd then me.scrollView:runDelay(math.max(offset / speed , 0.5) , onEnd); end end -- index从1开始 function SmartScrollView:jumpToIndex(index) local me = self; local innerContainer = me.scrollView:getInnerContainer(); if index < 1 and index > #me.orderArray then return; end if #me.scrollItems <= 0 then return; end while index <= me.headDataIdx or index >= me.tailDataIdx do me:addItem(index <= me.headDataIdx); end me:doLayout(); local item = me.scrollItems[index].layoutNode; if not item or not item:isVisible() then me.scrollView:jumpToTop(); return; end local pos = me.scrollView:convertToNodeSpace2D(item:getWorldPosition()); local center; local offset; if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then center = pos.x + item:getContentSize().width * (0.5 - item:getAnchorPoint().x); offset = center - me.scrollView:getSize().width * me.focusPercent; local finalPosX = innerContainer:getPositionX() - offset; innerContainer:setPositionX(finalPosX); local finalRightX = finalPosX + (1 - innerContainer:getAnchorPoint().x) * innerContainer:getSize().width; local finalLeftX = finalPosX - innerContainer:getAnchorPoint().x * innerContainer:getSize().width; if finalRightX < me.scrollView:getSize().width then innerContainer:setPositionX(finalPosX + me.scrollView:getSize().width - finalRightX); end if finalLeftX > 0 then innerContainer:setPositionX(finalPosX - finalLeftX); end else center = pos.y + item:getContentSize().height * (0.5 - item:getAnchorPoint().y); offset = center - me.scrollView:getSize().height * me.focusPercent; local finalPosY = innerContainer:getPositionY() - offset; innerContainer:setPositionY(finalPosY); local finalTopY = finalPosY + (1 - innerContainer:getAnchorPoint().y) * innerContainer:getSize().height; local finalBottomY = finalPosY - innerContainer:getAnchorPoint().y * innerContainer:getSize().height; if finalBottomY > 0 then innerContainer:setPositionY(finalPosY - finalBottomY); end if finalTopY < me.scrollView:getSize().height then innerContainer:setPositionY(finalPosY + me.scrollView:getSize().height - finalTopY); end end end -- orderArray更新后调用 function SmartScrollView:onDataChanged() local me = self; me:removeAllChildren(); -- 修正initIndex if me.initIndex < 1 then me.initIndex = 1; end if me.initIndex > #me.orderArray then me.initIndex = #me.orderArray; end -- 修正headDataIdx 的初始位置 me.headDataIdx = me.initIndex + math.floor(me.initCount / 2); if me.headDataIdx < 1 then me.headDataIdx = 1; end if me.headDataIdx > #me.orderArray then me.headDataIdx = #me.orderArray; end local count = #me.orderArray > me.initCount and me.initCount or #me.orderArray; for i = 1 , count do me:addItem(true); end me.tailDataIdx = me.headDataIdx + #me.scrollItems + 1; if me.isWheelStyle and me.headDataIdx < 1 and #me.scrollItems > 0 then me:fillStart(); end if me.isWheelStyle and me.tailDataIdx > #me.orderArray and #me.scrollItems > 0 then me:fillEnd(); end me:doLayout(); -- 第一次更新数据,播放动画 if me.isPlayAnime and me.playAnimeOnce then me.playAnimeOnce = false; local delayTime = 0.5 --app.config.Rule.get("SCROLL_VIEW_ANI_DELAY_TIME").value; local playTime = 0.5 --app.config.Rule.get("SCROLL_VIEW_ANI_PLAY_TIME").value; local time = 0; for i = me.headDataIdx + 1 , me.tailDataIdx - 1 do local node = me.scrollItems[i].layoutNode; if node and node:isVisible() then time = time + delayTime; local offset = node:getContentSize().width; for ii,vv in pairs(node:getChildren()) do if vv:isVisible() then vv:setPositionX(vv:getPositionX() - offset); vv:runAction(cc.Sequence.create(cc.DelayTime:create(time) , cc.MoveBy:create(playTime , cc.vec3(offset,0,0)) )); end end end end end me:jumpToIndex(me.initIndex); if me.isWheelStyle then me:onScrollWheel(); end end -- 在开头填空 function SmartScrollView:fillStart() local me = self; local node = me.scrollItems[1].layoutNode; local startNullNode = cc.Widget.create(); startNullNode:setAutoSize(false); if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then startNullNode:setSize(cc.size(me.scrollView:getSize().width * me.focusPercent - node:getContentSize().width / 2 , node:getContentSize().height )); else startNullNode:setSize(cc.size(node:getContentSize().width , me.scrollView:getSize().height * (1 - me.focusPercent) - node:getContentSize().height / 2 )); end startNullNode:setAutoSize(true); me.scrollView:addChild(startNullNode , -100); end -- 在结尾填空 function SmartScrollView:fillEnd() local me = self; local node = me.scrollItems[#me.orderArray].layoutNode; local endNullNode = cc.Widget.create(); endNullNode:setAutoSize(false); if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then endNullNode:setSize(cc.size( me.scrollView:getSize().width * (1 - me.focusPercent) - node:getContentSize().width / 2 , node:getContentSize().height )); else endNullNode:setSize(cc.size( node:getContentSize().width , me.scrollView:getSize().height * me.focusPercent - node:getContentSize().height / 2 )); end endNullNode:setAutoSize(true); me.scrollView:addChild(endNullNode , #me.orderArray + 100); end -- 滚轮效果 function SmartScrollView:onScrollWheel() local me = self; for i,v in pairs(me.scrollItems) do local node = v.layoutNode; local pos = me.scrollView:convertToNodeSpace2D(node:getWorldPosition()); -- ui相对于scrollView的center坐标 local center; local dis; local range; if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then center = pos.x + node:getContentSize().width * (0.5 - node:getAnchorPoint().x); local a = center > me.scrollView:getSize().width + node:getContentSize().width * 2; local b = center < -node:getContentSize().width * 2; if a or b then return; end dis = math.abs(center - me.scrollView:getSize().width * me.focusPercent); range = (me.scrollView:getSize().width + node:getContentSize().width) / 2 * me.radiusRate; else center = pos.y + node:getContentSize().height * (0.5 - node:getAnchorPoint().y); local a = center > me.scrollView:getSize().height + node:getContentSize().height * 2; local b = center < -node:getContentSize().height * 2; if a or b then return; end dis = math.abs(center - me.scrollView:getSize().height * me.focusPercent); range = (me.scrollView:getSize().height / 2 + node:getContentSize().height / 2) * me.radiusRate; end -- 可视范围内的 local item = node:getChildren()[0]; local percent = (range - dis) / range; if percent < 0.001 then percent = 0.001; end -- 置透明度 item:setOpacity(255 * percent); -- 置缩放大小 item:setScale(percent); -- 置渲染秩序 node:setLocalZOrder(range - dis); end end function SmartScrollView:nextDataIdx(isAddToHead) local me = self; if isAddToHead then me.headDataIdx = me.headDataIdx - 1; else me.tailDataIdx = me.tailDataIdx + 1; end end -- @param isAddToHead 是否向数组小端取数据 function SmartScrollView:addItem(isAddToHead) local me = self; local curDataIdx = isAddToHead and me.headDataIdx or me.tailDataIdx; if not me.orderArray[curDataIdx] then if curDataIdx >= 1 and curDataIdx <= #me.orderArray then me:nextDataIdx(isAddToHead); end return false; end assert(me.createUI , "SmartScrollView wasn't assigned ui create function!"); local item = me:createUI(me.orderArray[curDataIdx], curDataIdx); if not item then me:nextDataIdx(isAddToHead); return false; end local ui = item.ui or item; local node = cc.Widget:create(); node:setAutoSize(false); if me.scrollView:getDirection() == cc.ScrollViewDir.horizontal then node:setSize(cc.size(ui:getSize().width - me.coverRate , ui:getSize().height)); ui:setPositionX(ui:getAnchorPoint().x * ui:getSize().width - me.coverRate / 2); ui:setPositionY(ui:getAnchorPoint().y * ui:getSize().height); else node:setSize(cc.size(ui:getSize().width , ui:getSize().height - me.coverRate)); ui:setPositionX(ui:getAnchorPoint().x * ui:getSize().width); ui:setPositionY(ui:getAnchorPoint().y * ui:getSize().height - me.coverRate / 2); end -- 节点可见性 local oldValue = ui:isVisible(); ui:setVisible(true); ui.setVisible = function(this , flag) node:setVisible(flag); end ui.isVisible = function() return node:isVisible(); end node:addChild(ui); node:setAutoSize(true); node:setVisible(oldValue); me.scrollView:addChild(node , curDataIdx); me.scrollItems[curDataIdx] = {layoutNode = node , showObj = item}; me:nextDataIdx(isAddToHead); return true; end return SmartScrollView;