Quick-cocos2dx-lua实现的下拉刷新,上拉加载
该控件是使用quick-cocos2dx-lua实现的下拉刷新,上啦加载。目前上啦加载没处理出具和回调,只是做了弹性动画。
效果图:
![](https://img.haomeiwen.com/i4725810/575b654a5bbb576c.gif)
gif压缩后会变成优点模糊卡顿,使用过程中是很流畅的。
实现原理
listview结合tableview实现动态列表,在列表上层盖一个layout实现触摸事件分发,在头部和底部分别放置一个cliping遮板,挡住被滑动出去的view
代码结构
![](https://img.haomeiwen.com/i4725810/3cd179f00d0471c2.png)
RefreshView就是整个逻辑代码。
TablView是lua官方封装的一个控件,用于处理listview的多数据渲染卡顿的问题。
使用方式
--创建刷新布局,传入宽高坐标
RefreshView:create(self, 400, 600, 100, 100)
--设置listview的子item,一定要记得return view
RefreshView:setItemLayout(
function(listview, index)
local layout = ccui.Layout:create()
layout:setAnchorPoint(cc.p(0, 0))
local spriteOne = display.newSprite('ic_xima_buyu.png')
spriteOne:setPosition(cc.p(0, 0))
spriteOne:setAnchorPoint(cc.p(0, 0))
local text = ccui.Text:create(index, 'Airal', 18)
text:setPosition(cc.p(300, 20))
local colorLayer
print('index==', index)
print('index取磨==', index % 2)
if index % 2 == 0 then
colorLayer = display.newColorLayer(cc.c4b(255, 0, 0, 255))
-- colorLayer = display.newColorLayer(cc.c3b(238,153,34))
colorLayer:setContentSize(800, 100)
else
colorLayer = display.newColorLayer(cc.c4b(255, 0, 0, 135))
-- colorLayer = display.newColorLayer(cc.c4b(255, 255, 255, 1))
colorLayer:setContentSize(800, 100)
end
layout:addChild(colorLayer)
layout:addChild(spriteOne)
layout:addChild(text)
--不要忘记return
return layout
end
)
--设置item的数量,正式使用时table的长度
RefreshView:setItemCount(20)
--设置单个item的宽高
RefreshView:setItemContentSize(300, 150)
--设置第一次渲染的时候跳转到哪一个位置
RefreshView:setStartIndex(5)
--代码采用了java的建造者模式,随意需要build一下
RefreshView:build()
--设置下拉刷新的监听
refreshView:setRefreshListener(
function()
print('刷新了')
AppUtils.startDownCount(
2,
1,
function(time)
if time == 0 then
-- 模拟刷新完成
refreshView:onRefreshComplete()
end
end
)
end
)
使用方式和android里面的下拉刷新差不多,大概流程:创建->设置item->设置数据源->构建->设置监听
代码
local RefreshView = class('RefreshView')
local DataUtils = require('app.utils.DateUtils')
local this
-- 阻尼系数
local bounceFraction = 0.4
-- 辅助阻尼系数
local bounceFractionArg = 1.01
-- 能够滑动的最大距离
local maxScrollHeight = 300
local parentWidth = 800
local parentHeight = 600
local parentX = display.cx - 400
local parentY = display.cy - 300
local headViewHeight = 150
local headViewWidth = parentWidth
local headViewStartY = parentHeight
local itemContentSizeWidth = 0
local itemContentSizeHeight = 0
-- 头部滑动的上一次y轴位置
local headViewLastY = 0
-- 底部视图滑动的上一次y轴位置
local loadViewLastY = 0
-- 是否展示了头部刷新布局
local isShowHeadView = false
-- 是否正在刷新
local isRefresh = false
-- 是否正在重置view,如果正在重置view,则不处理手势移动
local isResetView = false
local touchStatus = -1
-- 是否处理视图滑动
local isDisposeScroll = true
-- listivew itme数量
local listItemSize = 0
-- listview开始的位置
local startItemIndex = 1
-- 设置是否开启下拉刷新
local isRefreshEnabale = true
local isAddListView = false
local isRunactions = false
local arrowStatus = 0
function ctor(params)
-- statements
--print('创建了')
--print('slef', self)
end
function RefreshView:create(parentSelf, viewWidth, viewHeight, viewPositionX, viewPositionY)
-- statements
this = parentSelf
self.parentView = parentSelf
parentWidth = viewWidth
parentHeight = viewHeight
headViewStartY = viewHeight
headViewWidth = viewWidth
parentX = viewPositionX
parentY = viewPositionY
self.isPull = false
self.isLoad = false
self.parentLayout = ccui.Layout:create()
end
function RefreshView:build()
-- 所有需要的东西设置好了以后 开始构建
self.parentLayout:setContentSize(parentWidth, parentHeight)
self.parentLayout:setPosition(cc.p(parentX, parentY))
self.parentLayout:setAnchorPoint(cc.p(0, 0))
self.parentView:addChild(self.parentLayout)
if not isAddListView then
self:addListView(self.parentLayout)
end
self:addNodeListenerView()
end
function RefreshView:getRefreshView()
-- statements
return self.refreshLayout
end
function RefreshView:getParentView()
-- statements
return self.parentLayout
end
function RefreshView:setViewVisible(isShow)
-- statements
if self.parentLayout then
self.parentLayout:setVisible(isShow)
end
if self.refreshLayout then
self.refreshLayout:setVisible(isShow)
end
end
function RefreshView:addNodeListenerView()
self.refreshLayout = ccui.Layout:create()
self.refreshLayout:setContentSize(parentWidth, parentHeight)
self.refreshLayout:setPosition(cc.p(0, 0))
self.refreshLayout:setAnchorPoint(cc.p(0, 0))
self.parentLayout:addChild(self.refreshLayout)
self:parentViewAddNodeListener()
self:addHeadView()
end
function RefreshView:addHeadView()
self.headView = ccui.Layout:create()
-- local colorLayer = display.newColorLayer(cc.c4b(255, 0, 0, 255))
-- colorLayer:setContentSize(headViewWidth, headViewHeight)
-- headView:addChild(colorLayer)
self.headView:setContentSize(headViewWidth, headViewHeight)
self.headView:setAnchorPoint(cc.p(0, 0))
self.headView:setPosition(cc.p(0, parentHeight))
--print('self.headView...', self.headView)
local refreshDate = DataUtils.getTimeMDHM()
if refreshDate then
self.refreshTime = tostring(refreshDate)
else
self.refreshTime = '刚刚'
end
self.label =
display.newTTFLabel(
{
text = '上次更新 ' .. self.refreshTime,
font = 'Arial',
size = 24,
color = cc.c3b(255, 255, 255) -- 使用白色
}
)
self.labelStatus =
display.newTTFLabel(
{
text = '正在刷新...',
font = 'Arial',
size = 24,
color = cc.c3b(255, 255, 255) -- 使用白色
}
)
self.labelStatus:setPosition(cc.p(headViewWidth / 2, headViewHeight / 2 + 20))
self.label:setPosition(cc.p(headViewWidth / 2, headViewHeight / 2 - 20))
self.arrowView = display.newSprite('ic_jt.png')
self.arrowView:setPosition(cc.p(headViewWidth / 2 - 150, headViewHeight / 2))
self.arrowView:setScale(0.2)
self.pressLoadingView = display.newSprite('ic_loading.png')
self.pressLoadingView:setPosition(cc.p(headViewWidth / 2 - 150, headViewHeight / 2))
self.pressLoadingView:setScale(0.2)
self.pressLoadingView:setVisible(false)
self.headView:addChild(self.labelStatus)
self.headView:addChild(self.label)
self.headView:addChild(self.arrowView)
self.headView:addChild(self.pressLoadingView)
local faceNode = cc.Node:create()
faceNode:addChild(self.headView)
-- 裁剪模板
local stencil = cc.Sprite:create('clipping_bj.jpg')
stencil:setAnchorPoint(cc.p(0, 0))
stencil:setPosition(cc.p(0, parentHeight))
local stencilBottom = cc.Sprite:create('clipping_bj.jpg')
stencilBottom:setAnchorPoint(cc.p(0, 1))
stencilBottom:setPosition(cc.p(0, -100))
-- stencil:addChild(stencilBottom)
-- 裁剪工具
local clipper = cc.ClippingNode:create()
clipper:setPosition(0, 0)
-- clipper:setPosition(100, 100)
-- 设置裁剪模板
clipper:setStencil(stencil)
-- 设置底板可见
clipper:setInverted(true)
-- 设置绘制底板的Alpha值为0
clipper:setAlphaThreshold(0.75)
-- 添加底板
clipper:addChild(faceNode)
self.refreshLayout:addChild(clipper)
end
function RefreshView:setListView(listviewJoin)
if listviewJoin then
isAddListView = true
self.listview = listviewJoin
self.listview:setContentSize(cc.size(parentWidth, parentHeight))
self.listview:setAnchorPoint(cc.p(0, 0))
self.listview:setPosition(cc.p(0, 0))
self.listview:setItemsMargin(0)
self.parentLayout:addChild(self.listview)
self.listview:addScrollViewEventListener(
function(ref, type)
if type == 0 then
self.isPull = true
self.isLoad = false
elseif type == 1 then
self.isPull = false
self.isLoad = true
else
self.isPull = false
self.isLoad = false
end
end
)
end
end
function RefreshView:addListView()
-- statements
self.listview = ccui.ListView:create()
self.listview:setContentSize(cc.size(parentWidth, parentHeight))
-- self.listview:setBackGroundColorType(1)
-- self.listview:setBackGroundColor(cc.c3b(0, 100, 0))
self.listview:setAnchorPoint(cc.p(0, 0))
self.listview:setPosition(cc.p(0, 0))
self.listview:setItemsMargin(0)
local faceNode = cc.Node:create()
faceNode:addChild(self.listview)
-- 裁剪模板 需要上下两个模板 不让listview移动的时候显示在上层
local stencil = cc.Sprite:create('clipping_bj.jpg')
stencil:setAnchorPoint(cc.p(0, 0))
stencil:setPosition(cc.p(0, parentHeight))
local stencilBottom = cc.Sprite:create('clipping_bj.jpg')
stencilBottom:setAnchorPoint(cc.p(0, 1))
stencilBottom:setPosition(cc.p(0, -parentHeight))
stencil:addChild(stencilBottom)
-- 裁剪工具
local clipper = cc.ClippingNode:create()
clipper:setPosition(0, 0)
-- clipper:setPosition(100, 100)
-- 设置裁剪模板
clipper:setStencil(stencil)
-- 设置底板可见
clipper:setInverted(true)
-- 设置绘制底板的Alpha值为0
clipper:setAlphaThreshold(0.75)
-- 添加底板
clipper:addChild(faceNode)
self.parentLayout:addChild(clipper)
-- convert to tablevlew
local TableView = require('app.helper.refreshview.TableView')
--print('self.listview...', self.listview)
--print('TableView...', TableView)
local sizeSource = function(self, index)
return cc.size(itemContentSizeWidth, itemContentSizeHeight)
end
local loadSoruce = function(listview, index)
local itemview
-- print('listview', listview, 'getItemCallBack', self.getItemCallBack)
if self.getItemCallBack then
itemview = self.getItemCallBack(listview, index)
end
if not itemview then
--print('请设置listview的item')
end
return itemview
end
local unloadSoruce = function(self, index)
-- print('do texture unload here:', index)
end
TableView.attachTo(self.listview, sizeSource, loadSoruce, unloadSoruce)
self.listview:initDefaultItems(tonumber(listItemSize))
self.listview:jumpTo(startItemIndex)
self.listview:addScrollViewEventListener(
function(ref, type)
if type == 0 then
self.isPull = true
self.isLoad = false
elseif type == 1 then
self.isPull = false
self.isLoad = true
else
self.isPull = false
self.isLoad = false
end
end
)
end
-- setItemLayout传入回调函数,函数参数为listview当前对象和小标index,一定要返回item的view
function RefreshView:setItemLayout(callback)
--print('callback', callback)
self.getItemCallBack = callback
end
function RefreshView:setItemCount(itemSize)
listItemSize = itemSize
end
function RefreshView:setItemContentSize(width, height)
itemContentSizeWidth = width
itemContentSizeHeight = height
end
function RefreshView:setStartIndex(position)
-- 设置初始位置,需要小于item的数量
startItemIndex = position
end
function RefreshView:getItemSize()
-- statements
return listItemSize
end
function RefreshView:setRefreshEnable(enable)
-- statements
if type(enable) == boolean then
isRefreshEnabale = enable
end
end
function RefreshView:parentViewAddNodeListener()
local moveY = 0
local lastY = 0
-- statements
self.refreshLayout:addNodeEventListener(
cc.NODE_TOUCH_EVENT,
function(event)
-- print('event...', json.encode(event))
if event.name == 'moved' then
touchStatus = 2
self.refreshLayout:setTouchSwallowEnabled(true)
if isResetView then
return
end
moveY = event.y - lastY
--print('moveY...', moveY)
local listY = self.listview:getPositionY()
if self.isPull then
local startY = self.headView:getPositionY()
if moveY > 0 and listY == 0 and not isShowHeadView then
-- 向上滑 不是下拉
--print(' 向上滑 不是下拉')
self.refreshLayout:setTouchSwallowEnabled(false)
elseif isShowHeadView and moveY > 0 then
-- 展示的过程中 上滑
-- self.resetHeadView()
--print('展示的过程中 上滑')
self:moveHeadDown(moveY, 2)
elseif isShowHeadView and moveY < 0 then
--print('展示的过程中 继续下拉')
self:moveHeadDown(moveY, 3)
else
--print('普通下拉')
self:moveHeadDown(moveY, 1)
end
elseif self.isLoad then
if moveY < 0 and listY == 0 then
--print('向上滑动')
self.refreshLayout:setTouchSwallowEnabled(false)
else
--print('加载更多')
self:moveLoadView(moveY)
end
else
--print('释放事件拦截')
if isRefresh or self.isLoad then
else
self.refreshLayout:setTouchSwallowEnabled(false)
end
end
end
if event.name == 'ended' then
moveY = 0
touchStatus = 3
self:checkViewScroll()
-- self.refreshLayout:setTouchSwallowEnabled(false)
end
if event.name == 'began' then
--print(' 你绯闻绯闻你发呢wife我self.refreshLayout', self.refreshLayout)
touchStatus = 1
self.refreshLayout:setTouchSwallowEnabled(false)
lastY = event.y
if isRefreshEnabale then
return self.isPull or self.isLoad or isRefresh
else
return false
end
end
end
)
self.refreshLayout:setTouchEnabled(true)
end
function RefreshView:moveLoadView(move)
-- 暂时只移动listview
--print('loadViewLastY', loadViewLastY)
local k = move * (1 - bounceFraction)
local j = 1 - (loadViewLastY * bounceFractionArg / maxScrollHeight)
-- local j = 1
local diff = k * j
--print(k, j, diff)
loadViewLastY = diff
-- self.headView:setPositionY(headViewLastY)
self.listview:setPositionY(diff)
end
function RefreshView:moveHeadDown(move, type)
-- 移动头部布局
--print('headView', self.headView)
--print('self.headView:getPosition()', json.encode(self.headView:getPositionY()))
--print('self.headView:getContentSize()', json.encode(self.headView:getContentSize()))
if self.headView then
self.headView:setVisible(true)
end
if type == 1 then
-- 起始位置 开始下滑
local k = move * (1 - bounceFraction)
local j = 1 - ((headViewStartY - headViewLastY) * bounceFractionArg / maxScrollHeight)
-- local j = 1
local diff = k * j
--print(k, j, diff)
headViewLastY = headViewStartY + diff
self.headView:setPositionY(headViewLastY)
self.listview:setPositionY(diff)
--print('diff', diff, 'headViewStartY', headViewStartY, 'maxScrollHeight/3', maxScrollHeight / 3)
if math.abs(diff) < (headViewHeight / 1.5) then
--print('旋转箭头111')
self:roteArrowView(false)
else
self:roteArrowView(true)
--print('旋转箭头222')
end
end
if type == 2 then
-- 展示的过程中 上滑动
local contentSize = self.headView:getContentSize()
local k = move * (1 - bounceFraction)
local j = 1 - ((headViewStartY - headViewLastY) * bounceFractionArg / maxScrollHeight)
-- local j = 1
local diff = k * j
--print(k, j, diff)
headViewLastY = headViewStartY - contentSize.height + diff
self.headView:setPositionY(headViewLastY)
self.listview:setPositionY(-contentSize.height + diff)
end
if type == 3 then
-- 展示的过程中 下滑动
local contentSize = self.headView:getContentSize()
local k = move * (1 - bounceFraction)
local j = 1 - ((headViewStartY - headViewLastY) * bounceFractionArg / maxScrollHeight)
-- local j = 1
local diff = k * j
--print(k, j, diff)
headViewLastY = headViewStartY - contentSize.height + diff
self.headView:setPositionY(headViewLastY)
self.listview:setPositionY(-contentSize.height + diff)
end
end
function RefreshView:roteArrowView(isUp)
--print('isUp', isUp, 'arrowStatus', arrowStatus)
if isUp then
if not isRunactions and arrowStatus == 0 then
isRunactions = true
local action = cc.RotateTo:create(0.2, -180)
self.arrowView:runAction(action)
arrowStatus = 1
this:performWithDelay(
function()
isRunactions = false
end,
0.2
)
end
else
if not isRunactions and arrowStatus == 1 then
isRunactions = true
local action = cc.RotateTo:create(0.2, 0)
self.arrowView:runAction(action)
arrowStatus = 0
this:performWithDelay(
function()
isRunactions = false
end,
0.2
)
end
end
end
function RefreshView:checkViewScroll()
-- 检查view的滑动
--print('检查view的滑动')
if isDisposeScroll then
if self.isPull then
self:checkViewHead()
elseif self.isLoad then
--print('检查加载的视图滚动')
self:checkViewLoad()
end
else
isDisposeScroll = true
--print('不处理滑动')
end
end
function RefreshView:checkViewLoad()
-- statements
local actionMoveL = cc.MoveTo:create(0.3, cc.p(headX, 0))
self.listview:runAction(actionMoveL)
self.refreshLayout:setTouchSwallowEnabled(false)
end
function RefreshView:checkViewHead()
local headX = self.headView:getPositionX()
local headY = self.headView:getPositionY()
--print('headViewLastY', headViewLastY, 'headY', headY)
local contentSize = self.headView:getContentSize()
local minYCenter = headViewStartY - maxScrollHeight / 3
--print('headViewStartY', headViewStartY)
--print('maxScrollHeight', maxScrollHeight)
--print('minYCenter', minYCenter)
--print('isRefresh', isRefresh)
if not isDisposeScroll or isResetView then
-- 不处理滑动 和 正在重置视图的时候不处理
return
end
if isRefresh then
-- 如果正在刷新,拉动布局,继续回归刷新布局
--print('如果正在刷新,拉动布局,继续回归刷新布局')
self:setHeadViewToRefreshLocation()
else
--print('正常判断')
if headViewLastY < minYCenter then
-- 超过一半的距离
--print('超过一半的距离')
self:setHeadViewToRefreshLocation()
self:onRefresh()
else
-- self.refreshLayout:setTouchSwallowEnabled(false)
-- 未超过一半的距离
--print('未超过一半的距离')
-- local actionMove = cc.MoveTo:create(0.3, cc.p(headX, headViewStartY))
-- self.headView:runAction(actionMove)
-- local actionMoveL = cc.MoveTo:create(0.3, cc.p(headX, 0))
-- self.listview:runAction(actionMoveL)
self:resetHeadView()
end
end
end
function RefreshView:setHeadViewToRefreshLocation()
-- statements
local headX = self.headView:getPositionX()
local contentSize = self.headView:getContentSize()
local actionMove = cc.MoveTo:create(0.3, cc.p(headX, headViewStartY - contentSize.height))
self.headView:runAction(actionMove)
local actionMoveL = cc.MoveTo:create(0.3, cc.p(headX, -contentSize.height))
self.listview:runAction(actionMoveL)
isShowHeadView = true
self.refreshLayout:setTouchSwallowEnabled(true)
self.arrowView:setVisible(false)
self.pressLoadingView:setVisible(true)
local action = cc.RotateBy:create(0.5, 180)
local actions = cc.RepeatForever:create(action)
self.pressLoadingView:runAction(actions)
end
function RefreshView:resetHeadView()
-- 重置头部视图
--print('重置头部视图')
if touchStatus ~= 3 then
-- 状态不是3 说明手势未抬起 重置后不处理滑动
isDisposeScroll = false
end
-- local contentSize = self.headView:getContentSize()
-- self.headView:setPositionY(headViewStartY - contentSize.height)
local headX = self.headView:getPositionX()
local actionMove = cc.MoveTo:create(0.3, cc.p(headX, headViewStartY))
self.headView:runAction(actionMove)
local actionMoveL = cc.MoveTo:create(0.3, cc.p(headX, 0))
self.listview:runAction(actionMoveL)
self.refreshLayout:setTouchSwallowEnabled(false)
isResetView = true
this:performWithDelay(
function()
if self.headView then
self.headView:setVisible(false)
self.pressLoadingView:setVisible(false)
self.arrowView:setVisible(true)
self.arrowView:setRotation(0)
end
end,
0.3
)
this:performWithDelay(
function()
--print('0.5秒倒计时到了')
isResetView = false
if self.headView then
local y = self.headView:getPositionY()
if y ~= headViewStartY then
-- 瞎jb乱滑导致view回归不了初始的位置,所以倒计时结束后强制回归
self.headView:setPositionY(headViewStartY)
self.listview:setPositionY(0)
end
end
end,
0.5
)
end
function RefreshView:setRefreshListener(callback)
self.onRefreshCallBack = callback
end
function RefreshView:onRefresh()
--print('onRefresh...刷新回调')
isRefresh = true
if self.onRefreshCallBack then
self.onRefreshCallBack()
end
end
function RefreshView:onRefreshComplete()
-- 下拉刷新完成
local refreshDate = DataUtils.getTimeMDHM()
--print('refreshDate', refreshDate)
--print('self.label', self.label)
if refreshDate then
self.label:setString('上次更新 ' .. tostring(refreshDate))
else
self.label:setString('上次更新 刚刚')
end
isRefresh = false
isShowHeadView = false
self:resetHeadView()
end
function RefreshView:onLoadComplete()
-- 加载更多完成
end
function RefreshView:onLoad()
end
return RefreshView
整体代码量不多,未处理上拉加载有接近700行,如果加上的话应该会有1000行代码。
tips
1. 由于时间关系,就没有处理上拉加载更多,因为在项目中用不到,可以参考下拉刷新自己加上。
2. 在头部的参数中有阻尼系数、下拉刷新布局的高度、允许滑动的最大距离等,可以根据需求自行修改,其他的功能也可以自己扩展。
3. tabview的代码在quick-cocos2d-lua的维护网页上,请自己下载。链接地址
网友评论