我有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's answer(第一部分),我解决了scrollViewDidScroll
内的动画问题:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
[UIView performWithoutAnimation:^{
[self refreshTiles];
}];
}
但是scrollViewDidScroll
必须(为了我的目的)执行动画的每一帧,就像
[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];
但是,现在它只在动画开始时执行一次。
我该如何解决这个问题?
答案 0 :(得分:18)
您是否尝试过同样的方法,但在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时,将准确剖析scrollView会发生什么,并拦截在scrollview上设置的动画并更改其参数,但由于这将是最hacky,由于Apple的变化和最棘手的方法而破碎,我赢了进去吧。
答案 1 :(得分:5)
执行此操作的一个好方法是使用AnimationEngine库。它是一个非常小的库:六个文件,如果你想要阻尼弹簧行为,还有三个文件。
在幕后,它使用CADisplayLink
每帧运行一次动画块。你会得到一个基于块的干净语法,它易于使用,还有一堆interpolation和easing 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;
}];
答案 2 :(得分:1)
尝试一下:
UIView.animate(withDuration: 0.6, animations: {
self.view.collectionView.contentOffset = newOffset
self.view.layoutIfNeeded()
}, completion: nil)