前言:公司项目原先所有UI都用的是UIListView,优化过很多遍了,仍然有很多地方不尽人意。想改掉这一块,本想重写一个tableView,新项目开发很急,因此先拿cocos自带的CCTableView来使用,改掉其原生的一些使用bug即可正常商用了。
cocos2dx自带的scrollView有两种。
1,cocos2dx/extensions/GUI/CCScrollView(自带) 和cocos2dx/cocos/ui/UIScrollView。(ios ui控件)
2,GUI部分里面就带有一个CCTableView就是即将使用的这个,继承CCScrollView。
3,CCTableView实现了界面item个数+1的显示,所以使用起来效率还是可以的。
但是在使用过程中发现缺少了几个需求,因此对c++方面做了以下3点更改:
1,缺少refreshData函数
// 添加刷新函数,刷新相同个数item的view,不用每次都重载
// CCTableView.h文件,添加以下函数:
void refreshData();
// CCTableView.cpp文件, 添加以下函数:
void TableView::refreshData()
{
if (_dataSource->numberOfCellsInTableView(this) == 0)
return;
for (const auto &cell: _cellsUsed)
{
_dataSource->tableCellAtIndex(this, cell->getIdx());
}
}
2,缺少跳转到第几个item的函数,同样修改CCTableView.cpp和CCTableView.h
// CCTableView.h文件,添加以下函数:
// 从上往下数,跳到第几个
bool setItemTop(int nIndex, bool animated=false);
// 跳到第几个为中心
bool setItemMiddle(int nIndex, bool animated=false);
// CCTableView.cpp文件, 添加以下函数:
bool TableView::setItemTop(int nIndex, bool animated)
{
if (_dataSource == NULL)
{
return false;
}
if (nIndex <= 0)
{
nIndex = 1;
}
else if ( nIndex > _vCellsPositions.size())
{
nIndex = _vCellsPositions.size();
}
this->unschedule(CC_SCHEDULE_SELECTOR(TableView::deaccelerateScrolling));
if (_direction == Direction::VERTICAL)
{
return this->setItemTopV(nIndex, animated);
}
else
{
return this->setItemTopH(nIndex, animated);
}
}
bool TableView::setItemTopV(int nIndex, bool animated)
{
if (_viewSize.height >= this->getContentSize().height)
{
this->setContentOffset(Vec2(this->getContentOffset().x, this->minContainerOffset().y), animated);
return true;
}
Vec2 offset = __offsetFromIndex(nIndex - 1);
if (_viewSize.height >= this->getContentSize().height - offset.y)
{
this->setContentOffset(Vec2(this->getContentOffset().x, this->maxContainerOffset().y), animated);
return true;
}
float fOffsetX = this->getContentOffset().x;
float fOffsetY = -(this->getContentSize().height - offset.y) + _viewSize.height;
this->setContentOffset(Vec2(fOffsetX, fOffsetY), animated);
return true;
}
bool TableView::setItemTopH(int nIndex, bool animated)
{
if (_viewSize.width >= this->getContentSize().width)
{
this->setContentOffset(Vec2(this->maxContainerOffset().x, this->getContentOffset().y ), animated);
return true;
}
Vec2 offset = __offsetFromIndex(nIndex - 1);
if (_viewSize.width >= this->getContentSize().width - offset.x)
{
this->setContentOffset(Vec2(this->minContainerOffset().x, this->getContentOffset().y), animated);
return true;
}
float fOffsetX = -offset.x;
float fOffsetY = this->getContentOffset().y;
this->setContentOffset(Vec2(fOffsetX, fOffsetY), animated);
return true;
}
bool TableView::setItemMiddle(int nIndex, bool animated)
{
if (_dataSource == NULL)
{
return false;
}
if (nIndex <= 0)
{
nIndex = 1;
}
else if ( nIndex > _vCellsPositions.size())
{
nIndex = _vCellsPositions.size();
}
this->unschedule(CC_SCHEDULE_SELECTOR(TableView::deaccelerateScrolling));
if (_direction == Direction::VERTICAL)
{
return this->setItemMiddleV(nIndex, animated);
}
else
{
return this->setItemMiddleH(nIndex, animated);
}
}
bool TableView::setItemMiddleV(int nIndex, bool animated)
{
Vec2 offset = __offsetFromIndex(nIndex - 1);
Size sizeCell = _dataSource->tableCellSizeForIndex(this, nIndex - 1);
const Size& sizeContent = this->getContentSize();
const Size& sizeView = _viewSize;
if (sizeView.height >= sizeContent.height || ((offset.y + sizeCell.height / 2) <= sizeView.height / 2) )
{
this->setContentOffset(Vec2(this->getContentOffset().x, this->minContainerOffset().y), animated);
return true;
}
if ((sizeContent.height - (offset.y + sizeCell.height/2)) <= sizeView.height/2)
{
this->setContentOffset(Vec2(this->getContentOffset().x, this->maxContainerOffset().y), animated);
return true;
}
float fOffsetX = this->getContentOffset().x;
float fOffsetY = sizeView.height - sizeContent.height + offset.y - (sizeView.height - sizeCell.height) / 2;
this->setContentOffset(Vec2(fOffsetX, fOffsetY), animated);
return true;
}
bool TableView::setItemMiddleH(int nIndex, bool animated)
{
Vec2 offset = __offsetFromIndex(nIndex - 1);
const Size sizeCell = _dataSource->tableCellSizeForIndex(this, nIndex - 1);
const Size& sizeContent = this->getContentSize();
const Size& sizeView = _viewSize;
if (sizeView.width >= sizeContent.width || ((offset.x + sizeCell.width/2) <= sizeView.width/2) )
{
this->setContentOffset(Vec2(this->maxContainerOffset().x, this->getContentOffset().y), animated);
return true;
}
if ((sizeContent.width - (offset.x + sizeCell.width/2)) <= sizeView.width/2)
{
this->setContentOffset(Vec2(this->minContainerOffset().x, this->getContentOffset().y), animated);
return true;
}
float fOffsetX = -offset.x + (sizeView.width - sizeCell.width) / 2;
float fOffsetY = this->getContentOffset().y;
this->setContentOffset(Vec2(fOffsetX, fOffsetY), animated);
return true;
}
3,将修改的函数导出接口到lua使用
//修改lua_cocos2dx_extension_auto.cpp,添加以下函数
//add --
int lua_cocos2dx_extension_TableView_refreshData(lua_State* tolua_S)
{
int argc = 0;
cocos2d::extension::TableView* cobj = nullptr;
bool ok = true;
#if COCOS2D_DEBUG >= 1
tolua_Error tolua_err;
#endif
#if COCOS2D_DEBUG >= 1
if (!tolua_isusertype(tolua_S,1,"cc.TableView",0,&tolua_err)) goto tolua_lerror;
#endif
cobj = (cocos2d::extension::TableView*)tolua_tousertype(tolua_S,1,0);
#if COCOS2D_DEBUG >= 1
if (!cobj)
{
tolua_error(tolua_S,"invalid 'cobj' in function 'lua_cocos2dx_extension_TableView_refreshData'", nullptr);
return 0;
}
#endif
argc = lua_gettop(tolua_S)-1;
if (argc == 0)
{
if(!ok)
{
tolua_error(tolua_S,"invalid arguments in function 'lua_cocos2dx_extension_TableView_refreshData'", nullptr);
return 0;
}
cobj->refreshData();
lua_settop(tolua_S, 1);
return 1;
}
luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d \n", "cc.TableView:refreshData",argc, 0);
return 0;
#if COCOS2D_DEBUG >= 1
tolua_lerror:
tolua_error(tolua_S,"#ferror in function 'lua_cocos2dx_extension_TableView_refreshData'.",&tolua_err);
#endif
return 0;
}
int lua_cocos2dx_extension_TableView_setItemTop(lua_State* tolua_S)
{
int argc = 0;
cocos2d::extension::TableView* cobj = nullptr;
bool ok = true;
#if COCOS2D_DEBUG >= 1
tolua_Error tolua_err;
#endif
#if COCOS2D_DEBUG >= 1
if (!tolua_isusertype(tolua_S,1,"cc.TableView",0,&tolua_err)) goto tolua_lerror;
#endif
cobj = (cocos2d::extension::TableView*)tolua_tousertype(tolua_S,1,0);
#if COCOS2D_DEBUG >= 1
if (!cobj)
{
tolua_error(tolua_S,"invalid 'cobj' in function 'lua_cocos2dx_extension_TableView_setItemTop'", nullptr);
return 0;
}
#endif
argc = lua_gettop(tolua_S)-1;
do{
if (argc == 2) {
int arg0;
ok &= luaval_to_int32(tolua_S, 2,(int *)&arg0, "cc.TableView:setItemTop");
if (!ok) { break; }
bool arg1;
ok &= luaval_to_boolean(tolua_S, 3,&arg1, "cc.TableView:setItemTop");
if (!ok) { break; }
bool ret = cobj->setItemTop(arg0, arg1);
tolua_pushboolean(tolua_S,(bool)ret);
return 1;
}
}while(0);
ok = true;
do{
if (argc == 1) {
int arg0;
ok &= luaval_to_int32(tolua_S, 2,(int *)&arg0, "cc.TableView:setItemTop");
if (!ok) { break; }
bool ret = cobj->setItemTop(arg0);
tolua_pushboolean(tolua_S,(bool)ret);
return 1;
}
}while(0);
ok = true;
luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d \n", "cc.TableView:setItemTop",argc, 1);
return 0;
#if COCOS2D_DEBUG >= 1
tolua_lerror:
tolua_error(tolua_S,"#ferror in function 'lua_cocos2dx_extension_TableView_setItemTop'.",&tolua_err);
#endif
return 0;
}
int lua_cocos2dx_extension_TableView_setItemMiddle(lua_State* tolua_S)
{
int argc = 0;
cocos2d::extension::TableView* cobj = nullptr;
bool ok = true;
#if COCOS2D_DEBUG >= 1
tolua_Error tolua_err;
#endif
#if COCOS2D_DEBUG >= 1
if (!tolua_isusertype(tolua_S,1,"cc.TableView",0,&tolua_err)) goto tolua_lerror;
#endif
cobj = (cocos2d::extension::TableView*)tolua_tousertype(tolua_S,1,0);
#if COCOS2D_DEBUG >= 1
if (!cobj)
{
tolua_error(tolua_S,"invalid 'cobj' in function 'lua_cocos2dx_extension_TableView_setItemMiddle'", nullptr);
return 0;
}
#endif
argc = lua_gettop(tolua_S)-1;
do{
if (argc == 2) {
int arg0;
ok &= luaval_to_int32(tolua_S, 2,(int *)&arg0, "cc.TableView:setItemMiddle");
if (!ok) { break; }
bool arg1;
ok &= luaval_to_boolean(tolua_S, 3,&arg1, "cc.TableView:setItemMiddle");
if (!ok) { break; }
bool ret = cobj->setItemMiddle(arg0, arg1);
tolua_pushboolean(tolua_S,(bool)ret);
return 1;
}
}while(0);
ok = true;
do{
if (argc == 1) {
int arg0;
ok &= luaval_to_int32(tolua_S, 2,(int *)&arg0, "cc.TableView:setItemMiddle");
if (!ok) { break; }
bool ret = cobj->setItemMiddle(arg0);
tolua_pushboolean(tolua_S,(bool)ret);
return 1;
}
}while(0);
ok = true;
luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d \n", "cc.TableView:setItemMiddle",argc, 1);
return 0;
#if COCOS2D_DEBUG >= 1
tolua_lerror:
tolua_error(tolua_S,"#ferror in function 'lua_cocos2dx_extension_TableView_setItemMiddle'.",&tolua_err);
#endif
return 0;
}
//
//然后修改lua_register_cocos2dx_extension_TableView函数将刚才的方法导出到lua
int lua_register_cocos2dx_extension_TableView(lua_State* tolua_S)
{
tolua_usertype(tolua_S,"cc.TableView");
tolua_cclass(tolua_S,"TableView","cc.TableView","cc.ScrollView",nullptr);
tolua_beginmodule(tolua_S,"TableView");
tolua_function(tolua_S,"new",lua_cocos2dx_extension_TableView_constructor);
tolua_function(tolua_S,"updateCellAtIndex",lua_cocos2dx_extension_TableView_updateCellAtIndex);
tolua_function(tolua_S,"setVerticalFillOrder",lua_cocos2dx_extension_TableView_setVerticalFillOrder);
tolua_function(tolua_S,"scrollViewDidZoom",lua_cocos2dx_extension_TableView_scrollViewDidZoom);
tolua_function(tolua_S,"_updateContentSize",lua_cocos2dx_extension_TableView__updateContentSize);
tolua_function(tolua_S,"getVerticalFillOrder",lua_cocos2dx_extension_TableView_getVerticalFillOrder);
tolua_function(tolua_S,"removeCellAtIndex",lua_cocos2dx_extension_TableView_removeCellAtIndex);
tolua_function(tolua_S,"initWithViewSize",lua_cocos2dx_extension_TableView_initWithViewSize);
tolua_function(tolua_S,"scrollViewDidScroll",lua_cocos2dx_extension_TableView_scrollViewDidScroll);
tolua_function(tolua_S,"reloadData",lua_cocos2dx_extension_TableView_reloadData);
//add-- 此处是我添加的3个函数
tolua_function(tolua_S,"refreshData",lua_cocos2dx_extension_TableView_refreshData);
tolua_function(tolua_S,"setItemTop",lua_cocos2dx_extension_TableView_setItemTop);
tolua_function(tolua_S,"setItemMiddle",lua_cocos2dx_extension_TableView_setItemMiddle);
//end
tolua_function(tolua_S,"insertCellAtIndex",lua_cocos2dx_extension_TableView_insertCellAtIndex);
tolua_function(tolua_S,"cellAtIndex",lua_cocos2dx_extension_TableView_cellAtIndex);
tolua_function(tolua_S,"dequeueCell",lua_cocos2dx_extension_TableView_dequeueCell);
tolua_endmodule(tolua_S);
std::string typeName = typeid(cocos2d::extension::TableView).name();
g_luaType[typeName] = "cc.TableView";
g_typeCast["TableView"] = "cc.TableView";
return 1;
}
在lua使用过程发现了CCTableView有两个问题,因此在lua使用时候做了以下修改
1,tableView滑动出可视区域,仍然可以点击被隐藏的cell上面的button。即穿透问题。
解决思路就是:在按钮上创建一个layer监听点击事件,判断点击区域的坐标是否属于tableView即可。
2, tableView点击cell上的button进行滑动,cell不可滑动。即button为wiget的时候touch被吞噬。
解决思路就是:监听点击事件同时,在开始点击储存begionPos,在点击结束判断位移的offset是否满足可以点击,满足就点击,不满足就滑动tableView
3,解决方法的lua代码如下:
--因为我们项目所有的按钮都是用UIbutton来创建的。所以只需要改UIButton即可
local ButtonEx = class("ButtonEx")
ButtonEx.__index = ButtonEx
function ButtonEx.extend(target)
local t = tolua.getpeer(target)
if not t then
t = {}
tolua.setpeer(target, t)
end
setmetatable(t, ButtonEx)
return target
end
function ButtonEx:setTableView(tableView)
self.tableView = tableView
if not self.tableView or self.touchLayer then
return
end
self:initTouchLayer()
end
function ButtonEx:initTouchLayer()
self:setSwallowTouches(false)
self.touchLayer = cc.Layer:create()
self.touchLayer:setContentSize(self:getContentSize())
self:addChild(self.touchLayer)
self.touchLayer:unregisterScriptTouchHandler()
local onTouch = function (eventType, x, y, id)
if eventType == "began" then
self.touchBeginPos = self:getWorldPos(self)
if self.isNotInView then
return true
end
if self.tableView then
local viewPos = self:getWorldPos(self.tableView)
local size = self.tableView:getViewSize()
local viewSize = cc.rect(viewPos.x, viewPos.y, size.width, size.height)
if not cc.rectContainsPoint(viewSize, cc.p(x, y)) then
self.isNotInView = true
end
end
return true
end
end
self.touchLayer:registerScriptTouchHandler(onTouch, false, 0, false)
self.touchLayer:setTouchEnabled(true)
end
function ButtonEx:isCanNotClick()
if not self.tableView then
return false
end
-- 防止界面移出clipNode仍然可点击
if self.isNotInView then
self.isNotInView = false
return true
end
-- 防止移动后仍触发点击事件
local curPos = self:getWorldPos(self)
local offset = 15
local moveX = math.abs(curPos.x - self.touchBeginPos.x) > offset
local moveY = math.abs(curPos.y - self.touchBeginPos.y) > offset
if moveX or moveY then
return true
end
end
function ButtonEx:getWorldPos(node)
local parent = node:getParent()
return parent:convertToWorldSpace(cc.p(node:getPosition()))
end
function ButtonEx:setTouchEvent(touchEvent)
local function _btnTouchEvent(sender, eventType)
if eventType == ccui.TouchEventType.ended then
--tableView
if self:isCanNotClick() then
return
end
end
if touchEvent then
touchEvent(sender, eventType);
end
end
self:addTouchEventListener(_btnTouchEvent)
end
--然后在使用的时候,只需要在cell上创建此按钮,
--然后在将此按钮上的tableView传入即可防止以上两个问题
function createButtonNormal(key, selKey, touchEvent, loadType )
local btn = ButtonEx.extend(ccui.Button:create())
btn:setTouchEnabled(true)
btn:setTouchEvent(touchEvent)
return btn
end
--使用方法
function use()
//创建cell上的按钮
local btn = createButtonNormal()
//传入正在使用的tableView
btn:setTableView(useTableView)
end
好了,做了如上修改,tableView就可以正常使用了,使用方法如下:
在panel里面可以调用如下方法,创建tableView
--@ tableView:refreshData() 刷新仅能刷新相同个数cell的view
--@ tableView:reloadData() 从组数据和view
--@ tableView:setDirection(Dir) 设置view显示横竖方向
--@ tableView:setVerticalFillOrder(Ver) 设置view数据正序或倒序
--@ tableView:initWithViewSize(size) 设置view可视区域大小
--@ tableView:setItemTop(num) 跳转到从顶部数下来第几个cell
--@ tableView:setItemMiddle(num) 同setItemTop,但以num为view中心位置
function view:createTableView(handleType, callBack)
local tableView = cc.TableView:create(cc.size(708,500))
tableView:registerScriptHandler(function (tabView)
return self:numberOfCell()(tabView)
end, cc.NUMBER_OF_CELLS_IN_TABLEVIEW)
tableView:registerScriptHandler(function (tabView, idx)
return self:sizeForIndex(tabView, idx)
end, cc.TABLECELL_SIZE_FOR_INDEX)
tableView:registerScriptHandler(function (tabView, idx)
return self:cellAtIndex(tabView, idx)
end, cc.TABLECELL_SIZE_AT_INDEX)
return tableView
end
function view:numberOfCell(tableView)
return 100
end
function view:sizeForIndex(tableView, idx)
return 705,150
end
function view:cellAtIndex(tableView, idx)
local cell = tableView:dequeueCell()
if not cell then
cell = cc.TableViewCell:create()
cell:refresh()
end
return cell
end
还有一处修改就是自带的滑动惯性有点慢,修改了CCScrollView.cpp里面的#define BOUNCE_BACK_FACTOR 0.05f ,减少这个值可以了
总结:使用tableView可以提高使用效率,有效减卡顿。稍作修改即可,挺方便的- -.
同一帧内不要做太多操作,分帧操作会很大程度提高流畅性