Swift Animation 3D动画

Posted by Calvin on 2017-06-27

锚点的基本概念

要想实现一些复杂的 3D 动画,首先要先搞清楚锚点的概念。AnchorPoint 是一个 CGPoint 类型的值,该值指定了一个基于 bounds 的坐标系位置。也就是说锚点指定了 bounds 相对于 position 的值,同时也是变化时候的中心点。

一般情况下,锚点的默认值是(0.5,0.5),取值范围为0~1之间。当锚点值为(0,0)时,那么在进行 2D、3D 变幻时都围绕着图层的左上角点进行变幻。当锚点值为(1,0)时,那么在 2D、3D 变幻时都围绕着图层的右上角进行变幻。

矩阵变换的基本原理

iOS 中利用 CATransform3D 实现 3D 变换效果。CATransform3D 其实质是定义了一个三维变换(4X4 CGFloat 值的矩阵),利用该矩阵可以实现图层的旋转、缩放、偏移、歪斜和视图透视等效果。

先看看 CATransform3D 所描述的矩阵有哪些功能

struct CATransform3D
{
CGFloat m11(x 缩放),m12(y 切变),m13(),m14();
CGFloat m21(x 切变),m22(y 缩放),m23(),m24();
CGFloat m31(),m32(),m33(),m34(透视);
CGFloat m41(x 平移),m42(y 平移),m43(z 平移),m44();
}

该矩阵是一个 4x4 的矩阵,矩阵中每一个值都会在变幻中起到一定的作用。那么如何让理解它们呢?假设原来图层的位置是 x、y、z、1。同样定义为一个矩阵,不过这个矩阵的行列是一个 1x4 的矩阵(表示一行四列)。该矩阵与 CATransform3D 相乘最终会得到一个新的矩阵[newx,newy,newz,1]。如下图所示:

2017062614984843499640.png

3D旋转效果

了解了锚点和矩阵的基本知识,现在举个例子实现图像的旋转。

var imageView:UIImageView?
override func viewDidLoad() {
super.viewDidLoad()
imageView = UIImageView()
imageView?.frame = CGRect(x: 0, y: 0, width: 400, height: 300)
imageView?.center = self.view.center
imageView?.image = UIImage(named: "image.jpg")
imageView?.contentMode = UIViewContentMode.scaleAspectFit
imageView!.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.view.addSubview(imageView!)
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(4)
var transform:CATransform3D = CATransform3DIdentity
transform.m22 = 0.5
imageView!.layer.transform = CATransform3DMakeRotation(CGFloat(M_PI/2.0), 1, 1, 1);
UIView.commitAnimations()
}

效果如下:

20170626149848471325132.gif

CATransform3DMakeRotation 该方法实现动画的 3D 旋转

  1. angle:CGFloat 表明动画的旋转角度
  2. _x:CGFloat 表明动画旋转时的旋转轴心
  3. _y:CGFloat 表明动画旋转时的旋转轴心
  4. _z:CGFloat 表明动画旋转时的旋转轴心

如果修改 CATransform3DMakeRotation 代码为:

imageView!.layer.transform = CATransform3DMakeRotation(CGFloat(M_PI/2.0), 1, 1, 0);

效果如下:

20170626149848484194652.gif

修改锚点位置和旋转轴如下:

imageView!.layer.anchorPoint = CGPoint(x: 0, y: 0)
imageView!.layer.transform = CATransform3DMakeRotation(CGFloat(M_PI/2.0), 0, 1, 0);

效果如下:

20170626149848495745391.gif

除了 CATransform3DMakeRotation 方法之外还有一些方法比较常用,比如用于移动的 CATransform3DMakeTranslation 方法,用于缩放的 CATransform3DMakeScale 方法。这些方法使用起来都比较简单,不是直接作用在矩阵上而是将矩阵变幻进行了2次封装。

我们将部分代码修改为如下:

UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(4)
var transform:CATransform3D = CATransform3DIdentity
transform.m22 = 0.5
imageView!.layer.transform = CATransform3DScale(transform, 1, 1, 1)
UIView.commitAnimations()
```
效果如下:
![20170626149848529521512.gif](https://raw.githubusercontent.com/CalvinCheungCoder/Blog/master/blog-img/20170626149848529521512.gif)
### Cover Flow 3D 效果
先初始化视图
```Swift
var imageViewArray:NSMutableArray?
let imageView1:UIImageView = UIImageView(frame:CGRect(x: 100, y: 100, width: 200, height: 250))
let imageView2:UIImageView = UIImageView(frame:CGRect(x: 100, y: 100, width: 200, height: 250))
let imageView3:UIImageView = UIImageView(frame:CGRect(x: 100, y: 150, width: 300, height: 200))
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.black
imageViewArray = [imageView1,imageView2,imageView3]
for i in 0...(imageViewArray?.count)!-1{
let imageView:UIImageView = imageViewArray?.object(at: i) as! UIImageView
let imageName:String = "\(i).jpg"
imageView.image = UIImage(named: imageName)
imageView.layer.anchorPoint.y = 0.0
view.addSubview(imageView)
}
}

实现 Cover Flow

override func viewWillAppear(_ animated: Bool) {
for i in 0...(imageViewArray?.count)!-1{
var imageTransform = CATransform3DIdentity
imageTransform.m34 = -0.005;
imageTransform = CATransform3DTranslate(
imageTransform, 0.0, 50.0, 0.0)
imageTransform = CATransform3DScale(
imageTransform, 0.95, 0.6, 1.0)
if i==0 {
imageTransform = CATransform3DRotate(
imageTransform, CGFloat(M_PI_4/2), 0.0, 1.0, 0.0)
}else if i==1{
imageTransform = CATransform3DRotate(
imageTransform, CGFloat(-M_PI_4/2), 0.0, 1.0, 0.0)
}
let imageView:UIImageView = imageViewArray?.object(at: i) as! UIImageView
let animation = CABasicAnimation(keyPath: "transform")
animation.fromValue = NSValue(caTransform3D:
imageView.layer.transform)
animation.toValue = NSValue(caTransform3D: imageTransform)
animation.duration = 3
let animBounds:CABasicAnimation = CABasicAnimation()
animBounds.keyPath = "position"
animBounds.duration = 3
if i==0 {
animBounds.toValue = NSValue(cgPoint:CGPoint(x: 100, y: 10))
}else if i==1{
animBounds.toValue = NSValue(cgPoint:CGPoint(x: 300, y: 10))
}else{
animBounds.toValue = NSValue(cgPoint:CGPoint(x: 200, y: 20))
}
let animGroup:CAAnimationGroup = CAAnimationGroup()
animGroup.duration = 3
animGroup.repeatCount = 1
animGroup.isRemovedOnCompletion = false
animGroup.fillMode=kCAFillModeForwards
animGroup.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
animGroup.animations = [animation,animBounds]
imageView.layer.add(animGroup, forKey: "\(i)")
}
}

效果如下:

20170626149848554481158.gif

本文代码:点击查看