当应用程序从后台恢复时,恢复动画停止的位置

Pal*_*ndo 56 core-animation ios

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];
Run Code Online (Sandbox Code Playgroud)

我已尝试按照技术问答QA1673中的描述"暂停并恢复"图层动画.

当应用程序进入后台时,动画将从图层中删除.为了补偿我听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;    
}
Run Code Online (Sandbox Code Playgroud)

问题是它在开始时再次启动,并且从上一个位置开始出现丑陋的跳跃,如应用程序进入后台时系统所采用的应用程序快照所示,回到动画循环的开始.

当我重新添加动画时,我无法弄清楚如何让它从最后位置开始.坦率地说,我只是不明白QA1673的代码是如何工作的:在resumeLayer它中设置了两次layer.beginTime,这似乎是多余的.但是当我删除第一个设置为零时,它没有恢复暂停的动画.这是通过简单的点击手势识别器测试的,它确实切换了动画 - 这与我从后台恢复的问题没有严格的关系.

在动画被删除之前我应该​​记住什么状态?当我稍后重新添加动画时,如何从该状态恢复动画?

ccl*_*ogg 51

嘿,我在游戏中偶然发现了同样的事情,最终找到了一个与你不同的解决方案,你可能会喜欢:)我想我应该分享我找到的解决方法......

我的情况是使用UIView/UIImageView动画,但它基本上仍然是CAAnimations的核心...我的方法的要点是我在视图上复制/存储当前动画,然后让Apple的暂停/恢复仍然工作,但之前恢复我重新添加我的动画.那么让我来介绍这个简单的例子:

假设我有一个名为movingView的UIView .UIView的中心通过标准的[ UIView animateWithDuration ... ]调用进行动画制作.使用上面提到的QA1673代码,它可以很好地暂停/恢复(当没有退出应用程序时)......但无论如何,我很快就意识到,在退出时,无论是否暂停,动画都被完全删除了......我在这里在你的位置.

所以在这个例子中,这就是我所做的:

  • 在头文件中有一个名为animationViewPosition的变量,类型为*CAAnimation**.
  • 当应用程序退出后台时,我这样做:

    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
    
    Run Code Online (Sandbox Code Playgroud)
    • 注意:那些2 ^调用是一个方法,它是UIApplicationDidEnterBackgroundNotification的处理程序(类似于你)
    • 注意2:如果您不知道(动画的)键是什么,您可以遍历视图的图层的' animationKeys '属性并将其记录下来(大概是动画中期).
  • 现在在我的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
    
    Run Code Online (Sandbox Code Playgroud)

这就是它!它到目前为止对我有用:)

只需为每个动画重复这些步骤,即可轻松扩展它以获得更多动画或视图.它甚至适用于暂停/恢复UIImageView动画,即标准[ imageView startAnimating ].(顺便说一句)的图层动画关键字是"内容".

清单1暂停和恢复动画.

-(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;
}
Run Code Online (Sandbox Code Playgroud)

  • 优秀解决方案 工作就像一个魅力.谢谢. (3认同)

Max*_*eod 18

经过大量的搜索和与iOS开发专家的讨论后,看来QA1673在暂停,后台处理,然后转移到前台时没有帮助.我的实验甚至表明委托从动画中发出的方法,例如animationDidStop变得不可靠.

有时他们会开火,有时他们不会开火.

这会产生很多问题,因为这意味着,您不仅会看到暂停时的不同屏幕,而且还会中断当前运动中的事件序列.

到目前为止我的解决方案如下:

动画开始时,我得到开始时间:

mStartTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];

当用户点击暂停按钮时,我从以下位置删除动画CALayer:

[layer removeAnimationForKey:key];

我得到绝对时间使用CACurrentMediaTime():

CFTimeInterval stopTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];

使用mStartTimestopTime我计算一个偏移时间:

mTimeOffset = stopTime - mStartTime;
Run Code Online (Sandbox Code Playgroud)

我还将对象的模型值设置为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;
}
Run Code Online (Sandbox Code Playgroud)

在恢复时,我重新计算基于暂停动画的剩余内容mTimeOffset.这有点乱,因为我正在使用CAKeyframeAnimation.我根据具体情况找出了哪些关键帧是优秀的mTimeOffset.此外,我考虑到暂停可能发生在帧的中间,例如在f1和之间f2.该时间从该关键帧的时间中扣除.

然后我重新将这个动画添加到图层:

[layer addAnimation:animationGroup forKey:key];

另一件需要记住的事情是,您需要检查标志,animationDidStop并仅removeFromSuperlayer在标志为的情况下从父项中删除动画层YES.这意味着在暂停期间图层仍然可见.

这种方法似乎非常费力.它确实有效!我希望能够使用QA1673简单地做到这一点.但目前用于后台处理,它不起作用,这似乎是唯一的解决方案.


Mat*_*ski 14

令人惊讶的是,这并不是那么简单.我根据cclogg的方法创建了一个类别,这应该使它成为一个单行.

CALayer的+ MBAnimationPersistence

MB_setCurrentAnimationsPersistent在设置所需的动画后,只需在您的图层上调用即可.

[movingView.layer MB_setCurrentAnimationsPersistent];
Run Code Online (Sandbox Code Playgroud)

或者指定应该明确持久化的动画.

movingView.layer.MB_persistentAnimationKeys = @[@"position"];
Run Code Online (Sandbox Code Playgroud)


小智 8

我无法评论,所以我会将其添加为答案.

我使用了cclogg的解决方案,但是当动画的视图从他的超级视图中删除,再次添加,然后转到后台时,我的应用程序崩溃了.

通过设置animation.repeatCount,动画变得无限Float.infinity.
我的解决方案是设置animation.removedOnCompletionfalse.

它的工作原理非常奇怪,因为动画永远不会完成.如果有人有解释,我喜欢听.

另一个提示:如果从超级视图中删除视图.不要忘记通过调用删除观察者NSNotificationCenter.defaultCenter().removeObserver(...).

  • 这应该是公认的答案。这工作完美无缺。 (3认同)
  • 出色的!这一行自动解决了我在背景后恢复动画时遇到的问题。 (3认同)
  • 这是一个答案...将animation.removedOnCompletion设置为false是我需要解决的问题,因为动画似乎已从背景返回时停止了. (2认同)

Art*_*eel 5

我写了一个基于@cclogg和@Matej Bukovinski答案的Swift 4.2版本扩展。您只需要打电话layer.makeAnimationsPersistent()

要点如下:CALayer + AnimationPlayback.swift,CALayer + PersistentAnimations.swift

核心部分:

public extension CALayer {
    static private var persistentHelperKey = "CALayer.LayerPersistentHelper"

    public func makeAnimationsPersistent() {
        var object = objc_getAssociatedObject(self, &CALayer.persistentHelperKey)
        if object == nil {
            object = LayerPersistentHelper(with: self)
            let nonatomic = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
            objc_setAssociatedObject(self, &CALayer.persistentHelperKey, object, nonatomic)
        }
    }
}

public class LayerPersistentHelper {
    private var persistentAnimations: [String: CAAnimation] = [:]
    private var persistentSpeed: Float = 0.0
    private weak var layer: CALayer?

    public init(with layer: CALayer) {
        self.layer = layer
        addNotificationObservers()
    }

    deinit {
        removeNotificationObservers()
    }
}

private extension LayerPersistentHelper {
    func addNotificationObservers() {
        let center = NotificationCenter.default
        let enterForeground = UIApplication.willEnterForegroundNotification
        let enterBackground = UIApplication.didEnterBackgroundNotification
        center.addObserver(self, selector: #selector(didBecomeActive), name: enterForeground, object: nil)
        center.addObserver(self, selector: #selector(willResignActive), name: enterBackground, object: nil)
    }

    func removeNotificationObservers() {
        NotificationCenter.default.removeObserver(self)
    }

    func persistAnimations(with keys: [String]?) {
        guard let layer = self.layer else { return }
        keys?.forEach { (key) in
            if let animation = layer.animation(forKey: key) {
                persistentAnimations[key] = animation
            }
        }
    }

    func restoreAnimations(with keys: [String]?) {
        guard let layer = self.layer else { return }
        keys?.forEach { (key) in
            if let animation = persistentAnimations[key] {
                layer.add(animation, forKey: key)
            }
        }
    }
}

@objc extension LayerPersistentHelper {
    func didBecomeActive() {
        guard let layer = self.layer else { return }
        restoreAnimations(with: Array(persistentAnimations.keys))
        persistentAnimations.removeAll()
        if persistentSpeed == 1.0 { // if layer was playing before background, resume it
            layer.resumeAnimations()
        }
    }

    func willResignActive() {
        guard let layer = self.layer else { return }
        persistentSpeed = layer.speed
        layer.speed = 1.0 // in case layer was paused from outside, set speed to 1.0 to get all animations
        persistAnimations(with: layer.animationKeys())
        layer.speed = persistentSpeed // restore original speed
        layer.pauseAnimations()
    }
}
Run Code Online (Sandbox Code Playgroud)