【问题标题】:How to properly animate UIScrollView contentOffset如何正确地为 UIScrollView contentOffset 设置动画
【发布时间】:2014-03-12 01:52:33
【问题描述】:

我有 UIScrollView 子类。它的内容是可重复使用的 - 大约 4 或 5 个视图用于显示数百个元素(同时滚动隐藏的对象重复使用并在需要查看它们时跳转到另一个位置)

我需要什么:能够自动将我的滚动视图滚动到任何位置。例如,我的滚动视图显示第 4、第 5 和第 6 个元素,当我点击某个按钮时,它需要滚动到第 30 个元素。换句话说,我需要 UIScrollView 的标准行为

这很好用:

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];

但我需要一些定制。例如,更改动画时长,添加一些代码以在动画结束时执行。

显而易见的决定:

[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
    [self setContentOffset:CGPointMake(index*elementWidth, 0)];
} completion:^(BOOL finished) {
    //some code
}];

但我有一些与滚动事件相关联的动作,所以现在它们都在动画块中,它也会导致所有子视图的框架也进行动画处理(由于很少有可重用的元素,所有的动画都不是我想要的)

问题是:如何为内容偏移制作自定义动画(实际上我需要自定义持续时间、结束动作和 BeginFromCurrentState 选项)没有动画所有代码,连接到scrollViewDidScroll 事件?

统一更新: 感谢Andrew's answer(第一部分)我解决了scrollViewDidScroll中的动画问题:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [UIView performWithoutAnimation:^{
        [self refreshTiles];
    }];
}

scrollViewDidScroll 必须(出于我的目的)执行动画的每一帧,就像在

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];

但是,现在它只在动画开始时执行一次。

我该如何解决这个问题?

【问题讨论】:

    标签: ios cocoa-touch uiview uiscrollview uiviewanimation


    【解决方案1】:

    您是否尝试过相同的方法,但在 scrollViewDidScroll 中禁用了动画?

    在 iOS 7 上,您可以尝试将代码包装在 scrollViewDidScroll

    [UIView performWithoutAnimation:^{
           //Your code here
        }];
    

    在之前的 iOS 版本上,您可以尝试:

      [CATransaction begin];
        [CATransaction setDisableActions:YES];
         //Your code here
        [CATransaction commit];
    

    更新:

    不幸的是,你遇到了整件事的难点。 setContentOffset: 只调用了一次委托,相当于setContentOffset:animated:NO,同样只调用了一次。

    setContentOffset:animated:YES 在动画更改滚动视图的边界时调用委托,并且您想要,但您不想要提供的动画,所以我能想出的唯一方法是逐渐改变contentOffset 的滚动视图,这样动画系统就不会像现在那样直接跳转到最终值。

    为此,您可以查看关键帧动画,例如 iOS 7:

    [UIView animateKeyframesWithDuration:duration delay:delay options:options animations:^{
        [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
           [self setContentOffset:CGPointMake(floorf(index/2) * elementWidth, 0)];
        }];
        [UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
            [self setContentOffset:CGPointMake(index*elementWidth, 0)];
        }];
    } completion:^(BOOL finished) {
        //Completion Block
    }];
    

    这将为您提供两次更新,当然您可以使用一些数学运算和循环来在适当的时间添加更多更新。

    在以前的 iOS 版本中,您必须使用 CoreAnimation 来制作关键帧动画,但它基本上是相同的,只是语法略有不同。

    方法二: 您可以尝试使用在动画开始时启动的计时器轮询滚动视图的presentationLayer 以获取任何更改,因为不幸的是presentationLayer 的属性不是KVO 可观察的。或者,您可以在图层的子类中使用needsDisplayForKey,以便在边界更改时收到通知,但这需要进行一些设置,并且会导致重绘,这可能会影响性能。

    方法三: 将准确剖析当动画为YES时scrollView会发生什么尝试拦截在scrollview上设置的动画并更改其参数,但由于Apple的更改和最棘手的方法,这将是最骇人听闻的,易碎的,我赢了不要进入它。

    【讨论】:

    • 谢谢!这太明显了)它有帮助,现在“scrollViewDidScroll”在没有动画的情况下执行。但与“contentOffset”的系统动画不同,它执行多次(我相信每一帧),现在它只执行一次。你知道如何解决这个问题吗?
    • 在我的回答中添加了对您的评论的回答,因为评论会很长。
    • 精彩的答案!非常感谢!
    • performWithoutAnimation 似乎对我不起作用。我有一个渐变层,我正在更新 alpha,但是渐变层似乎总是落后,不管我把整个代码放在 performWithoutAnimation 里面:/
    【解决方案2】:

    使用AnimationEngine 库是一个很好的方法。这是一个非常小的库:六个文件,如果您想要阻尼弹簧行为,还有三个文件。

    在幕后,它使用CADisplayLink 每帧运行一次动画块。您将获得易于使用的干净的基于块的语法,以及一组可以节省您时间的interpolationeasing functions

    动画contentOffset

    startOffset = scrollView.contentOffset;
    endOffset = ..
    
    // Constant speed looks good...
    const CGFloat kTimelineAnimationSpeed = 300;
    CGFloat timelineAnimationDuration = fabs(deltaToDesiredX) / kTimelineAnimationSpeed;
    
    [INTUAnimationEngine animateWithDuration:timelineAnimationDuration
                                       delay:0
                                      easing:INTULinear
                                  animations:^(CGFloat progress) {
                                        self.videoTimelineView.contentOffset = 
                                             INTUInterpolateCGPoint(startOffset, endOffset, progress);
                                  }
                                 completion:^(BOOL finished) {
                                       autoscrollEnabled = YES;
                                 }];
    

    【讨论】:

      【解决方案3】:

      试试这个:

      UIView.animate(withDuration: 0.6, animations: {
              self.view.collectionView.contentOffset = newOffset
              self.view.layoutIfNeeded()
          }, completion: nil)
      

      【讨论】:

        猜你喜欢
        • 2014-11-23
        • 1970-01-01
        • 1970-01-01
        • 2013-02-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多