ios 如何正确动画 UIScrollView contentOffset
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21749950/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to properly animate UIScrollView contentOffset
提问by Lloyd18
I have UIScrollView subclass. Its content is reusable - about 4 or 5 views are used to display hundreds of elements (while scrolling hidden objects reused and jumps to another position when its needed to see them)
我有 UIScrollView 子类。它的内容是可重用的 - 大约 4 或 5 个视图用于显示数百个元素(同时滚动隐藏的对象,并在需要查看它们时跳转到另一个位置)
What i need:ability to automatically scroll my scroll view to any position. For example my scroll view displays 4th, 5th and 6th element and when I tap some button it needs to scroll to 30th element. In other words I need standard behaviourof UIScrollView.
我需要的是:能够自动将我的滚动视图滚动到任何位置。例如,我的滚动视图显示第 4、第 5 和第 6 个元素,当我点击某个按钮时,它需要滚动到第 30 个元素。换句话说,我需要UIScrollView 的标准行为。
This works fine:
这工作正常:
[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];
but I need some customisation. For example, change animation duration, add some code to perform on end of animation.
但我需要一些定制。例如,更改动画持续时间,添加一些代码以在动画结束时执行。
Obvious decision:
明显的决定:
[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self setContentOffset:CGPointMake(index*elementWidth, 0)];
} completion:^(BOOL finished) {
//some code
}];
but I have some actions connected to scroll event, and so now all of them are in animation block and it causes all subview's frames to animate too (thanks to few reusable elements all of them animates not how i want)
但是我有一些与滚动事件相关的动作,所以现在它们都在动画块中,它也会导致所有子视图的帧也有动画(多亏了很少的可重用元素,它们都不是我想要的动画)
The question is:How can I make custom animation (in fact I need custom duration, actions on end and BeginFromCurrentState option) for content offset WITHOUTanimating all the code, connected to scrollViewDidScroll
event?
问题是:如何为内容偏移制作自定义动画(实际上我需要自定义持续时间、动作结束和 BeginFromCurrentState 选项)而不对连接到scrollViewDidScroll
事件的所有代码进行动画处理?
UPD:Thanks to Andrew's answer(first part) I solved issue with animation inside scrollViewDidScroll
:
UPD:感谢安德鲁的回答(第一部分),我解决了里面的动画问题scrollViewDidScroll
:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
[UIView performWithoutAnimation:^{
[self refreshTiles];
}];
}
But scrollViewDidScroll
must (for my purposes) executes every frame of animation like it was in case of
但是scrollViewDidScroll
必须(为了我的目的)执行动画的每一帧,就像在这种情况下一样
[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];
However, now it executes only once at start of animation.
但是,现在它只在动画开始时执行一次。
How can I solve this?
我该如何解决这个问题?
采纳答案by Andrew
Did you try the same approach, but with disabled animation in scrollViewDidScroll
?
您是否尝试过相同的方法,但禁用了动画scrollViewDidScroll
?
On iOS 7, you could try wrapping your code in scrollViewDidScroll
in
在 iOS 7 上,您可以尝试将代码包装scrollViewDidScroll
在
[UIView performWithoutAnimation:^{
//Your code here
}];
on previous iOS versions, you could try:
在以前的 iOS 版本上,您可以尝试:
[CATransaction begin];
[CATransaction setDisableActions:YES];
//Your code here
[CATransaction commit];
Update:
更新:
Unfortunately that's where you hit the tough part of the whole thing. setContentOffset:
calls the delegate just once, it's equivalent to setContentOffset:animated:NO
, which again calls it just once.
不幸的是,这就是您遇到整个事情的艰难部分的地方。setContentOffset:
只调用一次委托,它相当于setContentOffset:animated:NO
, 再次调用它一次。
setContentOffset:animated:YES
calls the delegate as the animation changes the bounds of the scrollview and you want that, but you don't want the provided animation, so the only way around this that I can come up with is to gradually change the contentOffset
of the scrollview, so that the animation system doesn't just jump to the final value, as is the case at the moment.
setContentOffset:animated:YES
在动画更改滚动视图的边界时调用委托,并且您希望这样做,但是您不想要提供的动画,因此我能想到的唯一解决方法是逐渐更改contentOffset
滚动视图的动画系统不只是跳转到最终值,就像目前的情况一样。
To do that you can look at keyframe animations, like so for iOS 7:
为此,您可以查看关键帧动画,例如 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
}];
This will get you two updates and of course you could use some math and a loop to add up a lot more of these with the appropriate timings.
这将为您提供两个更新,当然您可以使用一些数学和循环来在适当的时间添加更多这些更新。
On previous iOS versions, you'll have to drop to CoreAnimation for keyframe animations, but it's basically the same thing with a bit different syntax.
在以前的 iOS 版本中,您必须使用 CoreAnimation 来获得关键帧动画,但它基本上是相同的,只是语法略有不同。
Method 2:
You can try polling the presentationLayer of the scrollview for any changes with a timer that you start at the beginning of the animation, since unfortunately the presentationLayer's properties aren't KVO observable. Or you can use needsDisplayForKey
in a subclass of the layer to get notified when the bounds change, but that'll require some work to set up and it does cause redrawing, which might affect performance.
方法 2:您可以尝试使用在动画开始时启动的计时器轮询滚动视图的presentationLayer 以了解任何更改,因为不幸的是presentationLayer 的属性不是KVO 可观察的。或者,您可以needsDisplayForKey
在图层的子类中使用以在边界更改时获得通知,但这需要进行一些设置,并且确实会导致重绘,这可能会影响性能。
Method 3: Would be to dissect exactly what happens to the scrollView when animated is YES try and intercept the animation that gets set on the scrollview and change its parameters, but since this would be the most hacky, breakable due to Apple's changes and trickiest method, I won't go into it.
方法 3:当动画为 YES 尝试拦截在滚动视图上设置的动画并更改其参数时,将准确剖析 scrollView 发生的情况,但由于 Apple 的更改和最棘手的方法,这将是最笨拙、易破解的,我不会深入。
回答by bcattle
A nice way to do this is with the AnimationEnginelibrary. It's a very small library: six files, with three more if you want damped spring behavior.
一个很好的方法是使用AnimationEngine库。这是一个非常小的库:六个文件,如果您想要阻尼弹簧行为,还有三个文件。
Behind the scenes it uses a CADisplayLink
to run your animation block once every frame. You get a clean block-based syntax that's easy to use, and a bunch of interpolationand easing functionsthat save you time.
在幕后,它使用 aCADisplayLink
来每帧运行一次动画块。你得到一个干净的基于块的语法,易于使用,和一堆插值和缓解功能为您节省时间。
To animate contentOffset
:
动画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;
}];
回答by gerbil
Try this:
尝试这个:
UIView.animate(withDuration: 0.6, animations: {
self.view.collectionView.contentOffset = newOffset
self.view.layoutIfNeeded()
}, completion: nil)