CATransform3D基本动画

最近有看到一些在iOS上面实现的一些动画,通过一些简单的平移,旋转,缩放等等实现了一些特别炫的效果,于是就深入研究了一下,简单整理了一些基本的东西,在这块做一下分享。

前言

在学习它之前,我们先来了解一些基本的概念
1、三维坐标系:视角垂直与屏幕而言,x轴向右,y轴向下,z轴垂直屏幕向外。
2、坐标系原点:ios默认以图层的左上角点为坐标原点,osx默认以图层左下角为坐标原点。注意是默认,因为图层的坐标原点是可以设置的,下面会介绍。
3、view于layer的关系:对于UIView对象,layer属性承担了显示的职能,对view设置frame和bounds就是对view的layer设置,因此下面将不区分view和layer。
4、图层的锚点anchorPoint:是一个CGPoint值,x,y取值范围(0~1),默认为(0.5,0.5) 对于图层本身而言,顾名思义,锚点就用来定位图层的点。锚点有两个职能:(1)与position一同确定图层相对于父图层的位置;(2)作为图层旋转、平移、缩放的中心。
5、决定图层位置的position:图层的锚点相对于父图层坐标系原点的偏移。

CATransform3D

1.CATransform3D概念

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();
};

里面各个参数的设置可以进行相应的变换。

2.Translation(平移变换)

平移变换有两个方法:

(1)CATransform3D CATransform3DTranslate (CATransform3D t, CGFloat tx,
CGFloat ty, CGFloat tz)
(2)CATransform3D CATransform3DMakeTranslation (CGFloat tx,
CGFloat ty, CGFloat tz)

两个的区别:第一个可以在t的基础上再叠加变换,而第二个每次变换都是以初始状态为基础。

官方文档:Returns a transform that translates by ‘(tx, ty, tz)’. t’ = [1 0 0 0; 0 1 0 0; 0 0 1 0; tx ty tz 1].即返回一个4x4的矩阵,将该矩阵立起来后看:

1    0    0    0  
0    1    0    0  
0    0    1    0   
tx   ty   tz   1

对应CATransform3D的公式,tx、ty、tz参数分别用于x平移、y平移、z平移。x、y的平移比较好理解,对于tz来说,值越大,那么图层就越往外(接近屏幕),值越小,图层越往里(屏幕里)。
tx:X轴偏移位置,往下为正数。
ty:Y轴偏移位置,往右为正数。
tz:Z轴偏移位置,往外为正数。

//平移
-(void)transition{
    CATransform3D t = CATransform3DIdentity;
    //x方向平移50 y方向平移50
    [UIView animateWithDuration:2.0 animations:^{
        self.backImg.layer.transform = CATransform3DTranslate(t, 50, 50, 0);
    } completion:^(BOOL finished) {    
         //动画执行完成还原到初始状态
        self.backImg.layer.transform = CATransform3DIdentity;
    }];
}

3.Rotation(旋转变换)

(1)CATransform3D CATransform3DRotate (CATransform3D t, CGFloat angle,
CGFloat x, CGFloat y, CGFloat z)
(2)CATransform3D CATransform3DMakeRotation (CGFloat angle, CGFloat x,
CGFloat y, CGFloat z)

angle:旋转的弧度,所以要把角度转换成弧度:角度 * M_PI / 180
x:绕X轴进行旋转。值范围-1 — 1之间
y:绕Y轴进行旋转。值范围-1 — 1之间
z:绕Z轴进行旋转。值范围-1 — 1之间
旋转方向:旋转遵循�左手定则。以绕y轴旋转为例,当参数y为正时,左手大拇指指向y轴正向,手掌弯曲方向即为旋转方向,此时从大拇指指向往里看是顺时针方向。若y参数为负,将大拇指指向y轴负向,此时从大拇指指向往里看是逆时针方向。 绕x轴z轴旋转判断方法相同。

//旋转
-(void)rotate{
    CATransform3D t = CATransform3DIdentity;
    //x,y,z 的值决定旋转轴的方向
    [UIView animateWithDuration:2.0 animations:^{
        self.backImg.layer.transform = CATransform3DRotate(t, 60 * (M_PI / 180), 1, 1, 0);
    } completion:^(BOOL finished) {
        self.backImg.layer.transform = CATransform3DIdentity;
    }];
}
CATransform3D基本动画_第1张图片
原始状态.png

CATransform3D基本动画_第2张图片
x轴旋转.png

CATransform3D基本动画_第3张图片
y轴旋转.png

CATransform3D基本动画_第4张图片
z轴旋转.png

CATransform3D基本动画_第5张图片
x和y轴旋转.png

4.Scale(缩放变换)

(1)CATransform3D CATransform3DScale (CATransform3D t, CGFloat sx,
CGFloat sy, CGFloat sz)
(2)CATransform3D CATransform3DMakeScale (CGFloat sx, CGFloat sy,
CGFloat sz)

官方文档L:Returns a transform that scales by `(sx, sy, sz)’: * t’ = [sx 0 0 0; 0 sy 0 0; 0 0 sz 0; 0 0 0 1]. 返回的矩阵为:

sx   0    0    0  
0    sy   0    0  
0    0    sz   0  
0    0    0    1

sx、sy、sz参数对应x、y、z轴的比例缩放,>0为正向比例缩放,<0为反向比例缩放,参数等于1时表示不进行缩放。
sz:整体比例变换时,也就是m11(sx)== m22(sy)时,若m33(sz)>1,图形整体缩小,若0<1,图形整体放大,若m33(sz)<0,发生关于原点的对称等比变换。
以x,y轴为例:

//缩放
-(void)scale{
    CATransform3D t = CATransform3DIdentity;
    //x,y缩放
    [UIView animateWithDuration:2.0 animations:^{
        self.backImg.layer.transform = CATransform3DScale(t,0.5, 0.5, 1);
    } completion:^(BOOL finished) {
        self.backImg.layer.transform = CATransform3DIdentity;
    }];
}
CATransform3D基本动画_第6张图片
缩小0.5.png

CATransform3D基本动画_第7张图片
放大1.5.png

综合案例

通过上述三种动画方式我们做一个可以旋转的立方体。
具体思路:我们需要通过CATransformLayer创建一个容器,然后通过CALayer创建六个页面,通过平移旋转的方式将它们组合到一起放到容器里,通过给立方体添加手势实现旋转,具体的效果如下:


CATransform3D基本动画_第8张图片
效果.gif

具体代码如下:

-(void)diceCube{
    CATransformLayer * cube = [CATransformLayer layer];
    //第一个面
    //+z方向平移
    CATransform3D t = CATransform3DMakeTranslation(0, 0, 50);
    [cube addSublayer:[self diceFaceWithTransform:t withIndex:1]];
    
    //第二个面
    //+x方向平移
    t = CATransform3DMakeTranslation(50, 0, 0);
    //y轴旋转90度
    t = CATransform3DRotate(t, 90 * (M_PI / 180), 0, 1, 0);
    [cube addSublayer:[self diceFaceWithTransform:t withIndex:2]];
    
    //第三面
    //+y方向平移
    t = CATransform3DMakeTranslation(0, 50, 0);//CATransform3D CATransform3DInvert (CATransform3D t);
    //x轴旋转90度
    t = CATransform3DRotate(t, 90 * (M_PI / 180), -1, 0, 0);
    [cube addSublayer:[self diceFaceWithTransform:t withIndex:3]];
    
    //第四面
    //-x方向平移
    t = CATransform3DMakeTranslation(-50, 0, 0);
    //y轴旋转90度
    t = CATransform3DRotate(t, 90 * (M_PI / 180), 0, -1, 0);
    [cube addSublayer:[self diceFaceWithTransform:t withIndex:4]];
    
    //第五面
    //-y方向平移
    t = CATransform3DMakeTranslation(0, -50, 0);
    //x轴旋转90度
    t = CATransform3DRotate(t, 90 * (M_PI / 180), 1, 0, 0);
    [cube addSublayer:[self diceFaceWithTransform:t withIndex:5]];
    
    //第六面
    //-z方向平移
    t = CATransform3DMakeTranslation(0, 0, -50);
    t = CATransform3DRotate(t, 180 * (M_PI / 180), 1, 0, 0);
    [cube addSublayer:[self diceFaceWithTransform:t withIndex:6]];
    //旋转30度
//    cube.transform = CATransform3DMakeRotation(30 * (M_PI / 180), 1, 1, 1);
    
    //设置中心点的位置
    cube.position = CGPointMake(200, 200);
    [self.view.layer addSublayer:cube];
    self.cube = cube;
//    CATransform3D transA = CATransform3DMakeScale(1.0, 1.0, 1.0);
//    transA = CATransform3DRotate(transA, 180 * (M_PI / 180), 1, 1, 1);
//    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
//    animation.duration          = 2;
//    animation.autoreverses      = YES;
//    animation.repeatCount       = 100;
//    animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 0.5)];
//    animation.toValue           = [NSValue valueWithCATransform3D:transA];
//    [cube addAnimation:animation forKey:nil];
}
-(CALayer *)diceFaceWithTransform:(CATransform3D)transform withIndex:(NSInteger)index{
    CATextLayer *face = [CATextLayer layer];
    face.bounds = CGRectMake(0, 0, 100, 100);
    face.string = [NSString stringWithFormat:@"第%zd面",index];
    face.fontSize = 20;
    face.contentsScale = 2;
    face.alignmentMode = @"center";
    face.truncationMode = @"middle";
    face.transform = transform;
    CGFloat red = (rand() / (double)INT_MAX);
    CGFloat green = (rand() / (double)INT_MAX);
    CGFloat blue = (rand() / (double)INT_MAX);
    face.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    return face;
}

注:这块有个细节就是每个面都有字,如果没有考虑旋转方向的时候有的面的字是反的,所以添加每一个面的时候要考虑这个面的旋转方向(字始终在你创建该面的上方)

添加拖动手势的代码

- (void)pan:(UIPanGestureRecognizer *)recognizer{
    CGFloat w = [UIScreen mainScreen].bounds.size.width;
    CGFloat h = [UIScreen mainScreen].bounds.size.height;
    //获取到的是手指移动后,在相对坐标中的偏移量(以手指接触屏幕的第一个点为坐标原点)
    CGPoint translation = [recognizer translationInView:self.view];
    NSLog(@"x = %f ------ y = %f",translation.x, translation.y);
    CATransform3D transform = CATransform3DIdentity;
    transform = CATransform3DRotate(transform, translation.x * (M_PI * 2 / w), 0, 1, 0);
    transform = CATransform3DRotate(transform, translation.y * (-M_PI * 2 / h), 1, 0, 0);
    self.cube.transform = transform;
}

总结

总的来说,要做出比较炫的动效,需要熟悉其中动画的基本知识和运作原理。每一个小的细节都会导致效果的不同,我也是一个新手,大学里学的线性代数对于矩阵的一些简概念和简单操作,现在全忘完了,看到变换里有矩阵相关的一些东西,一脸懵逼,最后查了一些资料才勉强看懂,看来以后得多看看以前学过的数学知识。
非常感谢大家!希望大家共同进步!多提宝贵意见!
最后附上项目代码
CATransform3DDemo

你可能感兴趣的:(CATransform3D基本动画)