锚点的基本概念

要想实现一些复杂的 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

Cover Flow 3D 效果

先初始化视图

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

本文代码:点击查看