为了达到单元格重用的目的所以要使用TableView,但是原生的TableView添加效果很难,比如翻牌效果,比如
删除之后剩下的单元格上移、比如单元格之间的间隔等都是原生TableView无法实现的,之前在TableView的基础上做过一个UITableView,但是删除之后的单元格上移实在是无法弄出来,所以干脆 仿造TableView写一个自己的TableView。
//重新加载数据
void UITableView::reloadData()
{
_cellUsed.clear();
_length = _delegate->numberOfTableView();
cordSizeMap();
caculateCellPos();
initViewBoarderPos();
initCellPos();
}
```
//Cell的位置问题
//首先设置_innerContainer的坐标系,通过下面两句
_innerContainer->ignoreAnchorPointForPosition(true);
_innerContainer->setAnchorPoint(Vec2(0, 1));
可以改变_innerContainer的坐标系的位置,具体什么原理 尚不大清楚,如果有清楚的还请说下
改成这种的好处是:首先计算方便,最左边的Cell 都是以0开始,比如水平方向Vec2(x,0),获取到Cell的Size以Cell之间的Margin之后就可以依次计算每个Cell的位置,尤其是竖直方向,因为默认坐标系是在左下角,这样就会导致X Y 方向计算差别比较大,而且竖直方向一旦_innerContainer的大小发生变化,比如竖直方向减小的话就会像这样
绿色的位缩小后的_innerContainer,这样的话为了使上面的Cell看起来不动还得往上移动。但是这样的话所有的坐标就都乱了,因为一旦发生缩减,因为ScrollView一旦设置了_innerContainer的Size之后会自动移动到上面,结果就是最下面Cell为(0,0)点的Cell被移出屏幕外了。而且滚动位置也变了。。总之一大堆麻烦事。。。
所以设置成左上角是最理想的情况
为了计算更加方便,这里将Cell的anchorPoint设置成(0,1)。
计算cell的位置之前需要先知道Cell之间的间隔以及每个Cell的大小
//记录cell的Size
void UITableView::cordSizeMap()
{
_sizeMap.clear();
for (int i=0; i<_length; i++) {
auto size = _delegate->tableCellSizeAtIndex(i);
_sizeMap.insert(_sizeMap.begin()+i, size);
}
}
//计算Cell应该处于的位置并设置滚动区域的大小
void UITableView::caculateCellPos()
{
_posMap.clear();
if(!_isVertical) //如果是水平方向
{
float first = 0;
for (int i=0; i< _length; i++) {
float otherWidth=0;
for (int j =0; j<i; j++) {
otherWidth+=_sizeMap.at(j).width+ _margin;
}
float x = first+otherWidth;
float y = 0;
//依次计算出点 将其添加到数组_posMap中(_posMap一开始是map,后来改成了vector)
_posMap.insert(_posMap.begin()+i, Vec2(x,y));
}
//计算_innerContainer的可移动范围
float Width = _posMap.at(_length-1).x+_sizeMap.at(_length-1).width;
float Heght = _viewSize.height;
setInnerContainerSize(Size(Width,Heght));
}
else
{
float first = 0;
for (int i=0; i< _length; i++) {
float otherHeight=0;
for (int j =0; j<i; j++) {
//因为Y方向下排列Cell,所以都是负数
otherHeight-=(_sizeMap.at(j).height+ _margin);
}
float x = 0;
float y = first+otherHeight;
_posMap.insert(_posMap.begin()+i, Vec2(x,y));
}
float Width = _viewSize.width;
float Heght = fabs(_posMap.at(_length-1).y-_sizeMap.at(_length-1).height);
setInnerContainerSize(Size(Width,Heght));
}
}
```
//位置有了,那么之后应该确立边界,一旦Cell出了边界将其移除,一旦Cell将要显示将其添加进去
我的思路是:ScrollView的大小是viewSize的大小,那么就边界很明显就是ScrollView的边界,从其边界上取两个点
比如竖直方向的话,从Y=0 处取一个点,从Y= viewSize.height处取一个点,这样就是两个边界点了,
然后判断cell的位置是否在这两点之内就可以了,我的做法是将这两个点都转换成世界坐标点,将cell的位置同样转换成世界坐标点,然后判断cell的位置是否落在两点之外,如果落在之外,那么将其移除。
因为checkOutOffSide方法是在ScrollView滑动的时候触发的,所以只需要判断_cellUsed中的第一个和最后一个是否出了边界就行了,因为移动的时候这个方法会被多次出发。
其中_viewPos就是两个边界点的世界坐标值 代表左边界/上边界 代表右边界或者下边界
//计算边界点
void UITableView::initViewBoarderPos()
{
_viewPos.clear();
if(!_isVertical)
{
//左
auto pos = _innerContainer->convertToWorldSpace(Vec2(0,0));
_viewPos = pos;
//右
auto pos2 = _innerContainer->convertToWorldSpace(Vec2(_viewSize.width,0));
_viewPos = pos2;
}
else
{
//上
auto pos = convertToWorldSpace(Vec2(0,_viewSize.height));
_viewPos = pos;
//下
auto pos2 = convertToWorldSpace(Vec2(0,0));
_viewPos = pos2;
}
}
//检查是否落在屏幕之外了:
void UITableView::checkOutOffSide()
{
if (!_isVertical) {
if (!_cellUsed.empty()) {
auto cell = _cellUsed.at(0);
auto cellPos = _innerContainer->convertToWorldSpace(cell->getPosition());
if (cellPos.x + _sizeMap.at(0).width < _viewPos.x) {
_cellFreed.pushBack(cell);
_innerContainer->removeChild(cell);
_cellUsed.erase(0);
return;
}
if(_cellUsed.empty())
return;
cell = _cellUsed.back();
cellPos = _innerContainer->convertToWorldSpace(cell->getPosition());
if (cellPos.x > _viewPos.x) {
_cellFreed.pushBack(cell);
_innerContainer->removeChild(cell);
_cellUsed.erase(_cellUsed.size()-1);
return;
}
}
}
else
{
if (!_cellUsed.empty()) {
auto cell = _cellUsed.at(0);
auto cellPos = _innerContainer->convertToWorldSpace(cell->getPosition());
if (cellPos.y - _sizeMap.at(cell->getIdx()).height > _viewPos.y) {
_innerContainer->removeChild(cell);
_cellFreed.pushBack(cell);
_cellUsed.erase(0);
return;
}
if(_cellUsed.empty())
return;
cell = _cellUsed.back();
cellPos = _innerContainer->convertToWorldSpace(cell->getPosition());
if (cellPos.y < _viewPos.y) {
_innerContainer->removeChild(cell);
_cellFreed.pushBack(cell);
_cellUsed.erase(_cellUsed.size()-1);
return;
}
}
}
}
//边界点确定了之后,应该将Cell添加进去了,
void UITableView::initCellPos()
{
for (int i=0; i<_posMap.size(); i++) {
auto nextPos = _posMap.at(i);
nextPos = _innerContainer->convertToWorldSpace(nextPos);
if(!_isVertical)
{
//如果将要添加的Cell的位置在边界内,就一直往里添加
if(nextPos.x < _viewPos.at(1).x) {
auto newCell = _delegate->tableCellAtIndex(i);
initCellProperty(newCell,i); //这个方法是设置cell的位置以及一些其他设置
_cellUsed.pushBack(newCell);
_innerContainer-> addChild(newCell);
}
else
{
break;
}
}
else
{
if(nextPos.y > _viewPos.at(1).y)
{
auto newCell = _delegate->tableCellAtIndex(i);
initCellProperty(newCell,i);
_cellUsed.pushBack(newCell);
_innerContainer-> addChild(newCell);
}
else
{
break;
}
}
}
}
```
之后就是在ScrollView滚动的时候动态的添加Cell以及移除Cell了,为了让这个方法在触摸滑动的时候以及惯性移动的时候都调用这个方法,我重写了ScrollVew的onTouchMoved方法,以及在ScrollView当中的自动移动方法中添加了一个该方法的调用,ScrollView中声明scrollViewDidScroll 为虚方法,这样就可以在自动移动的时候调用到这里
在ScrollView::processAutoScrolling(float deltaTime)这个方法中添加了一个scrollViewDidScroll的调用
void UITableView::onTouchMoved(Touch *touch, Event *unusedEvent)
{
ScrollView::onTouchMoved(touch, unusedEvent);
scrollViewDidScroll();
}
void UITableView::scrollViewDidScroll()
{
checkOutOffSide();
if(_cellUsed.empty())
return;
if(!_isVertical)
{
auto cell = _cellUsed.at(_cellUsed.size()-1);
if(cell->getIdx() < _length-1)//说明其后面还有cell
{
int index = cell->getIdx()+1;
auto nextPos = _posMap.at(index);
nextPos = _innerContainer->convertToWorldSpace(nextPos);
if(nextPos.x < _viewPos.at(1).x)
{
auto newCell = _delegate->tableCellAtIndex(index);
initCellProperty(newCell,index);
_cellUsed.pushBack(newCell);
_innerContainer-> addChild(newCell);
}
}
cell = _cellUsed.at(0);
if(cell->getIdx() >0)//说明其前面还有cell
{
int index = cell->getIdx()-1; //前面那个cell 的index
auto nextPos = _posMap.at(index);
nextPos = _innerContainer->convertToWorldSpace(nextPos);
if(nextPos.x +_sizeMap.at(index).width > _viewPos.at(0).x)
{
auto newCell = _delegate->tableCellAtIndex(index);
initCellProperty(newCell,index);
_cellUsed.insert(0, newCell);
_innerContainer-> addChild(newCell);
}
}
}
else
{
auto cell = _cellUsed.at(_cellUsed.size()-1);
if(cell->getIdx() < _length-1)//说明其后面还有cell
{
int index = cell->getIdx()+1;
auto nextPos = _posMap.at(index);
nextPos = _innerContainer->convertToWorldSpace(nextPos);
if(nextPos.y > _viewPos.at(1).y)
{
auto newCell = _delegate->tableCellAtIndex(index);
initCellProperty(newCell,index);
_innerContainer-> addChild(newCell);
_cellUsed.pushBack(newCell);
}
}
cell = _cellUsed.at(0);
if(cell->getIdx() > 0)//说明其前面还有cell
{
int index = cell->getIdx()-1;
auto nextPos = _posMap.at(index);
nextPos = _innerContainer->convertToWorldSpace(nextPos);
if(nextPos.y -_sizeMap.at(index).height < _viewPos.at(0).y)
{
auto newCell = _delegate->tableCellAtIndex(index);
initCellProperty(newCell,index);
_innerContainer-> addChild(newCell);
_cellUsed.insert(0, newCell);
}
}
}
}
```
到这里为止,已经实现了Cell的重用功能,删除的功能有些复杂,删除了之后需要将后续的Cell修改位置,以及将该Cell所对应的_posMap以及_sizeMap中的值删除掉,同时将_cellUsed中位于删除位置之后的cell的index前移一位,之后为了能让删除之后的cell移动过去,还得重写计算之后的cell的位置,因为cell的Size不是固定的所以,我使用了相对位移 来算,因为之后的cell的位置都是向前移动相同的距离,所以只要计算出来这个距离就行了
void UITableView::removeCellAtIndex(int idx)
{
int cordI = -1;
for (int i=0; i<_cellUsed.size(); i++) {
if(_cellUsed.at(i)->getIdx() == idx)
{
cordI = i;
continue;
}
//为之后的Cell调整idx
if (cordI!=-1) {
auto cell = _cellUsed.at(i);
cell->setIdx(cell->getIdx()-1);//之后的Cell的 idx依次前移
}
}
if (cordI!=-1) {
auto cell = _cellUsed.at(cordI);
_cellUsed.erase(cordI); //删除之后后面的会向前移动一位
_cellFreed.pushBack(cell);
removeChild(cell);
updateWithRemove(idx);
for (int i= cordI; i<_cellUsed.size(); i++) {
auto cell = _cellUsed.at(i);
auto label = (Label*) cell->getChildByTag(9);
label->setString(__String::createWithFormat("%d",cell->getIdx())->getCString());
//因为这里定的是一个固定的时间,所以其速度不一定相同,删除一个大的cell移动的快,删除一个小的cell移动的慢,也可以固定速度,只要将距离求出来就可以了
auto pos = _posMap.at(cell->getIdx());
cell->runAction(MoveTo::create(0.3, pos));
}
}
}
void UITableView::updateWithRemove(int idx)
{
if (!_isVertical) {
float mv_x =0;
//更新_posMap
if (idx != _posMap.size()-1) { //如果不是最后一个
mv_x = _sizeMap.at(idx).width + _margin;
//新增Cell
int index = _cellUsed.back()->getIdx()+1;
float delt = 0;
while(delt < mv_x) {
if (index==_length-1) {
break;
}
auto newCell = _delegate->tableCellAtIndex(index);
initCellProperty(newCell,index);
newCell->setPosition(_posMap);//因为cellUsed中的index统一前移了一位,但是_posMap还没移动,所以这里用+1
_cellUsed.pushBack(newCell);
addChild(newCell);
delt += _sizeMap.width+_margin;
index++;
}
//更新位置
for(int i=idx;i<_posMap.size()-1;i++)
{
auto newX = _posMap.at(i+1).x - mv_x;
auto newPos = Vec2(newX,0);
_posMap.at(i) = newPos;
}
}
else
{
mv_x = _sizeMap.at(idx).width;
}
//删除最后一个元素删掉
_posMap.pop_back();
//更新_sizeMap
auto itor = _sizeMap.begin()+idx;
_sizeMap.erase(itor);
//更新Length
_length--;
//更新大小
auto size = getInnerContainerSize();
size.width -= mv_x;
setInnerContainerSize(size);
initViewBoarderPos();
}
else
{
//will updating .............
float mv_y =0;
//更新_posMap
if (idx != _posMap.size()-1) { //如果不是最后一个
mv_y = _sizeMap.at(idx).height + _margin;
//新增Cell
int indx = _cellUsed.back()->getIdx()+1;
float delt = 0;
while(delt < mv_y) {
if (indx==_length-1) {
break;
}
log("index = %d ",indx);
log("cell insert -------------------%d",indx);
auto newCell = _delegate->tableCellAtIndex(indx);
initCellProperty(newCell,indx);
newCell->setContentSize(_sizeMap);
auto pos =_posMap;
newCell->setPosition(pos);//因为cellUsed中的index统一前移了一位,但是_posMap还没移动,所以这里用+1
_cellUsed.pushBack(newCell);
_innerContainer->addChild(newCell);
delt += _sizeMap.height+_margin;
indx++;
}
//更新位置
for(int i=idx;i<_posMap.size()-1;i++)
{
auto newY = _posMap.at(i+1).y + mv_y;
auto newPos = Vec2(0,newY);
_posMap.at(i) = newPos;
}
}
else
{
mv_y = _sizeMap.at(idx).height;
}
//删除最后一个元素删掉
_posMap.pop_back();
//更新_sizeMap
auto itor = _sizeMap.begin()+idx;
_sizeMap.erase(itor);
//更新Length
_length--;
//记录当前滚动结点的位置
auto pos = getInnerContainerPosition();
//更新大小
auto size = getInnerContainerSize();
size.height -= mv_y;
setInnerContainerSize(size);
initViewBoarderPos();
setInnerContainerPosition(pos);
}
}
```