swiftSwift专题ios开发

Swift中的动画

2015-11-01  本文已影响1593人  Girl_iOS

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

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有许多参数:
@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
// 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)
// 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
// 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
// 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天

上一篇下一篇

猜你喜欢

热点阅读