• ‎Cocos2d-x 学习笔记(21.1) ScrollView “甩出”效果与 deaccelerateScrolling 方法


    1. 简介

    “甩出”效果是当我们快速拖动container并松开后,container继续朝原方向运动,但是渐渐减速直到停止的效果。

    ScrollView的onTouchEnded方法会设置Timer,间隔0、延迟0、无限次数,回调函数是deaccelerateScrolling方法。说明触摸结束时,当该方法不被unschedule时将每帧执行一次。

    2. setContentSize

    先看一个和“甩出”有关的方法setContentSize:

    ScrollView的setContentSize重写了Node的方法,设置的是container的尺寸。

    注意,还调用了updateInset方法,执行:

      _maxInset = this->maxContainerOffset();
      _maxInset.set(_maxInset.x + _viewSize.width * INSET_RATIO, _maxInset.y + _viewSize.height * INSET_RATIO);
      _minInset = this->minContainerOffset();
      _minInset.set(_minInset.x - _viewSize.width * INSET_RATIO, _minInset.y - _viewSize.height * INSET_RATIO);

    设置了_maxInset:maxContainerOffset的值加部分可视范围。_minInset:minContainerOffset减部分可视范围。

    INSET_RATIO为0.2,可以自行修改。

    _maxInset和_minInset相当于扩大了偏移范围,并且只在有回弹效果时才有用处。

    3. deaccelerateScrolling

    情况一:开启回弹,不执行setContentSize

    deaccelerateScrolling方法中会判断ScrollView是否设置了回弹效果。

    我们先看有回弹的情况,如果不对ScrollView执行setContentSize,这样的话maxInset和minInset均为0。

        if (_bounceable) //有回弹则true
        {
            maxInset = _maxInset;
            minInset = _minInset;
        }

    接下来设置container位置,代码不再粘贴。

    然后,该if语句第二第三个条件永远满足,那么将会执行unschedule,说明了deaccelerateScrolling将会只执行这一次。之后,当container此时超出范围时,再通过relocateContainer方法设置回弹效果,不再赘述。

    if (
      (fabsf(_scrollDistance.x) <= SCROLL_DEACCEL_DIST && fabsf(_scrollDistance.y) <= SCROLL_DEACCEL_DIST) ||
      ((_direction == Direction::BOTH || _direction == Direction::VERTICAL) && (newY >= maxInset.y || newY <= minInset.y)) ||
      ((_direction == Direction::BOTH || _direction == Direction::HORIZONTAL) && (newX >= maxInset.x || newX <= minInset.x))
    )
    {
      this->unschedule(CC_SCHEDULE_SELECTOR(ScrollView::deaccelerateScrolling));
      this->relocateContainer(true);
    }

    因为deaccelerateScrolling仅执行一次,仅在设置一次container的位置,所以没有“甩出”效果。

    情况二:没有回弹

    当没有回弹时,maxInset和minInset被设置成container偏移范围的界限。

    if (_bounceable) //false
        {
        //...
        }
    else
        {
            maxInset = this->maxContainerOffset();
            minInset = this->minContainerOffset();
        }  

    那么,如果拖动在范围之内,接下来if判断的3个条件中后两个将为false,我们看第一个条件:

      (fabsf(_scrollDistance.x) <= SCROLL_DEACCEL_DIST && fabsf(_scrollDistance.y) <= SCROLL_DEACCEL_DIST)

    _scrollDistance在if之前执行了:

      _scrollDistance = _scrollDistance * SCROLL_DEACCEL_RATE;

    SCROLL_DEACCEL_DIST是一个界限,当_scrollDistance绝对值小于它时,使得if第一个条件满足,执行unschedule,deaccelerateScrolling将不会在下一帧执行。

    onTouchMoved结束时保存了_scrollDistance,该向量是两次Moved之差。在触摸结束后,每帧执行deaccelerateScrolling时,_scrollDistance都会乘以小于0的系数SCROLL_DEACCEL_RATE,使得_scrollDistance渐渐变小。

    并且,每次deaccelerateScrolling方法开始会根据当前_scrollDistance设置container的新位置:

      _container->setPosition(_container->getPosition() + _scrollDistance);

    到这里,显而易见,虽然触摸结束了,但是deaccelerateScrolling将会在触摸结束后每帧执行,设置container的新位置,而每帧位置的增长都渐渐变小,实现了“甩动甩出”的效果。

    当_scrollDistance小于界限值时,将会unschedule销毁Timer,deaccelerateScrolling不会在下一阵执行,我们看到的“甩出”效果就结束了。

    还有一个问题,在无回弹情况下,如果“甩出”时container到了边界是如何处理的?

    看deaccelerateScrolling部分代码:

        _container->setPosition(_container->getPosition() + _scrollDistance); //假设此时设置位置后越界
        
        if (_bounceable) //false
        {
        //...
        }
        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)); //

    container在setPosition后又用setContentOffset方法设置了一次位置。

    看setContentOffset部分代码:

            if (!_bounceable)
            {
                const Vec2 minOffset = this->minContainerOffset();
                const Vec2 maxOffset = this->maxContainerOffset();
                
                offset.x = MAX(minOffset.x, MIN(maxOffset.x, offset.x));
                offset.y = MAX(minOffset.y, MIN(maxOffset.y, offset.y));
            }
    
            _container->setPosition(offset);
    
            if (_delegate != nullptr)
            {
                _delegate->scrollViewDidScroll(this);
            }

    当没有越界时,确实是执行了两次参数一样的setPosition。

    越界时,位置会被修正为边界位置setPosition。

    同时,因为修正前的坐标已经越界,deaccelerateScrolling方法最后会触发unschedule,“甩出”效果终止。

    情况三:开启回弹,执行setContentSize

    因为执行了setContentSize,maxInset和minInset不是0,而是比maxContainerOffset()和minContainerOffset()计算的值扩大了部分可视范围。

    本情况中偏移范围扩大了,是为了当container“甩出”时,允许在一定程度内超出maxContainerOffset()和minContainerOffset()范围。当“甩出”过了一定程度,会触发if三条件中的越界条件,从而执行unschedule,再执行relocateContainer设置回弹效果。

    4. 总结

    onTouchEnded设置了一个在触摸结束后每帧执行的Timer。

    当有“甩出”效果时,对“甩出”时每帧之间container的距离间隔设为比上一帧缩小,实现了每帧移动的距离慢慢减小,直到到达临界点停止。

    当没有“甩出”效果时,deaccelerateScrolling仅执行一次,设置一次container的位置。

  • 相关阅读:
    c++11之智能指针
    SurfaceFlinger与Surface概述
    android GUI 流程记录
    文章收藏
    android performance
    POJ3349
    java中的volatile和synchronized
    [原创]分期还款的名义利率与真实利率
    Java IO 流总结
    telegram
  • 原文地址:https://www.cnblogs.com/deepcho/p/cocos2dx-ccscrollview-deacceleratescrolling.html
Copyright © 2020-2023  润新知