引言
本篇博客我们仍然是来讨论一下Core Animation中的显式动画,在前面的博客中已经介绍了属性动画,帧动画。接下来我们就来介绍一下动画的组合、页面切换时候的过渡动画、以及如何取消动画。
组合动画
CABasicAnimation和CAKeyframeAnimation很好用,但它们是都是单一的动画,如果一个图层需要做多个动画听起来好像有一点麻烦。不过Core Animation还提供了类CAAnimationGroup,它可以把多个动画组合在一起。
CAAnimationGroup继承自CAAnimation,有一个animations数组的属性,用来组合多个动画。
我就把两个属性动画和关键帧动画组合起来写一个小实例,代码如下:
Swift
let colorLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
colorLayer.frame = CGRect(x: 100, y: 200, width: 100, height: 100)
colorLayer.backgroundColor = UIColor.red.cgColor
self.view.layer.addSublayer(colorLayer)
// 属性动画 - 旋转
let animation = CABasicAnimation()
animation.keyPath = "transform.rotation"
animation.duration = 4.0
animation.byValue = 2 * Double.pi
// 帧动画 - 改颜色
let animation1 = CAKeyframeAnimation()
animation1.keyPath = "backgroundColor"
animation1.duration = 4.0
animation1.values = [UIColor.red.cgColor, UIColor.green.cgColor, UIColor.blue.cgColor, UIColor.red.cgColor]
animation1.keyTimes = [0.0, 0.25, 0.5, 0.75, 1.0]
// 动画组
let group = CAAnimationGroup()
group.animations = [animation, animation1]
group.duration = 4.0
colorLayer.add(group, forKey: nil)
}
过渡动画
当我们想要使用属性动画对一些比较难做动画的布局进行添加动画时,比如修改文本,或者修改图这是比较困难的。属性动画只对图层的可动画属性起作用。
所以当我们改变一个不能动画的属性,或者从层级关系中添加或者移除图层时,属性动画就不能起作用了。
于是就有了过渡概念。过渡并不像属性动画平滑地在两个值之间做动画,而是直接影响到整个图层的外观,然后通过一个交换过渡到新的外观。
过渡动画使用CATransition创建,它也是CAAnimation的子类。和其它子类不同CATransition有一个type和subtype来标识变换的效果。
type是CATransitionType类型有以下几个值:
- .fade:默认的过渡类型是,当我们修改一个图层之后会有一个淡入淡出的效果。
- .push:图层的改变会从边缘的一侧滑动进来,然后把旧的图层从另外一侧推出去。
- .moveIn:新的图层从顶部滑入进入,但不会把旧的图层推出去。
- .reveal:是把原始的图层滑动出去,然后显示新的图层。
后面三种过渡类型都是有一个默认的动画方向,从左往右。
然后subtype就是用来管理这个方向的,提供了下面四个值:
- .fromRight:动画从右侧开始。
- .fromLeft:动画从左侧开始。
- .fromTop:动画从顶部开始。
- .fromBottom:动画从底部开始。
下面列举一个简单的实例,修改图层的寄宿图,代码如下:
Swift
let imageView = UIImageView()
let images = [
UIImage(named: "panghu1.jpeg"),
UIImage(named: "panghu2.jpeg"),
UIImage(named: "panghu3.jpg"),
UIImage(named: "panghu4.jpeg"),
UIImage(named: "panghu5.jpeg"),
]
func addImageView() {
imageView.frame = CGRect(x: 100, y: 200, width: 100, height: 100)
imageView.image = images[0]
self.view.addSubview(imageView)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let transition = CATransition()
transition.type = .push
transition.subtype = .fromRight
imageView.layer.add(transition, forKey: nil)
imageView.image = images.randomElement()!
}
效果如下:
从代码中可以看得出,过渡动画和另外两个动画添加到图层上的方式是一样的都是通过-addAnimation:forKey:方法。但是过渡动画对于同一个图层只能使用一次,因此无论你对过渡动画的key设置为什么值,最后都会变成transition。
如果上述案例我们之间采用图层设置contents的方式来设置寄宿图,会发现即使我们没有设置CATransition,当切换寄宿图时也会有默认的动画。也就是说CATtransition也是图层的默认行为不需要我们显式的去设置它。
CATransition的作用远不止如此,因为它并不作用于指定的属性,也就是说我们可以在即使不能准确得知改变了什么的情况下对图层做动画。比如,在不知道UITableView的哪一行被添加或者删除的情况下,直接就可以平滑地删除它。或者在不知道UIViewController内部的视图层级情况下对两个不同的实例做过渡动画。
这两个例子和我们之前所讨论的情况有些不一样,因为它们不是图层属性的改变,而是整个图层树的改变,我们在动画过程中添加或者删除了图层。
为了要确保CATransition添加到的图层在过渡动画结束后不会在树状结构中被移除(否则CATransition也将会一起被移除),我们需要将动画添加到被音响图层的父图层当中。
下面我们来写一个案例,展示在UITabBarController切换标签时候添加淡入淡出动画,代码如下:
Swift
let tabbar = UITabBarController()
let viewController1 = UIViewController()
let viewController2 = UIViewController()
func addTabBarViewController() {
viewController1.view.backgroundColor = UIColor.red
viewController1.tabBarItem.title = "首页"
viewController2.view.backgroundColor = UIColor.green
viewController2.tabBarItem.title = "我的"
tabbar.viewControllers = [viewController1, viewController2]
self.view.addSubview(tabbar.view)
tabbar.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let transition = CATransition()
transition.type = .fade
transition.duration = 1.0
tabBarController.view.layer.add(transition, forKey: nil)
}
效果如下:
UIKit 的过渡动画
CATransition使用起来很棒,但是它所提供的动画类型实在太少了。
其实UIKit通过也提供了一些过渡动画的方法
open class func transition(with view: UIView, duration: TimeInterval, options: UIView.AnimationOptions = [], animations: (() -> Void)?, completion: ((Bool) -> Void)? = nil)
不过它的过渡方式与CATransition所提供的方式不太一样,常用的有一下几种:
Swift
public static var curveEaseInOut: UIView.AnimationOptions { get } // default
public static var curveEaseIn: UIView.AnimationOptions { get }
public static var curveEaseOut: UIView.AnimationOptions { get }
public static var curveLinear: UIView.AnimationOptions { get }
public static var transitionFlipFromLeft: UIView.AnimationOptions { get }
public static var transitionFlipFromRight: UIView.AnimationOptions { get }
public static var transitionCurlUp: UIView.AnimationOptions { get }
public static var transitionCurlDown: UIView.AnimationOptions { get }
public static var transitionCrossDissolve: UIView.AnimationOptions { get }
public static var transitionFlipFromTop: UIView.AnimationOptions { get }
public static var transitionFlipFromBottom: UIView.AnimationOptions { get }
public static var preferredFramesPerSecond60: UIView.AnimationOptions { get }
public static var preferredFramesPerSecond30: UIView.AnimationOptions { get }
我们可以使用它来修改一下上面切换图片的案例,代码如下:
Swift
UIView.transition(with: imageView, duration: 1.0, options: .transitionFlipFromLeft, animations: {
self.imageView.image = self.images.randomElement()!
}, completion: nil)
效果如下:
可以发现这个效果是CATransition所不具备的,所以当我们要设置过渡动画时,可以根据不同的需求来选择使用CATransition还是使用UIKIt实现的方案。
自定动画
除了上面两个方案,我们还可以完全自定义过渡效果。过渡动画的基本原则就是对原始图层外观进行截图,然后添加一个段动画平滑地过渡到下一个图层的。所以我们只需要如何对图层进行截图,就可以使用属性动画或者帧动画来代替过渡动画。
CALayer提供了一个open func render(in ctx: CGContext)方法,可以通过把它绘制到Core Graphics的上下文中捕获当前内容的图片,然后在视图中显示出来。
下面我们就来使用这个方式来创建一个简单的过渡动画。
在第一个页面中present出另外一个页面,代码如下:
Swift
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, true, 1.0)
self.view.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
let homeVC = HomeViewController()
homeVC.image = image
homeVC.modalPresentationStyle = .fullScreen
self.present(homeVC, animated: false, completion: nil)
}
在另外一个页面中,设置动画:
Swift
var image:UIImage?
override func viewDidAppear(_ animated: Bool) {
let imageView = UIImageView(image: image)
imageView.frame = self.view.bounds
view.addSubview(imageView)
UIView.animate(withDuration: 1.0, animations: {
imageView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
imageView.alpha = 0
}) { (finished) in
imageView.removeFromSuperview()
}
}
效果如下:
这里有个警告:-renderInContext:捕获了图层的图片和子图层,但是不能对子图层正确地处理变换效果,而且对视频和OpenGL内容也不起作用。但是用CATransition,或者用私有的截屏方式就没有这个限制了。
取消动画
当我们添加动画时使用open func add(_ anim: CAAnimation, forKey key: String?)方法可以为动画设置一个key。
使用这个key可以获取动画,也可以移除动画,但是不能支持在动画过程中修改动画。
Core Animation为移除动画添加了两个方法
移除指定动画
open func removeAnimation(forKey key: String)
移除所有动画
open func removeAllAnimations()
动画一旦被移除,图层就会立刻更新到模型图层的值,一般来讲动画在结束之后会自动移除,除非设置isRemovedOnCompletion属性为false。
不过如果你设置了动画结束后不自动移除,那么一定要注意手动移除它,否则它将一直存在,一直到图层被销毁。
注意:如果给动画设置了代理,代理属于强引用,如果不手动移除动画则会发生循环引用导致内存泄漏。
结语
在本篇博客中,我们主要介绍了动画组,过渡动画,UIKit中的过渡动画,以及自定义动画和取消动画,有了这些功能我们可以创建出很多有趣的动画和页面切换效果。
下一篇博客我们将介绍CAMediaTiming协议,来看看Core Animation是怎么处理时间的。