【iOS动画】学习笔记第一弹(View Animation)

本文是笔者学习iOS动画的一些小总结;

View Animation

动画其实就是UIView基本属性(animatable)的操作,我们写动画的时候,其实不需要关心其中的数学计算,只需要熟悉API的特性即可。

屏幕快照 2017-08-20 22.45.54.png

我们可以看到UIView中的属性中注明为animatable的,那么这个属性就是我们写动画可以用到的属性。还有一个alpha属性同样是animatable

本文中的动画方法都是UIKit为我们封装好的一些方法,也就是UIView提供的类方法,比如常见的animate(withDuration:animations:),因为是UIKit层提供的,所以这些动画的方法都相对简单好用,没有涉及到 Core Animation提供的复杂API。

animate

UIView的一个extension里都是animate的类方法,如下图所示:

屏幕快照 2017-08-20 23.53.18.png

normal

UIViewanimate类方法是最简单的动画方法,相信大家一定都用过。

UIView.animate(withDuration: 0.5, delay: 0.4,
  options: [.repeat, .autoreverse, .curveEaseIn],
  animations: {
    self.password.center.x += self.view.bounds.width
  },
  completion: nil
)

这里想了好久不知道如何去解释,索性就不去解释了,反正iOSer能看懂就行😂

这里的options倒可以解释下, 这个属性可以告诉UIKit如何去执行animations里的操作,是UIViewAnimationOptions类型。常见的有

repeat // 重复的执行`animations`里的操作
curveEaseInOut //类似汽车行驶先加速之后到达目的地时减速的效果执行动画
curveEaseIn // 汽车加速
curveEaseOut // 汽车减速
curveLinear // 汽车匀速

spring

spring类型的animate类方法相对于normal类型的增加了弹簧的效果,使动画更加符合现实中的动画效果。

 UIView.animate(withDuration: 1.5, delay: 0.0, usingSpringWithDamping: 0.2,
      initialSpringVelocity: 0.0, options: [],
      animations: {
        self.loginButton.bounds.size.width += 80.0
      },
      completion: nil
)

方法中usingSpringWithDamping取值为0-1,数值越大,弹簧效果越不明显。

transition

之前的normal和spring都是基于UIViewanimatable属性的动画,如果想要给添加view或者移除view做动画的话,就需要使用transition类型的animate类方法来实现。

UIView.transition(with: animationContainerView,
   duration: 0.33,
   options: [.curveEaseOut, .transitionFlipFromBottom],
   animations: {
     self.animationContainerView.addSubview(newView)
   },
   completion: nil
 )

上述代码给animationContainerView制造了一个动画,当有subview添加到animationContainerView中时,动画就会执行。方法中的options同样的告诉UIKit如何去执行animations里的操作,是UIViewAnimationOptions类型。常见的有

.transitionFlipFromLeft
.transitionFlipFromRight
.transitionCurlUp
.transitionCurlDown
.transitionCrossDissolve
.transitionFlipFromTop
.transitionFlipFromBottom

当我们想替换view时,可以使用下面的方法

UIView.transition(from: oldView, to: newView, duration: 0.33,
  options: .transitionFlipFromTop, completion: nil)

UIKit帮了我们太多😂

keyframe

之前的三种类型的动画都是单一效果的,现实中的动画大多是有许多步骤的,此时如果我们使用completion来连接下一个动画,代码会被嵌套很多层。所以我们可以将每个单独的步骤拆分成一个个的Keyframes,然后用Keyframe动画将各个单独的Keyframes连接起来生成一个完整的动画。

 UIView.animateKeyframes(withDuration: 1.5, delay: 0.0, animations: { 
            UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: { 
                self.planeImage.center.x += 80.0
                self.planeImage.center.y -= 10.0
            })
            
            UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 0.4, animations: { 
                self.planeImage.transform = CGAffineTransform(rotationAngle: -.pi / 8)
            })
            
            UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25, animations: { 
                self.planeImage.center.x += 100.0
                self.planeImage.center.y -= 50
                self.planeImage.alpha = 0.0
            })
            
            UIView.addKeyframe(withRelativeStartTime: 0.51, relativeDuration: 0.01, animations: { 
                self.planeImage.transform = .identity
                self.planeImage.center = CGPoint(x: 0.0, y: originalCenter.y)
            })
            
            UIView.addKeyframe(withRelativeStartTime: 0.55, relativeDuration: 0.45, animations: { 
                self.planeImage.alpha = 1.0
                self.planeImage.center = originalCenter
            })
            
        }, completion: nil)

上例写了一个飞机起飞到着陆的动画,addKeyframe里的withRelativeStartTimerelativeDuration都是相对于animateKeyframeswithDuration来计算的。

auxiliary views

 // cube animation
    func cubeTransition(label: UILabel, text: String, direction: AnimationDirection) {
        let auxLabel = UILabel(frame: label.frame)
        auxLabel.text = text
        auxLabel.font = label.font
        auxLabel.textAlignment = label.textAlignment
        auxLabel.textColor = label.textColor
        auxLabel.backgroundColor = label.backgroundColor
        
        let auxLabelOffset = CGFloat(direction.rawValue) * label.frame.size.height / 2.0
        auxLabel.transform = CGAffineTransform(scaleX: 1.0, y: 0.1)
         .concatenating(CGAffineTransform(translationX: 0.0, y: auxLabelOffset))
        label.superview?.addSubview(auxLabel)
        
        UIView.animate(withDuration: 0.5, delay: 0.0, options: [.curveEaseOut], animations: { 
            auxLabel.transform = .identity
            label.transform = CGAffineTransform(scaleX: 1.0, y: 0.1)
             .concatenating(
                CGAffineTransform(translationX: 0.0, y: -auxLabelOffset)
            )
        }, completion: { _ in
            label.text = auxLabel.text
            label.transform = .identity
            auxLabel.removeFromSuperview()
        })
    }

上述代码写了一个label立方体翻转的效果,但其实不是真的3D效果,这里使用到了一个辅助的label,对两个label同时动画,最后移除这个辅助label,从而达到了立方体翻转的效果。

Auto Layout

之前的动画都是设置 animatable的属性,UIKit帮助我们实现对应的动画效果。但是现在我们大多更多的使用Auto Layout来进行布局界面,此时上面的几种动画方法同样也是有效的,只是此时我们操作的是约束。

UIViewPropertyAnimator 更新于2017-11-11

UIViewPropertyAnimator 是iOS10新出的一个动画相关的类,通过这个类我们可以简单的创建更加易于控制的view animation,也就是说此时我们在一些场景中可以不去创建layer animation,通过UIViewPropertyAnimator也可以完成需要。

UIViewPropertyAnimator和之前的UIView.animate系列方法是相辅相成的,也就是说有些简单的情况,我们只需要使用UIView.animate系列方法就可以了,没有必要去使用UIViewPropertyAnimator。

一个简单的UIViewPropertyAnimator创建的动画如下:

let scale = UIViewPropertyAnimator(duration: 0.33, curve: .easeIn)
   // addAnimations 也可以添加之前的上文所说的keyFrame animation等
    scale.addAnimations {
      view.alpha = 1.0
    }
    scale.addAnimations({
      view.transform = CGAffineTransform.identity
    }, delayFactor: 0.33) // 这里的delayFactor是一个相对比例(0-1),是相对于动画剩下的时间,这样就保证了延迟的时间不会超过总时间
    scale.addCompletion {_ in
      print("ready")
    }

各种不同的animator 可以封装在单独的一个类,这样不同的view如果想要创建相同的动画直接调用方法然后start即可。

animation timing

UIViewPropertyAnimator默认提供了以下的curve选择:

public enum UIViewAnimationCurve : Int {

    
    case easeInOut // slow at beginning and end

    case easeIn // slow at beginning

    case easeOut // slow at end

    case linear
}

其实这些curve都是控制两个control point形成的贝塞尔曲线,具体可以去这个网站实际体验下curve

UIViewPropertyAnimator提供了自定义这两个control point的方法,可以让我们自定义timing 效果。

  public convenience init(duration: TimeInterval, controlPoint1 point1: CGPoint, controlPoint2 point2: CGPoint, animations: (() -> Swift.Void)? = nil)

除了使用自定义control point的方式来自定义timing,我们还可以通过下面这个方法来为animator提供自定义的timing方式:

public init(duration: TimeInterval, timingParameters parameters: UITimingCurveProvider)

UITimingCurveProvider是一个协议,UIKit提供了两个已经遵守该协议的类,我们可以方便的拿来使用。
UICubicTimingParameters and UISpringTimingParameters. 例如下面这个spring timing:

 let spring = UISpringTimingParameters(dampingRatio: 0.55)
    
 let animator = UIViewPropertyAnimator(duration: 1.0, timingParameters: spring)

这样这个animator的动画运行时就是弹簧效果了。

animation state

因为UIViewPropertyAnimator可以创建##易于控制##的动画,所以UIViewPropertyAnimator可以让我们知道现在动画的状态,以下是三个主要的状态属性:

1.isRunning(Bool): animator 的动画是否正在运行,默认是false,当调用`startAnimation`后变为true;
2.isReversed(Bool): animator 的动画执行顺序,默认是false,当设置为true后,动画会反方向执行,此时才改变没有效果,也就是只能一次性设置;
3.state: animator 动画的状态,具体见下图:
屏幕快照 2017-11-11 16.57.24.png

fractionComplete可以接受一个animator的完成百分比,达到一种可交互的动画,类似3Dtouch 的重按动画效果;

ViewController Transition Animation

同样的正是因为UIViewPropertyAnimator可以让我们控制动画的一系列状态,我们也可以使用它来实现控制器的可交互切换动画,下面方法是iOS10中UIViewControllerAnimatedTransitioning协议中新增的一个方法,允许提供一个animator:

func interruptibleAnimator(using transitionContext:
  UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating

总的来说,UIViewPropertyAnimator是一个中间产物,为我们提供了多一种的选择,不及UIView.animate 来的简单,不及Layer Animation的全面。

最后

未完待续第二弹

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

推荐阅读更多精彩内容