如何正确设置UIScrollView contentOffset的动画

我有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事件的所有代码?

UPD:感谢Andrew的回答 (第一部分)我解决了scrollViewDidScroll动画问题:

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

但是scrollViewDidScroll必须(为了我的目的)执行每个动画帧,就像它的情况一样

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

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

我怎么解决这个问题?

您是否尝试过相同的方法,但在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调用委托,因为动画更改了scrollview的边界而你想要它,但你不想要提供的动画,所以我能想出的唯一方法就是逐步改变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,但它的语法略有不同。

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

方法3:准确地剖析当动画为YES时滚动视图会发生什么,并拦截在滚动视图上设置的动画并更改其参数,但由于Apple的变化和最棘手的方法,这将是最hacky,易碎的,我不会进入它。

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

在幕后,它使用CADisplayLink每帧运行一次动画块。 您可以轻松使用基于块的清晰语法,以及一系列插值 和缓动function ,可以节省您的时间。

要为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; }];