Swift中的动画

本文翻译自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天

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,001评论 5 13
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,000评论 1 23
  • 前言 本文只要描述了iOS中的Core Animation(核心动画:隐式动画、显示动画)、贝塞尔曲线、UIVie...
    GitHubPorter阅读 3,538评论 7 11
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,321评论 6 30
  • 显式动画 显式动画,它能够对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动。 属性动画 ...
    清风沐沐阅读 1,867评论 1 5