原文地址
简介
分析的知识点:
- layer tree
- CGAffineTransform
- addArc
- calculationMode
- CAAnimationGroup
- UIView.animate animateKeyframes、CABasicAnimation、CAKeyframeAnimation
提到Core Animation,就会想到CALayer
。
修改一个View的layer
后,会用Core Graphics
框架来渲染。
Core Animation
有三个tree:
-
model layer tree
(简称layer tree
),存储动画的最终状态值,当改变layer的属性值时,使用这一个 -
presentation layer tree
,在动画过程中,使用这个来获取当前动画过程中的值,但不要改变他的值 -
rendering tree
,最后一个对Core Animation
而言是私有的。
UIView常用动画
透明度变化:
UIView.animate(withDuration: 0.3, animations: {
self.pandaImageView.alpha = 1
}) { _ in
self.pandaImageView.removeFromSuperview()
}
或者更改一下frame,直接这样调用,使用非常方便。再看下面这一个,放大缩小再复原:
UIView.animate(withDuration: 0.3, animations: {
self.pandaImageView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
}) { _ in
UIView.animate(withDuration: 0.3, animations: {
self.pandaImageView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
}, completion: { _ in
UIView.animate(withDuration: 0.3, animations: {
self.pandaImageView.transform = CGAffineTransform(scaleX: 1, y: 1)
}, completion: { _ in
self.view.removeFromSuperview()
})
})
}
感觉也没啥问题,就是嵌套多了一点,也有点丑,针对这种嵌套,可以这样解决:
let totalTime = toBig + toSmall + dismissView
UIView.animateKeyframes(withDuration: totalTime, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: toBig/totalTime, animations: {
self.frontView.transform = CGAffineTransform(scaleX: 1.25, y: 1.25)
})
UIView.addKeyframe(withRelativeStartTime: toBig/totalTime, relativeDuration: toSmall, animations: {
self.frontView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
})
UIView.addKeyframe(withRelativeStartTime: (toBig + Animation.toSmall)/totalTime, relativeDuration: dismissView, animations: {
self.alpha = 0
})
}) { _ in
self.removeFromSuperview()
}
注 : withRelativeStartTime
和 relativeDuration
这两个值都是0.0到1.0之间的数值, 需要除以总动画时间。
这种比刚才的写法要好很多,不用嵌套多层,但解决不了复杂的动画。如果在做放大缩小前还有别的动画呢,比如先沿特定曲线路径移动,再放大缩小,可能这种写法就满足不了,对于复杂动画构建及时间函数控制,看下文。
基本动画
让一个图片做直线运动:
func lineAnimation() {
let animation = CABasicAnimation()
animation.keyPath = "position.x";
animation.fromValue = kScreenWidth/2;
animation.toValue = kScreenWidth;
animation.duration = 1;
pandaImageView.layer.add(animation, forKey: "line")
pandaImageView.layer.position = CGPoint(x: kScreenWidth, y: kScreenHeight/2)
}
动画的键路径"position.x",在x轴上的直线运动,其它的键可以查看Core Animation
键路径的列表
动画开始后,图片从中心位置移到了屏幕边缘,动画结束后,图片回到了中心点。要想让动画结束后保持最后的状态,有两种方法:
- 在
model layer
上更新属性,推荐这种,这样presentation layer
和model layer
就同步了 - 设置动画的
fillMode
属性为kCAFillModeForward
以留在最终状态,并设置removedOnCompletion
为NO
以防止它被自动移除
采用第二种方法会将已完成的动画保持在layer上,有额外开销,还需要考虑何时需要手动remove掉,比如在应用切后台时。
沿路径动画
做有弧度的曲线或者圆形动画时,可以用CAKeyframeAnimation
,指定path
,逆时针做90度的圆弧运动:
let keyframeAnimation = CAKeyframeAnimation(keyPath: "position")
let path = CGMutablePath()
path.addArc(center: CGPoint(x: 0, y: 0), radius: radius, startAngle: 0, endAngle: 1.5 * .pi, clockwise: true)
keyframeAnimation.path = path
keyframeAnimation.calculationMode = kCAAnimationPaced
keyframeAnimation.duration = 2.0;
pandaImageView.layer.add(keyframeAnimation, forKey: "round")
pandaImageView.layer.position = CGPoint(x: 0, y: 0)
CGAffineTransform
对pandaImageView
做了一个CGAffineTransform(translationX: kScreenWidth/2, y: kScreenHeight/2)
, CGAffineTransform
为一个变换,可以把一个坐标系映射到另外一个坐标系,平移、放缩、旋转都可以做到,详细请看, CGAffineTransform
默认的anchor point
为中心点(0.5, 0.5),这里我们不用改,正是想要的,在应用上这个transform
后, addArc
圆的圆心就变成了(0,0)
addArc
addArc
中clockwise
,有如下解释:
true to make a clockwise arc; false to make a counterclockwise arc.
clockwise为true时,path的方向为逆时针,而算角度,始终以逆时针方向算。
calculationMode
calculationMode
能决定动画的时间曲线, kCAAnimationPaced
为匀速,其它的几种mode可以查看官方文档,默认为kCAAnimationLinear.
A->B->C->D->A,连起来的动画,需要一个path:
path.addLine(to: CGPoint(x: radius, y: 0))
path.addArc(center: CGPoint(x: 0, y: 0), radius: radius, startAngle: 0, endAngle: 1.5 * .pi, clockwise: true)
path.addLine(to: CGPoint(x: -radius/2, y: 0))
path.addLine(to: CGPoint(x: 0, y: 0))
放缩动画
let keyframeAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
keyframeAnimation.values = [1.2, 0.8, 1]
keyframeAnimation.duration = 1.0;
keyframeAnimation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)]
关于timing function
,看下图:
动画组
let roundAnimation = self.roundAnimation()
let scaleAnimation = self.scaleAnimation()
scaleAnimation.beginTime = roundAnimation.duration
let groupAnimation = CAAnimationGroup()
groupAnimation.animations = [roundAnimation, scaleAnimation]
groupAnimation.duration = roundAnimation.duration + scaleAnimation.duration
pandaImageView.layer.add(groupAnimation, forKey: "group")
指定好开始时间和持续时间,可以使用一个动画组
动画效果如图:
对于开篇提到的问题,用动画组合可以很好的解决问题
参考文章
objccn动画解释
Core Animation Programming Guide