Swift中的动画

作者: Girl_iOS | 来源:发表于2015-11-01 23:30 被阅读1593次

    本文翻译自O(∩_∩)O
    Prototyping iOS Animations in Swift中介绍了UIKit中基于block方法的一些小的动画效果并且教你如何利用随机数的变化来创建一个复杂的场景.
    这些知识能够创建一些有意思的动画,但仅是苹果所提供创建动画效果工具中的冰山一角.
    这篇文章将教你一些进阶的动画制作,一旦你掌握这些知识,将会打开动画的潘多拉墨盒:)

    • 容器的页面转换
      苹果提供了许多默认的动画效果,使你能很轻松地完成页面之间的动画过渡.
      为了很好地利用这些方法,你需要一个父容器.这个父容器一般为一个不可见的大尺寸的UIView.做这些动画效果之前需要做一些准备工作.
      我们要创建两种不同颜色的UIView之间的转换动画需要另外创建一个UIView.
      首先,需要在viewDidLoad()方法中创建三个views...
    let container = UIView()
    let redSquare = UIView()
    let blueSquare = UIView()
    override func viewDidLoad() {
        super.viewDidLoad()
        // set container frame and add to the screen
        self.container.frame = CGRect(x: 60, y: 60, width: 200, height: 200)
        self.view.addSubview(container)
        // set red square frame up
        // we want the blue square to have the same position as redSquare 
        // so lets just reuse blueSquare.frame
        self.redSquare.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        self.blueSquare.frame = redSquare.frame
        // set background colors
        self.redSquare.backgroundColor = UIColor.redColor()
        self.blueSquare.backgroundColor = UIColor.blueColor()
        // for now just add the redSquare
        // we'll add blueSquare as part of the transition animation 
        self.container.addSubview(self.redSquare)   
    }
    

    运行程序,你将看到一个红色色块:

    static-red-square.png
    现在我们在storyboard中添加一个按钮来控制动画方法.
    UIView.transitionWithView有许多参数:
    • view: 添加转场动画的页面
    • duration: 动画所持续的秒数
    • options: 可选参数(例如动画的类型)
    • animations: 定义要变换部分的block
    • completion: 动画结束时的block
      动画block中要完成移走一个view的同时在容器中新增一个view.
    @IBAction func animateButtonTapped(sender: AnyObject) {
      
        // create a 'tuple' (a pair or more of objects assigned to a single variable)
        let views = (frontView: self.redSquare, backView: self.blueSquare)
    
        // set a transition style
        let transitionOptions = UIViewAnimationOptions.TransitionCurlUp
    
        UIView.transitionWithView(self.container, duration: 1.0, options: transitionOptions, animations: {
            // remove the front object...
            views.frontView.removeFromSuperview()
       
            // ... and add the other object
            self.container.addSubview(views.backView)
       
        }, completion: { finished in
            // any code entered here will be applied
            // .once the animation has completed
        })
    }
    
    container-1.gif
    红色方块转换到蓝色方块时,一切正常,但之后就一直是蓝色方块.
    这是因为我们的@IBAction方法设置的是红色可见方块.而这只有在首次点击动画按钮时才为true.
    要解决这个问题需要判断出哪个色块是可见的,从而实现从从红色转变为蓝色,或者蓝色转变为红色.
    有许多方法可以做到,但我们现在使用Swift的一个特性'tuple'来实现.
    // create a 'tuple' (a pair or more of objects assigned to a single variable)
    var views : (frontView: UIView, backView: UIView)
    
    // if redSquare has a superView (e.g it's in the container)
    // set redSquare as front, and blueSquare as back
    // otherwise flip the order
    if(self.redSquare.superview){
        views = (frontView: self.redSquare, backView: self.blueSquare)
    }
    else {
        views = (frontView: self.blueSquare, backView: self.redSquare)
    }
    

    现在我们能够在红蓝色块中自由转换了!

    container-2.gif
    从一个View转变到另一个View是常见的动画效果,苹果为我们提供了一种简洁直接的用法来自动实现removeSuperview()和addSubView()的效果:
    @IBAction func animateButtonTapped(sender: AnyObject) {
        
        // create a 'tuple' (a pair or more of objects assigned to a single variable)
        var views : (frontView: UIView, backView: UIView)
    
        if(self.redSquare.superview){
            views = (frontView: self.redSquare, backView: self.blueSquare)
        }
        else {
            views = (frontView: self.blueSquare, backView: self.redSquare)
        }
        
        // set a transition style
        let transitionOptions = UIViewAnimationOptions.TransitionCurlUp
    
        // with no animation block, and a completion block set to 'nil' this makes a single line of code  
        UIView.transitionFromView(views.frontView, toView: views.backView, duration: 1.0, options: transitionOptions, completion: nil)
        
    }
    

    我们来尝试下其他的转换效果:

    let transitionOptions = UIViewAnimationOptions.TransitionCurlDown
    
    container-3.gif
    let transitionOptions = UIViewAnimationOptions.TransitionFlipFromLeft
    
    container-4.gif
    • Keyframe block animations
      另一个iOS7新增加的特性,用于替代创建动画时插入开始和结束值,它是将一个整体分为你想要的多个部分.
      例如我们想要将一张图片旋转360度.
      用我们熟知的动画方法通过改变transform属性来实现:
    // create and add blue-fish.png image to screen
    let fish = UIImageView()
    fish.image = UIImage(named: "blue-fish.png")
    fish.frame = CGRect(x: 50, y: 50, width: 50, height: 50)
    self.view.addSubview(fish)
    // angles in iOS are measured as radians PI is 180 degrees so PI × 2 is 360 degrees
    let fullRotation = CGFloat(M_PI * 2)
    UIView.animateWithDuration(1.0, animations: {
        // animating `transform` allows us to change 2D geometry of the object 
        // like `scale`, `rotation` or `translate`
        self.fish.transform = CGAffineTransformMakeRotation(fullRotation)
    })
    

    因为初始值和结束值相同,iOS不能在中间添加新值.
    如果要实现如此,我们可以利用animateKeyFramesWithDuration来将翻转分割成许多小组合,然后将他们组成一个完整的动画效果:
    我们将整个翻转分割成3部分,每一部分翻转1/3:

    let duration = 2.0
    let delay = 0.0
    let options = UIViewKeyframeAnimationOptions.CalculationModeLinear
    UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: {
        // each keyframe needs to be added here
        // within each keyframe the relativeStartTime and relativeDuration need to be values between 0.0 and 1.0
        UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/3, animations: {
            // start at 0.00s (5s × 0)
            // duration 1.67s (5s × 1/3)
            // end at   1.67s (0.00s + 1.67s)
            self.fish.transform = CGAffineTransformMakeRotation(1/3 * fullRotation)
        })
        UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 1/3, animations: {
            self.fish.transform = CGAffineTransformMakeRotation(2/3 * fullRotation)
        })
        UIView.addKeyframeWithRelativeStartTime(2/3, relativeDuration: 1/3, animations: {
            self.fish.transform = CGAffineTransformMakeRotation(3/3 * fullRotation)
        })
        }, completion: {finished in
            // any code entered here will be applied
            // once the animation has completed
        })
    }
    

    现在我们可以很轻松的实现翻转360的动画效果.

    rotate-1.gif

    如果你手动输入开始和持续时间很容易出错,但如果要实现keyframes动画之间的连贯顺滑,我们可以通过CalculationModePaced参数来忽略你输入的开始和持续时间,自动计算出动画相对应的时间:

    let duration = 2.0
    let delay = 0.0
    let options = UIViewKeyframeAnimationOptions.CalculationModePaced
    
    UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: {
        
        // note that we've set relativeStartTime and relativeDuration to zero. 
        // Because we're using `CalculationModePaced` these values are ignored 
        // and iOS figures out values that are needed to create a smooth constant transition
        UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0, animations: {
            self.fish.transform = CGAffineTransformMakeRotation(1/3 * fullRotation)
        })
        
        UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0, animations: {
            self.fish.transform = CGAffineTransformMakeRotation(2/3 * fullRotation)
        })
        
        UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0, animations: {
            self.fish.transform = CGAffineTransformMakeRotation(3/3 * fullRotation)
        })
        
    }, completion: nil)
    
    • 使一个物体按照贝塞尔曲线运动
      一个有意思的动画效果是物体按照曲线运动.
      我们很容易实现从A到B移动的效果,而实现多个坐标点ABCDE的移动我们需要再次利用keyframebased动画.
      我们可以利用keyframe block来实现移动效果,但如果想要顺滑我们必须定义多个keyframe,但却使代码变得复杂难懂.
      好消息是我们可以用贝塞尔曲线自动完成keyframe所做的工作.
      这需要我们使用更强大的iOS动画特性,了解它后并不会很难.
    // first set up an object to animate
    // we'll use a familiar red square
    let square = UIView()
    square.frame = CGRect(x: 55, y: 300, width: 20, height: 20)
    square.backgroundColor = UIColor.redColor()
    // add the square to the screen
    self.view.addSubview(square)
    // now create a bezier path that defines our curve
    // the animation function needs the curve defined as a CGPath
    // but these are more difficult to work with, so instead
    // we'll create a UIBezierPath, and then create a 
    // CGPath from the bezier when we need it
    let path = UIBezierPath()
    path.moveToPoint(CGPoint(x: 16,y: 239))
    path.addCurveToPoint(CGPoint(x: 301, y: 239), controlPoint1: CGPoint(x: 136, y: 373), controlPoint2: CGPoint(x: 178, y: 110))
    // create a new CAKeyframeAnimation that animates the objects position 
    let anim = CAKeyframeAnimation(keyPath: "position")
    // set the animations path to our bezier curve
    anim.path = path.CGPath
    // set some more parameters for the animation
    // this rotation mode means that our object will rotate so that it's parallel to whatever point it is currently on the curve 
    anim.rotationMode = kCAAnimationRotateAuto
    anim.repeatCount = Float.infinity
    anim.duration = 5.0
    // we add the animation to the squares 'layer' property
    square.layer.addAnimation(anim, forKey: "animate position along path")
    
    path-1.gif

    现在有了一个单一的动画,我们需要把它成倍增加来创建一个复杂的场景:

    // loop from 0 to 5
    for i in 0...5 {
        
        // create a square 
        let square = UIView()
        square.frame = CGRect(x: 55, y: 300, width: 20, height: 20)
        square.backgroundColor = UIColor.redColor()
        self.view.addSubview(square)
        
        // randomly create a value between 0.0 and 150.0
        let randomYOffset = CGFloat( arc4random_uniform(150))
        
        // for every y-value on the bezier curve
        // add our random y offset so that each individual animation
        // will appear at a different y-position
        let path = UIBezierPath()
        path.moveToPoint(CGPoint(x: 16,y: 239 + randomYOffset))
        path.addCurveToPoint(CGPoint(x: 301, y: 239 + randomYOffset), controlPoint1: CGPoint(x: 136, y: 373 + randomYOffset), controlPoint2: CGPoint(x: 178, y: 110 + randomYOffset))
        
        // create the animation 
        let anim = CAKeyframeAnimation(keyPath: "position")
        anim.path = path.CGPath
        anim.rotationMode = kCAAnimationRotateAuto
        anim.repeatCount = Float.infinity
        anim.duration = 5.0
        
        // add the animation 
        square.layer.addAnimation(anim, forKey: "animate position along path")
    }
    

    现在我们有多个方块,但他们开始时间相同,看起来不太自然:

    path-2.gif

    我们可以设定不同方块的动画时间为随机数(这样方块就有不同的移动速度),还有方块开始的位置(以使它们交错出现).

    // each square will take between 4.0 and 8.0 seconds
    // to complete one animation loop
    anim.duration = Double(arc4random_uniform(40)+30) / 10
    
    // stagger each animation by a random value
    // `290` was chosen simply by experimentation
    anim.timeOffset = Double(arc4random_uniform(290))
    

    由于设置的动画不同的属性值,现在我们的方块随贝塞尔曲线移动看起来自然多了.

    path-3.gif

    现在我们很容易就能将红色色块换成图片并且增加页面的背景,从而创建出鱼儿自由游动的场景.

    fishes.gif
    • 贝塞尔曲线动画
      另一个有用的知识是画出曲线的动画.
      当我们创建一个曲线的动画时,贝塞尔曲线的路径并不能展示在屏幕上,但可以用keyframe动画替代.
      在本示例中我们要实现在屏幕中从0到100%画出曲线的动画.
      需要在点击方法@IBAction中增加一些代码.
    // set up some values to use in the curve
    let ovalStartAngle = CGFloat(90.01 * M_PI/180)
    let ovalEndAngle = CGFloat(90 * M_PI/180)
    let ovalRect = CGRectMake(97.5, 58.5, 125, 125)
    // create the bezier path
    let ovalPath = UIBezierPath()
    ovalPath.addArcWithCenter(CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect)),
        radius: CGRectGetWidth(ovalRect) / 2,
        startAngle: ovalStartAngle,
        endAngle: ovalEndAngle, clockwise: true)
    // create an object that represents how the curve 
    // should be presented on the screen
    let progressLine = CAShapeLayer()
    progressLine.path = ovalPath.CGPath
    progressLine.strokeColor = UIColor.blueColor().CGColor
    progressLine.fillColor = UIColor.clearColor().CGColor
    progressLine.lineWidth = 10.0
    progressLine.lineCap = kCALineCapRound
    // add the curve to the screen
    self.view.layer.addSublayer(progressLine)
    // create a basic animation that animates the value 'strokeEnd'
    // from 0.0 to 1.0 over 3.0 seconds
    let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd")
    animateStrokeEnd.duration = 3.0
    animateStrokeEnd.fromValue = 0.0
    animateStrokeEnd.toValue = 1.0
    // add the animation
    progressLine.addAnimation(animateStrokeEnd, forKey: "animate stroke end animation")
    

    这个动画简单地画出了一个圆形,但可以推广于各种图形.有人还将此用于画出文字的路径,或者你可以显示你画任意一条曲线时的路径.

    progress-1.gif
    • 系统默认动画
      iOS7另一个新特性是UIView.performSystemAnimation,但其中仅有UISystemAnimation.Delete的参数,希望苹果能不断丰富此功能以使之能够简单的调用.
    // create and add blue-fish.png image to screen
    let fish = UIImageView()
    fish.image = UIImage(named: "blue-fish.png")
    fish.frame = CGRect(x: 50, y: 50, width: 50, height: 50)
    self.view.addSubview(fish)
    // create an array of views to animate (in this case just one)
    let viewsToAnimate = [fish]
    // perform the system animation
    // as of iOS 8 UISystemAnimation.Delete is the only valid option
    UIView.performSystemAnimation(UISystemAnimation.Delete, onViews: viewsToAnimate, options: nil, animations: {
        // any changes defined here will occur
        // in parallel with the system animation 
    }, completion: { finished in 
        // any code entered here will be applied
        // once the animation has completed
    })
    
    delete.gif

    Girl学iOS100天 第5天

    相关文章

      网友评论

        本文标题:Swift中的动画

        本文链接:https://www.haomeiwen.com/subject/jshqhttx.html