在 Swift 中有效地取消关键帧动画

Posted

技术标签:

【中文标题】在 Swift 中有效地取消关键帧动画【英文标题】:Efficiently canceling key frame animation in Swift 【发布时间】:2016-04-03 18:58:04 【问题描述】:

我有一个关键帧动画来显示 Swift 中的发牌效果。如果您按下按钮,我会使用第二个动画(使用 .BeginFromCurrentState ...)来取消交易。这对简单的单视图动画有效。然而,按下按钮后会有 2-10 秒的延迟,大概是在运行每个取消动画时。有没有更简单更顺畅的方法来实现我想要的(立即取消交易)。

这是设置交易动画的代码 sn-p:

    let durationSlice = 1.0/Double(numCards*numPlayers+1)
    var durationSliceStart : Double = 0
    UIView.animateKeyframesWithDuration(FiveKings.ANIMATION_250MS*(Double(numCards)*Double(numPlayers)+1), delay: 0.0 ,
        options: [.CalculationModeCubic],
        animations: 
            for iCard in 0..<numCards 
                durationSliceStart = Double(iCard*numPlayers) * durationSlice
                //translate the cards off the pile and to each mini Hand (and they stay visible)
                for iPlayer in 0..<numPlayers 
                    //Animate the card into view at the start of this set
                    UIView.addKeyframeWithRelativeStartTime(durationSliceStart+Double(iPlayer)*durationSlice, relativeDuration: 0.0, animations: 
                            pileCardViews[iCard*numPlayers + iPlayer].alpha = 1.0
                        )


                    //add a random amount of translation and rotation to simulate messy cards
                    let messyX = CGFloat((drand48()-0.5) * MESSY_CARD_XY_OFFSET) * self.mDrawPile.bounds.width
                    let messyY = CGFloat((drand48()-0.5) * MESSY_CARD_XY_OFFSET) * self.mDrawPile.bounds.height
                    let messyRotation = CGFloat((drand48()-0.5) * MESSY_CARD_ANGLE) * 2.0 * 3.14

                    //convert the destination miniHand into the coordinates of mDrawPile
                    let miniHandLayout = self.mGame.players.getPlayer(iPlayer).miniHandLayout!
                    miniHandDestinationPoint = miniHandLayout.cardView.convertPoint(CGPoint(x: 0,y: 0), toView: self.mDrawPile)
                    UIView.addKeyframeWithRelativeStartTime(durationSliceStart+Double(iPlayer)*durationSlice, relativeDuration: durationSlice, animations: 
                        pileCardViews[iCard*numPlayers + iPlayer].transform = CGAffineTransformConcat(
                            CGAffineTransformMakeRotation(3.14+messyRotation),
                            CGAffineTransformMakeTranslation(miniHandDestinationPoint.x + messyX, miniHandDestinationPoint.y + messyY))
                        if iCard == 0 miniHandLayout.cardView.alpha = 1.0
                    )

                

            //end iCard
            //animate a card to the DiscardPile
            let discardPileDestination = self.mDiscardPile.convertPoint(CGPoint(x: 0, y: 0), toView: self.mDrawPile)
            UIView.addKeyframeWithRelativeStartTime(durationSliceStart+Double(numPlayers)*durationSlice, relativeDuration: durationSlice, animations:
                
                    pileCardViews[numCards*numPlayers].transform = CGAffineTransformMakeTranslation(discardPileDestination.x, discardPileDestination.y)
                    pileCardViews[numCards*numPlayers].alpha = 1.0
            )
        ,
        //completion block removes the added cards
        completion: (_ : Bool) in
            //remove the added cards
            for pcv in pileCardViews pcv.removeFromSuperview()
            self.afterDealing()
        
    )//end UIView.animateKeyFramesWithDuration

这是您按下“跳过交易”按钮时运行的代码:

    if !mDealingPileCards.isEmpty 
        for pcv in self.mDealingPileCards pcv.stopAnimation() //also seems to call completion handler
        self.mDealingPileCards.removeAll(keepCapacity: true)
        self.setShowHint(stringKey: "toDisableDealing", setShowHint: FiveKings.HandleHint.SET_AND_SHOW_HINT, hintLevel: GameDifficulty.MEDIUM)
    

stopAnimation 的实现是 UIView 的扩展:

func stopAnimation() 
    UIView.animateWithDuration(0.0, delay: 0.0, options: [.BeginFromCurrentState],
        animations: 
            self.alpha = 1.0
            self.transform = CGAffineTransformIdentity
        , completion: nil)

编辑:我尝试过使用...layer.removeAllAnimations,但也会出现同样的延迟问题,可能是在完成处理程序运行时?

【问题讨论】:

【参考方案1】:

要取消动画,只需对正在动画的每个图层(或每个视图的图层)说removeAllAnimations。您还需要决定现在希望该视图出现在哪里,但这是一个不同的问题(换句话说,您必须考虑取消的实际组成部分)。

【讨论】:

是的,我已经试过了。发生同样的问题 - removeAllAnimations 完成时有很长的暂停。 "removeAllAnimations 完成时有很长的停顿" 真的吗?你确定你没有线程问题吗? 在我上面发布的代码 sn-p 中,如果我将 pcv.stopAnimation() 替换为 pcv.layer.removeAllAnimations() 我会在 UI 中得到相同的“停滞”。你能指出我可以做些什么来追踪“线程问题”吗?没有其他事情发生。 感谢@matt 的怀疑,因为它为我指明了正确的方向。看我的回答。【参考方案2】:

这个问题的直接答案(它本身提出了另一个问题)是stopAnimation(使用.BeginFromCurrentState 调用另一个动画)或..layer.removeAllAnimations 实际上会取消动画。但是,在我的情况下,完成块中正在进行额外的 UI 工作,这显然要到预定时间才会运行。换句话说,即使你单独取消/移除关键帧动画,完成块也会延迟。

【讨论】:

以上是关于在 Swift 中有效地取消关键帧动画的主要内容,如果未能解决你的问题,请参考以下文章

什么是关键帧动画

到底啥是动画关键帧啊

动画的五个关键帧都有哪些?

关键帧中的非动画属性在 iOS 上被忽略

unity路径途径有几个关键帧

什么是关键帧动画