首页 > 代码库 > ScrollView 重点分析

ScrollView 重点分析

打开ScrollView,跟我一起看源码。

重点1:ScrollView中的container : Node* 的锚点AnchorPoint是被重置为Vec2(0,0),也就是设置大地图的时候要求用左下角作为依赖的参照点。但是,看看Layer的源码,

// Layer
Layer::Layer()
: _touchEnabled(false)
, _accelerometerEnabled(false)
, _keyboardEnabled(false)
, _touchListener(nullptr)
, _keyboardListener(nullptr)
, _accelerationListener(nullptr)
, _touchMode(Touch::DispatchMode::ALL_AT_ONCE)
, _swallowsTouches(true)
{
    _ignoreAnchorPointForPosition = true;
    setAnchorPoint(Vec2(0.5f, 0.5f));
}

ScrollView继承自Layer。


首先回顾一下Layer的坐标系相关:


在Node中有个属性_ignoreAnchorPointForPosition,默认为false,不能忽略锚点做节点坐标系中的影响。

Layer和Scene继承自Node,默认contentSize是也为0的,不同的是_ignoreAnchorPointForPosition为true,挂载在其他渲染树是都是以(0,0)为参考点,无论怎么设置_ignoreAnchorPointForPosition属性都不会对其位置产生变化。以父节点的左下角为节点坐标系为起点很容易符合我们的想法,但是要知道确切意识到是以父节点的contentSize的左下角为原点。而Node、Layer、Scene等容器默认的contentSize是(0,0).而在Sprite为容器的节点坐标系中就不太好控制了,每次应该认真去计算父节点的 contentSize的左下角位置,才能设置当年子节点的具体位置。这是父节点的相关,当然子节点都是以自身锚地为参照点去setPisition().例如,这样子才能将ScrollView正确的显示在屏幕中间,要把ScrollView的cententSize的左下角点因为设置了大小产生的偏移移除掉——这个时候我们多么希望contentSize的_ignoreAnchorPointForPosition也是false,这样子就可以使用类似精灵的操作模式,~~可惜不是。例子代码:

pScrollView->setPosition(visibleSize.width/2 - pScrollView->getContentSize().width/2,visibleSize.height/2-pScrollView->getContentSize().height/2);


知道了container的锚点相关,我们就可以正确的处理ScrollView的显示问题了。


dragging,拖拽。当touchBegan时设置这个状态,和设置初始的移动位置:

bool ScrollView::onTouchBegan(Touch* touch, Event* event)
{
    if (!this->isVisible())
    {
        return false;
    }
    
    Rect frame = getViewRect();

    //dispatcher does not know about clipping. reject touches outside visible bounds.
    if (_touches.size() > 2 ||
        _touchMoved          ||
        !frame.containsPoint(touch->getLocation()))
    {
        return false;
    }

    if (std::find(_touches.begin(), _touches.end(), touch) == _touches.end())
    {
        _touches.push_back(touch);
    }

    if (_touches.size() == 1)
    { // scrolling
        _touchPoint     = this->convertTouchToNodeSpace(touch);
        _touchMoved     = false;
        _dragging     = true; //dragging started
        _scrollDistance = Vec2(0.0f, 0.0f);
        _touchLength    = 0.0f;
    }
    else if (_touches.size() == 2)
    {
        _touchPoint = (this->convertTouchToNodeSpace(_touches[0]).getMidpoint(
                        this->convertTouchToNodeSpace(_touches[1])));
        
        _touchLength = _container->convertTouchToNodeSpace(_touches[0]).getDistance(
                       _container->convertTouchToNodeSpace(_touches[1]));
        
        _dragging  = false;
    } 
    return true;
}

拖拽开始后就播放相关的动画和设置坐标:


触摸结束之后Reset除位移以为的状态:

void ScrollView::onTouchEnded(Touch* touch, Event* event)
{
    if (!this->isVisible())
    {
        return;
    }
    
    auto touchIter = std::find(_touches.begin(), _touches.end(), touch);
    
    if (touchIter != _touches.end())
    {
        if (_touches.size() == 1 && _touchMoved)
        {
            this->schedule(schedule_selector(ScrollView::deaccelerateScrolling));
        }
        _touches.erase(touchIter);
    } 

    if (_touches.size() == 0)
    {
        _dragging = false;    
        _touchMoved = false;
    }
}


开始减速直至停止播放动画:

void ScrollView::deaccelerateScrolling(float dt)
{
    if (_dragging)
    {
        this->unschedule(schedule_selector(ScrollView::deaccelerateScrolling));
        return;
    }
    
    float newX, newY;
    Vec2 maxInset, minInset;
    
    _container->setPosition(_container->getPosition() + _scrollDistance);
    
    if (_bounceable)
    {
        maxInset = _maxInset;
        minInset = _minInset;
    }
    else
    {
        maxInset = this->maxContainerOffset();
        minInset = this->minContainerOffset();
    }
    
    newX = _container->getPosition().x;
    newY = _container->getPosition().y;
    
    _scrollDistance     = _scrollDistance * SCROLL_DEACCEL_RATE;
    this->setContentOffset(Vec2(newX,newY));
    
    if ((fabsf(_scrollDistance.x) <= SCROLL_DEACCEL_DIST &&
         fabsf(_scrollDistance.y) <= SCROLL_DEACCEL_DIST) ||
        newY >= maxInset.y || newY <= minInset.y ||
        newX >= maxInset.x || newX <= minInset.x)
    {
        this->unschedule(schedule_selector(ScrollView::deaccelerateScrolling));
        this->relocateContainer(true);
    }
}

重点在于如何计算offset的偏移位置,待续。先从tableview开始下手,处理简单的坐标偏移和移动动画,另外解决更新操作。待续!!





补记:_isUsedCellsDirty 鉴于TableView不能动态更新的问题,可以考虑把_isUsedCellsDirty设为true,然后在渲染的时候就会重新渲染啦。



ScrollView 重点分析