版本源码来自2.x,转载请注明
另我实现了可以循环的版本http://blog.csdn.net/u011225840/article/details/31354703
bool CCScrollView::ccTouchBegan(CCTouch* touch, CCEvent* event)
{
if (!this->isVisible() || !m_bCanTouch)
{
return false;
}
CCRect frame = getViewRect();
//dispatcher does not know about clipping. reject touches outside visible bounds.
/*
1. ccScrollView只允许至多两个触摸点,多于两个后将不会认为发成了触摸。
2. 当CCScrollView处于移动状态时,在此状态下新发生触摸将不会被认为发生。
3.注意frame不是当前的尺寸,而是当前ViewSize的frame,也就是触摸点必须在显示的Rect内才会认定为触摸(可以通过setViewSize来设置大小)
*/
if (m_pTouches->count() > 2 ||
m_bTouchMoved ||
!frame.containsPoint(m_pContainer->convertToWorldSpace(m_pContainer->convertTouchToNodeSpace(touch))))
{
m_pTouches->removeAllObjects();
return false;
}
if (!m_pTouches->containsObject(touch))
{
m_pTouches->addObject(touch);
}
//CCLOG("CCScrollView::ccTouchBegan %d", m_pTouches->count());
/*
当触摸点为1的时候,设置单点触摸的属性。尤其是m_bDragging属性表示触摸行为是拖动
*/
if (m_pTouches->count() == 1)
{ // scrolling
m_tTouchPoint = this->convertTouchToNodeSpace(touch);
m_bTouchMoved = false;
m_bDragging = true; //dragging started
m_tScrollDistance = ccp(0.0f, 0.0f);
m_fTouchLength = 0.0f;
}
/*
当触摸点个数为2时,设置双点触摸的属性
*/
else if (m_pTouches->count() == 2)
{
m_tTouchPoint = ccpMidpoint(this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)),
this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1)));
m_fTouchLength = ccpDistance(m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)),
m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1)));
m_bDragging = false;
}
return true;
}
void CCScrollView::ccTouchMoved(CCTouch* touch, CCEvent* event)
{
if (!this->isVisible())
{
return;
}
/*
如果此时不允许滚动,则退出。这个可以通过set函数设置。默认为false
*/
if(this->m_bScrollLock)
{
return;
}
if (m_pTouches->containsObject(touch))
{
/*
啊哦,好玩的来咯。
滚动状态时
*/
if (m_pTouches->count() == 1 && m_bDragging)
{ // scrolling
CCPoint moveDistance, newPoint, maxInset, minInset;
CCRect frame;
float newX, newY;
frame = getViewRect();
//获得当前点的坐标,并且获得当前点与上一次触碰点的距离(moveDistance也是CCPoint,x与y是当前点与上一点的x距离,y距离)
newPoint = this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0));
moveDistance = ccpSub(newPoint, m_tTouchPoint);
float dis = 0.0f;
//如果有方向的限定,根据方向限定获取对应的距离
if (m_eDirection == kCCScrollViewDirectionVertical)
{
dis = moveDistance.y;
}
else if (m_eDirection == kCCScrollViewDirectionHorizontal)
{
dis = moveDistance.x;
}
else
{
dis = sqrtf(moveDistance.x*moveDistance.x + moveDistance.y*moveDistance.y);
}
//如果移动距离过短,则不判断发生了移动
if (!m_bTouchMoved && fabs(convertDistanceFromPointToInch(dis)) < MOVE_INCH )
{
//CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y);
return;
}
//第一次移动,则将moveDistance置0
if (!m_bTouchMoved)
{
moveDistance = CCPointZero;
}
m_tTouchPoint = newPoint;
m_bTouchMoved = true;
//点必须在viewRect内部
if (frame.containsPoint(this->convertToWorldSpace(newPoint)))
{
//根据可以移动的direction来设置moveDistance
switch (m_eDirection)
{
case kCCScrollViewDirectionVertical:
moveDistance = ccp(0.0f, moveDistance.y);
break;
case kCCScrollViewDirectionHorizontal:
moveDistance = ccp(moveDistance.x, 0.0f);
break;
default:
break;
}
//这个版本无用啊。。。。
maxInset = m_fMaxInset;
minInset = m_fMinInset;
//获取容器的新坐标,注意是容器哦
newX = m_pContainer->getPosition().x + moveDistance.x;
newY = m_pContainer->getPosition().y + moveDistance.y;
//滚动的CCPoint矢量设置
m_tScrollDistance = moveDistance;
this->setContentOffset(ccp(newX, newY));
}
}
//双点触摸时,效果是缩放,len是双点触摸每次移动时的距离,
//而m_fTouchLength是双点开始时的距离,会根据move过程中距离与初始距离的比例进行缩放
else if (m_pTouches->count() == 2 && !m_bDragging)
{
const float len = ccpDistance(m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)),
m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1)));
this->setZoomScale(this->getZoomScale()*len/m_fTouchLength);
}
}
}
单点触摸时,调用了一个函数叫setContentOffset。下面继续分析contentOffset。
void CCScrollView::setContentOffset(CCPoint offset, bool animated/* = false*/)
{
//默认情况,不做处理
if (animated)
{ //animate scrolling
this->setContentOffsetInDuration(offset, BOUNCE_DURATION);
}
//好玩的东西哦
else
{ //set the container position directly
//是否做越界处理,什么是越界,就是当你拖动整个容器时,如果已经到了容器的边界,还能不能再拖动,可以通过set函数进行设置
if (!m_bBounceable)
{
const CCPoint minOffset = this->minContainerOffset();
const CCPoint maxOffset = this->maxContainerOffset();
offset.x = MAX(minOffset.x, MIN(maxOffset.x, offset.x));
offset.y = MAX(minOffset.y, MIN(maxOffset.y, offset.y));
}
//CCLOG("The offset x is %f , y is %f",offset.x,offset.y);
m_pContainer->setPosition(offset);
//伟大的delegate来了,当你在滚动过程中想做除了基本界面滚动的额外操作时,请根据自己的不同情况,实现该delegate~完美的依赖抽象的设计,nice
if (m_pDelegate != NULL)
{
m_pDelegate->scrollViewDidScroll(this);
}
}
}
void CCScrollView::setZoomScale(float s)
{
if (m_pContainer->getScale() != s)
{
CCPoint oldCenter, newCenter;
CCPoint center;
//设置缩放中心
if (m_fTouchLength == 0.0f)
{
center = ccp(m_tViewSize.width*0.5f, m_tViewSize.height*0.5f);
center = this->convertToWorldSpace(center);
}
else
{
center = m_tTouchPoint;
}
//缩放后中心的位置相对于world坐标系会产生offset,这里将offset进行计算
oldCenter = m_pContainer->convertToNodeSpace(center);
m_pContainer->setScale(MAX(m_fMinScale, MIN(m_fMaxScale, s)));
newCenter = m_pContainer->convertToWorldSpace(oldCenter);
const CCPoint offset = ccpSub(center, newCenter);
//delegate的又一次出现
if (m_pDelegate != NULL)
{
m_pDelegate->scrollViewDidZoom(this);
}
//将产生的offset进行处理
this->setContentOffset(ccpAdd(m_pContainer->getPosition(),offset));
}
}
void CCScrollView::ccTouchEnded(CCTouch* touch, CCEvent* event)
{
if (!this->isVisible())
{
return;
}
//将touch从pTouches中移除
if (m_pTouches->containsObject(touch))
{
//当剩下一个touch时,需要在每一帧调用方法deaccelerateScrolling
if (m_pTouches->count() == 1 && m_bTouchMoved)
{
this->schedule(schedule_selector(CCScrollView::deaccelerateScrolling));
}
m_pTouches->removeObject(touch);
//CCLOG("CCScrollView::ccTouchEnded %d", m_pTouches->count());
//m_pDelegate->scrollViewDidStop(this);
}
//没有touch时,需要设置状态
if (m_pTouches->count() == 0)
{
m_bDragging = false;
m_bTouchMoved = false;
}
}
在这个函数中,有一个地方没有理解,求大神指点
void CCScrollView::deaccelerateScrolling(float dt)
{
//如果刚好在帧开始前 又有一个触摸点发生了began,造成了滚动状态,则取消并返回
if (m_bDragging)
{
this->unschedule(schedule_selector(CCScrollView::deaccelerateScrolling));
return;
}
//好玩的东西来咯
float newX, newY;
CCPoint maxInset, minInset;
CCLOG("The end distance is %f",m_tScrollDistance.x);
//这里我不清楚为啥要出来,我用输出发现在move中,已经将此offset设置过了,不知为何还要设置,求大神解答。
m_pContainer->setPosition(ccpAdd(m_pContainer->getPosition(), m_tScrollDistance));
//是否允许越界,获得的inset信息
if (m_bBounceable)
{
maxInset = m_fMaxInset;
minInset = m_fMinInset;
}
else
{
maxInset = this->maxContainerOffset();
minInset = this->minContainerOffset();
}
//check to see if offset lies within the inset bounds
newX = MIN(m_pContainer->getPosition().x, maxInset.x);
newX = MAX(newX, minInset.x);
newY = MIN(m_pContainer->getPosition().y, maxInset.y);
newY = MAX(newY, minInset.y);
newX = m_pContainer->getPosition().x;
newY = m_pContainer->getPosition().y;
m_tScrollDistance = ccpSub(m_tScrollDistance, ccp(newX - m_pContainer->getPosition().x, newY - m_pContainer->getPosition().y));
m_tScrollDistance = ccpMult(m_tScrollDistance, SCROLL_DEACCEL_RATE);
this->setContentOffset(ccp(newX,newY));
if ((fabsf(m_tScrollDistance.x) <= SCROLL_DEACCEL_DIST &&
fabsf(m_tScrollDistance.y) <= SCROLL_DEACCEL_DIST) ||
newY > maxInset.y || newY < minInset.y ||
newX > maxInset.x || newX < minInset.x ||
newX == maxInset.x || newX == minInset.x ||
newY == maxInset.y || newY == minInset.y)
{
this->unschedule(schedule_selector(CCScrollView::deaccelerateScrolling));
//越界动画,从越界部分慢慢移动到不越界状态的函数。
this->relocateContainer(true);
//伟大的delegate。。。
m_pDelegate->scrollViewDidStop(this);
}
}
void CCScrollView::relocateContainer(bool animated)
{
//这个函数将容器从当前地方通过动画移动到玩家自己设置的允许偏移的地方
CCPoint oldPoint, min, max;
float newX, newY;
//偏移值自己可以设置
min = this->minContainerOffset();
max = this->maxContainerOffset();
oldPoint = m_pContainer->getPosition();
newX = oldPoint.x;
newY = oldPoint.y;
if (m_eDirection == kCCScrollViewDirectionBoth || m_eDirection == kCCScrollViewDirectionHorizontal)
{
newX = MAX(newX, min.x);
newX = MIN(newX, max.x);
}
if (m_eDirection == kCCScrollViewDirectionBoth || m_eDirection == kCCScrollViewDirectionVertical)
{
newY = MIN(newY, max.y);
newY = MAX(newY, min.y);
}
//还是调用setContentOffset,但是需要动画
if (newY != oldPoint.y || newX != oldPoint.x)
{
this->setContentOffset(ccp(newX, newY), animated);
}
}
void CCScrollView::setContentOffsetInDuration(CCPoint offset, float dt)
{
CCFiniteTimeAction *scroll, *expire;
//滚动的偏移动画
scroll = CCMoveTo::create(dt, offset);
//滚动完成后的动画(负责停止performedAnimatedScroll,并且调用delegate)
expire = CCCallFuncN::create(this, callfuncN_selector(CCScrollView::stoppedAnimatedScroll));
m_pContainer->runAction(CCSequence::create(scroll, expire, NULL));
//负责不停调用delegate
this->schedule(schedule_selector(CCScrollView::performedAnimatedScroll));
}