需求描述
Cocos原生tableview可以非常方便的实现横向/竖向列表
有一天需求突然变成倾斜的横向列表
需求效果如图所示
原理分析
CCTableView.h
std::vector<float> _vCellsPositions;
列表中每个元素的相对位置是确定的
保存在以上数据结构中,仅存了一个数字
因为默认HORIZONTAL列表元素的y值始终为0
VERTICAL列表元素的x值始终为0
这样就可以把列表的全部元素想象成一条竖着或者横着的长龙
显示区域只能显示龙的一小部分
用户拖动决定了龙露出来的位置
因此为了实现斜向滑动需要做以下两点修改
1.把元素位置由横向排列,改为斜着排列
2.把用户拖动有横向拖动,改为斜着拖动
代码实现
首先我们在ScrollView类中定义一个变量(TableView继承了ScrollView)
_skewTangent
表示横向列表的倾斜角正弦值
默认不倾斜正弦值为0
private: float _skewTangent; public: void setSkewTangent(float val){ _skewTangent = val; }
然后我们设置元素们的位置
源代码中横向列表的y值是0
注释掉它,替换为由正弦计算得出的y值
Vec2 TableView::__offsetFromIndex(ssize_t index) { Vec2 offset; Size cellSize; switch (this->getDirection()) { case Direction::HORIZONTAL: //offset = Vec2(_vCellsPositions[index], 0.0f); offset = Vec2(_vCellsPositions[index], -1 * _skewTangent * _vCellsPositions[index]); break; default: offset = Vec2(0.0f, _vCellsPositions[index]); break; } return offset; }
再解决拖动问题
在用户横向拖动的时候
增加Y方向上的位移即可
/* change moveDistance by _skewTangent*/
void ScrollView::onTouchMoved(Touch* touch, Event* event) { if (!this->isVisible()) { return; } if (std::find(_touches.begin(), _touches.end(), touch) != _touches.end()) { if (_touches.size() == 1 && _dragging) { // scrolling Vec2 moveDistance, newPoint; Rect frame; float newX, newY; frame = getViewRect(); newPoint = this->convertTouchToNodeSpace(_touches[0]); moveDistance = newPoint - _touchPoint; float dis = 0.0f; if (_direction == Direction::VERTICAL) { dis = moveDistance.y; float pos = _container->getPosition().y; if (!(minContainerOffset().y <= pos && pos <= maxContainerOffset().y)) { moveDistance.y *= BOUNCE_BACK_FACTOR; } } else if (_direction == Direction::HORIZONTAL) { dis = moveDistance.x; float pos = _container->getPosition().x; if (!(minContainerOffset().x <= pos && pos <= maxContainerOffset().x)) { moveDistance.x *= BOUNCE_BACK_FACTOR; } } else { dis = sqrtf(moveDistance.x*moveDistance.x + moveDistance.y*moveDistance.y); float pos = _container->getPosition().y; if (!(minContainerOffset().y <= pos && pos <= maxContainerOffset().y)) { moveDistance.y *= BOUNCE_BACK_FACTOR; } pos = _container->getPosition().x; if (!(minContainerOffset().x <= pos && pos <= maxContainerOffset().x)) { moveDistance.x *= BOUNCE_BACK_FACTOR; } } if (!_touchMoved && fabs(convertDistanceFromPointToInch(dis)) < MOVE_INCH ) { //CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y); return; } if (!_touchMoved) { moveDistance = Vec2::ZERO; } _touchPoint = newPoint; _touchMoved = true; if (_dragging) { switch (_direction) { case Direction::VERTICAL: moveDistance = Vec2(0.0f, moveDistance.y); break; case Direction::HORIZONTAL: /* change moveDistance by _skewTangent*/ //moveDistance = Vec2(moveDistance.x, 0.0f); moveDistance = Vec2(moveDistance.x, _skewTangent * moveDistance.x); break; default: break; } newX = _container->getPosition().x + moveDistance.x; newY = _container->getPosition().y + moveDistance.y; _scrollDistance = moveDistance; this->setContentOffset(Vec2(newX, newY)); } } else if (_touches.size() == 2 && !_dragging) { const float len = _container->convertTouchToNodeSpace(_touches[0]).getDistance( _container->convertTouchToNodeSpace(_touches[1])); this->setZoomScale(this->getZoomScale()*len/_touchLength); } } }
这样子基本满足了需求
但会产生一些问题
在滑动到左端和右端时
Y方向上位置会出错
还要加上以下代码处理
void ScrollView::relocateContainer(bool animated) { Vec2 oldPoint, min, max; float newX, newY; min = this->minContainerOffset(); max = this->maxContainerOffset(); oldPoint = _container->getPosition(); newX = oldPoint.x; newY = oldPoint.y; if (_direction == Direction::BOTH || _direction == Direction::HORIZONTAL) { newX = MAX(newX, min.x); newX = MIN(newX, max.x); /* code goes here*/ if (_skewTangent != 0.0f && (newX == min.x || newX == max.x)) { //斜向滑动边界处理 newY = MIN(newY, max.y + _skewTangent * max.x); newY = MAX(newY, min.y + _skewTangent * min.x); } } if (_direction == Direction::BOTH || _direction == Direction::VERTICAL) { newY = MIN(newY, max.y); newY = MAX(newY, min.y); } if (newY != oldPoint.y || newX != oldPoint.x) { this->setContentOffset(Vec2(newX, newY), animated); } }
也可以考虑通过修改以下方法
minContainerOffset()
maxContainerOffset()
但这次我没有这么做
以上遍实现了该功能