《iOS 三问》 -- iOS 动画之 View 动画
iOS 的动画实质上是在 CALayer 上实现的,但是在 UIView 上也提供了对底层动画的封装,使得我们可以很方便地直接对 UIView 进行一些简单的动画。使用 UIView 提供的动画机制,可以作用于这些属性上:alpha,backgroundColor,bounds,center,frame,transform。
1 Block 动画
block 动画是最简单的 View 动画,你只需要把要变化的属性在 UIView 提供的 animations:block
中即可,例如下面这段代码,将指定 view 动画为背景色渐变为红色,然后位置沿 x 轴右移 100 个象素点,并且在动画完成之后执行回调,将 view 的背景色修改为蓝色。
1 | [UIView animateWithDuration:1 animations:^{ |
1.1 在 block 之外修改动画属性
在 iOS 动画概述 中我们分析过动画的实现细节,在重绘时刻 (redraw monment),动画才会被处理,而 View 本身的重绘也会发生在此刻,我们以下面几个实例也深入了解下。
下面的几个示例可以在我的 iOSOneDemo 中查看。
1.1.1 示例 1 - 在 block 中先移 100 再移 300
1 | [UIView animateWithDuration:1 animations:^{ |
上面这段代码并不会有两个动画出现。在重绘时刻动画被处理时,同一个属性的修改会被覆盖,最终 animView 只会从当前位置移动到 x=300 的位置上去
1.1.2 示例 2 - 在动画 block 之前修改属性
1 | CGPoint p = animView.center; |
最终看到的动画是,animView 会马上渲染在 x=350 的地方,然后再向左动画移至 200 处。因为在重绘阶段,发现 animView.center 被设到 350 了,所以会马上触发一次重绘更新 animView 在屏幕上的位置。然后执行动画 block,从当前位置也就是 x=350 的地方,移至 x=200 的地方。
1.1.3 示例 3 - 在动画 block 之后修改属性
1 | [UIView animateWithDuration:1 animations:^{ |
这个最终看到的动画比较难想,animView 会马上移到 x=200 的位置,然后再动画右移至 x=350 的位置。为什么会这样呢?
我们可以想像一下在执行完这段代码后的动画处理,一开始的动画 block 是让 animView 从当前 position 移到 x=200 处,会设一个动画的起始值 (animView 的当前值) 和一个终止值 (x=200),但是 block 后又去 animView 的 center 属性进行了修改,导致动画的起始值变成当前值 (x=200), 终止值变成 (x=350)。所以在重绘阶段的动画就是从 x=200 处移动 x=350 处。
1.1.4 小结
鉴于这种在动画 block 前后修改同一属性的动画实际效果是啥比较烧脑,所以网上的议论都是禁止在动画 block 的前后去修改动画属性。如果非得修改属性的话,比如给要动画的 view 在动画之前设置一个初始值,可以使用 [UIView performWithoutAnimation:block]
:
1 | [UIView animateWithDuration:1 animations:^{ |
这样,在 performWithoutAnimation:block
之中的属性修改将会独立于动画显示出来,比如上面的例子,view 会先放到 x=200 的地方,然后再做 backgroundColor 渐变的动画。
1.2 动画选项 options
block 动画可以使用 UIViewAnimationOptions
对动画进行配置,从而达到我们想要的效果。动画的设置主要是设置:
1 | - duration 设置动画的持续时间 |
比如:
1 | NSUInteger op = UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat; |
1.2.1 小技巧 - 动画重复 N 次
1 | - (void)rotateView:(UIView *)view times:(int)times |
1.3 block 动画嵌套
block 动画是可以嵌套的,考虑这种情形,同一个 View 有两个动画要同时开始,但是他们的配置不同(比如持续时长不同),这时就可以使用嵌套 block 动画。但是要使用上 UIViewAnimationOptionOverrideInheritedDuration
这个选项。
1 | [UIView animateWithDuration:2 animations:^{ |
1.4 停止动画
这个很简单,使用 [animView.layer removeAllAnimations]; // 停止所有动画
即可。
2 弹性动画 (Spring Animation)
iOS7 中提供了强大的弹性动画选项,可以很方便地制作弹性动画,从而让你的交互更加生动。
主要用到 damp
与 springVelocity
两个属性,如下示例:
1 | [UIView animateWithDuration:2 |
3 关键帧动画 (Keyframe Animation)
也是 iOS7 新增的动画方式。当你的动画是由多个动画串联的时候,你会怎样去组织他们?
在这之前一般是在一个动画的 complete block
中去开启下一个动画,这样会使得代码比较长而且不容易修改,比如中间要去掉一个动画你必须把一头一尾的 complete block
修改之。而关键帧动画就可以很好地解决这个问题 – 也就是说,关键帧动画主要负责多个动画的串联,如示例:
1 | __block CGPoint currentPoint = animView.center; |
其中主要两个方法
1 | 1. animateKeyframesWithDuration: 创建一个关键帧动画,创建整个关键帧动画的外壳,整体的持续时间与动画选项; |
4 过渡动画 (Transition Animation)
就像电影画面从一个场景转到另一个场景,view 也可以设置这样的转场动画。具体的效果可以在我的 iOSOneDemo 中查看。
过渡动画比较简单,主要两个方法:
1 | 1. [UIView transitionWithView:] - 用于 view 自动的 content 变化过场动画 |
4.1 View 自身 Content 变化转场动画
1 | animView.image = [UIImage imageNamed:@"sunny"]; |
上面的动画会导致当前阳光普照的图片向左翻转,转成一张下雨的图片,看起来就像是下雨的这张图片在它的背面一样。
4.2 从一个 view 到另一个 view 的过场动画
1 | [UIView transitionFromView:view1 |
这样就完成了从 view1 变成 view2 的转场动画。值得注意的是,动画完成后,view1 已被 view2 替代,那么 view1 怎么处置呢?如果没有配置了 UIViewAnimationOptionShowHideTransitionViews
选项,那么 view1 会被 remove 掉,而 view2 是 add 进来。而如果使用了 UIViewAnimationOptionShowHideTransitionViews
选顶,view1 只是 hide,而 view2 只是 show(也就是说 view2 之前就要先被 add 进来)。
5 引用
【1】[Matt Neuburg - 《Programming iOS deep into views,view controllers and frameworks》]