当应用程序从后台恢复时,在animation停止的位置恢复
在我看来,我有一个无止境的循环的CABasicAnimation
重复的图像瓷砖:
a = [CABasicAnimation animationWithKeyPath:@"position"]; a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; a.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)]; a.toValue = [NSValue valueWithCGPoint:CGPointMake(image.size.width, 0)]; a.repeatCount = HUGE_VALF; a.duration = 15.0; [a retain];
我试图按照技术问答QA1673中的描述“暂停和恢复”图层animation。
当应用程序进入后台时,animation将从图层中移除。 为了弥补,我听取了UIApplicationDidEnterBackgroundNotification
并调用stopAnimation
并响应UIApplicationWillEnterForegroundNotification
调用startAnimation
。
- (void)startAnimation { if ([[self.layer animationKeys] count] == 0) [self.layer addAnimation:a forKey:@"position"]; CFTimeInterval pausedTime = [self.layer timeOffset]; self.layer.speed = 1.0; self.layer.timeOffset = 0.0; self.layer.beginTime = 0.0; CFTimeInterval timeSincePause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; self.layer.beginTime = timeSincePause; } - (void)stopAnimation { CFTimeInterval pausedTime = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil]; self.layer.speed = 0.0; self.layer.timeOffset = pausedTime; }
问题是,它从头开始,从最后一个位置开始有一个丑陋的跳转,就像在应用程序进入后台时应用程序快照中看到的那样,回到animation循环的开始处。
当我重新添加animation时,我无法弄清楚如何使它从最后位置开始。 坦率地说,我只是不明白QA1673的代码是如何工作的:在resumeLayer
它将resumeLayer
设置为两次,这似乎是多余的。 但是,当我删除了第一个设置为零时,它不会恢复暂停的animation。 这是用简单的水龙头手势识别器testing的,它确实切换了animation – 这与我从背景中恢复的问题没有严格的关系。
在animation被删除之前我应该记住什么状态,以后如何重新添加animation,如何从该状态恢复animation?
经过与iOS开发专家的大量search和交stream之后,看起来QA1673在暂停,背景,然后移动到前景方面没有任何帮助。 我的实验甚至表明,animation引发的委托方法(如animationDidStop
变得不可靠。
有时他们会开火,有时他们不会。
这就产生了很多问题,因为这意味着你不仅在看着你停下来的不同的屏幕,而且当前运动的事件顺序也会中断。
我到目前为止的解决scheme如下:
当animation开始时,我得到开始时间:
mStartTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
当用户点击暂停button时,我从CALayer
删除animation:
[layer removeAnimationForKey:key];
我使用CACurrentMediaTime()
获取绝对时间:
CFTimeInterval stopTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
使用mStartTime
和stopTime
计算偏移时间:
mTimeOffset = stopTime - mStartTime;
我也将对象的模型值设置为presentationLayer
的模型值。 所以,我的stop
方法如下所示:
//-------------------------------------------------------------------------------------------------- - (void)stop { const CALayer *presentationLayer = layer.presentationLayer; layer.bounds = presentationLayer.bounds; layer.opacity = presentationLayer.opacity; layer.contentsRect = presentationLayer.contentsRect; layer.position = presentationLayer.position; [layer removeAnimationForKey:key]; CFTimeInterval stopTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; mTimeOffset = stopTime - mStartTime; }
在简历上,我根据mTimeOffset
重新计算暂停animation的mTimeOffset
。 这有点乱,因为我正在使用CAKeyframeAnimation
。 我根据mTimeOffset
找出哪些关键帧是优秀的。 另外,我考虑到暂停可能发生在中间帧,例如在f1
和f2
之间的中途。 那个时间是从那个关键帧的时间中扣除的。
然后我将这个animation重新添加到图层:
[layer addAnimation:animationGroup forKey:key];
另外要记住的是,你将需要检查animationDidStop
的标志,并且如果标志是YES
,则只使用removeFromSuperlayer
从父级删除animation层。 这意味着图层在暂停期间仍然可见。
这个方法似乎很费力。 它虽然工作! 我希望能够使用QA1673简单地做到这一点 。 但是,目前的背景,这是行不通的,这似乎是唯一的解决办法。
嘿,我在我的游戏中偶然发现了同样的事情,最终find了一个与你不同的解决scheme,你可能会喜欢:)我想我应该分享我find的解决方法…
我的情况是使用UIView / UIImageViewanimation,但它的核心基本上仍然是CAAnimations …我的方法的要点是,我复制/存储当前的animation视图,然后让苹果暂停/继续工作,但之前恢复我重新添加我的animation。 所以让我来举个简单的例子:
比方说,我有一个名为movingView的UIView。 UIView的中心通过标准的[ UIView animateWithDuration … ]来调用。 使用提到的QA1673代码,它可以很好地暂停/恢复(当不退出应用程序时)…但是无论如何,我很快意识到,在退出时,无论是否暂停,animation都被完全删除了……在这里,我是在你的位置。
所以在这个例子中,我做了以下操作:
- 在头文件中有一个名为类似于* CAAnimation **的animationViewPosition的variables。
-
当应用程序退出到后台,我这样做:
animationViewPosition = [[movingView.layer animationForKey:@"position"] copy]; // I know position is the key in this case... [self pauseLayer:movingView.layer]; // this is the Apple method from QA1673
- 注意:那些2 ^调用是在UIApplicationDidEnterBackgroundNotification (类似于你)的处理程序的方法中,
- 注2:如果你不知道关键是什么(你的animation),你可以遍历视图的图层的“ animation键 ”属性,并logging下来(大概中间animation)。
-
现在在我的UIApplicationWillEnterForegroundNotification处理程序中:
if (animationViewPosition != nil) { [movingView.layer addAnimation:animationViewPosition forKey:@"position"]; // re-add the core animation to the view [animationViewPosition release]; // since we 'copied' earlier animationViewPosition = nil; } [self resumeLayer:movingView.layer]; // Apple's method, which will resume the animation at the position it was at when the app exited
而这就是它! 它为我工作到目前为止:)
只需对每个animation重复这些步骤,就可以轻松地将其扩展为更多的animation或视图。 它甚至适用于暂停/恢复UIImageViewanimation,即标准的[ imageView startAnimating ]。 那个层的animation键(顺便说一下)是“内容”。
清单1暂停和恢复animation。
-(void)pauseLayer:(CALayer*)layer { CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.speed = 0.0; layer.timeOffset = pausedTime; } -(void)resumeLayer:(CALayer*)layer { CFTimeInterval pausedTime = [layer timeOffset]; layer.speed = 1.0; layer.timeOffset = 0.0; layer.beginTime = 0.0; CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; layer.beginTime = timeSincePause; }
令人惊讶的是,这不是更直接。 我根据cclogg的方法创build了一个类别,这个类别应该是单行的。
CALayer的+ MBAnimationPersistence
设置好所需的animation后,只需在图层上调用MB_setCurrentAnimationsPersistent
。
[movingView.layer MB_setCurrentAnimationsPersistent];
或者指定应该明确保持的animation。
movingView.layer.MB_persistentAnimationKeys = @[@"position"];
我不能发表评论,所以我会将其添加为答案。
我使用cclogg的解决scheme,但是当animation的视图从他的超级视图中删除时,我的应用程序崩溃,再次添加,然后去背景。
将animation.repeatCount设置为Float.infinity
,将animation.repeatCount
设为无限。
我的解决scheme是将animation.removedOnCompletion
设置为false
。
这是非常奇怪的,因为animation永远不会完成。 如果有人有解释,我喜欢听。
另一个提示:如果您从超级视图中删除视图。 不要忘记通过调用NSNotificationCenter.defaultCenter().removeObserver(...)
来移除观察者。
我能够通过保存当前animation的副本并将其添加到简历中来恢复animation(但不是animation位置)。 我在加载时调用startAnimation,在进入前景时暂停,在进入后台时暂停。
- (void) startAnimation { // On first call, setup our ivar if (!self.myAnimation) { self.myAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; /* Finish setting up myAnimation */ } // Add the animation to the layer if it hasn't been or got removed if (![self.layer animationForKey:@"myAnimation"]) { [self.layer addAnimation:self.spinAnimation forKey:@"myAnimation"]; } } - (void) pauseAnimation { // Save the current state of the animation // when we call startAnimation again, this saved animation will be added/restored self.myAnimation = [[self.layer animationForKey:@"myAnimation"] copy]; }
以防万一任何人需要Swift 3解决scheme来解决这个问题:
你所要做的就是从这个类中inheritance你的animation视图。 它始终坚持并恢复它的图层上的所有animation。
class ViewWithPersistentAnimations : UIView { private var persistentAnimations: [String: CAAnimation] = [:] private var persistentSpeed: Float = 0.0 override init(frame: CGRect) { super.init(frame: frame) self.commonInit() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.commonInit() } func commonInit() { NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil) } deinit { NotificationCenter.default.removeObserver(self) } func didBecomeActive() { self.restoreAnimations(withKeys: Array(self.persistentAnimations.keys)) self.persistentAnimations.removeAll() if self.persistentSpeed == 1.0 { //if layer was plaiyng before backgorund, resume it self.layer.resume() } } func willResignActive() { self.persistentSpeed = self.layer.speed self.layer.speed = 1.0 //in case layer was paused from outside, set speed to 1.0 to get all animations self.persistAnimations(withKeys: self.layer.animationKeys()) self.layer.speed = self.persistentSpeed //restore original speed self.layer.pause() } func persistAnimations(withKeys: [String]?) { withKeys?.forEach({ (key) in if let animation = self.layer.animation(forKey: key) { self.persistentAnimations[key] = animation } }) } func restoreAnimations(withKeys: [String]?) { withKeys?.forEach { key in if let persistentAnimation = self.persistentAnimations[key] { self.layer.add(persistentAnimation, forKey: key) } } } } extension CALayer { func pause() { if self.isPaused() == false { let pausedTime: CFTimeInterval = self.convertTime(CACurrentMediaTime(), from: nil) self.speed = 0.0 self.timeOffset = pausedTime } } func isPaused() -> Bool { return self.speed == 0.0 } func resume() { let pausedTime: CFTimeInterval = self.timeOffset self.speed = 1.0 self.timeOffset = 0.0 self.beginTime = 0.0 let timeSincePause: CFTimeInterval = self.convertTime(CACurrentMediaTime(), from: nil) - pausedTime self.beginTime = timeSincePause } }
在要点: https : //gist.github.com/grzegorzkrukowski/a5ed8b38bec548f9620bb95665c06128
我使用cclogg的解决scheme,效果很好。 我也想分享一些其他的信息,可能会帮助别人,因为它让我感到沮丧了一段时间。
在我的应用程序,我有一些animation,一些永远循环,一些只运行一次,随机产生。 cclogg的解决scheme为我工作,但是当我添加了一些代码
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
为了在只有一次animation完成时做某些事情,只要这些特定的一次性animation在暂停时运行,那么当我恢复我的应用(使用cclogg的解决scheme)时,这个代码就会触发。 所以我添加了一个标志(我的自定义UIImageView类的成员variables),并将其设置为YES,以恢复所有图层animation( resumeLayer
中的resumeLayer,类似于Apple解决schemeQA1673),以防止发生这种情况。 我为每个正在恢复的UIImageView执行此操作。 然后,在animationDidStop
方法中,只有当该标志为NO时才运行一次性animation处理代码。 如果是,则忽略处理代码。 将标志切换回NO。 这样,当animation真正完成时,您的处理代码将运行。 所以像这样:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag if (!resumeFlag) { // do something now that the animation is finished for reals } resumeFlag = NO; }
希望能帮助别人。