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;
    }];
}
原始状态.png

x轴旋转.png

y轴旋转.png

z轴旋转.png

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;
    }];
}
缩小0.5.png

放大1.5.png

综合案例

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


效果.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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • >*很不幸,没人能告诉你母体是什么,你只能自己体会* --骇客帝国 在第四章“可视效果”中,我们研究了一些增强图层...
    夜空下最亮的亮点阅读 1,645评论 0 2
  • Core Animation其实是一个令人误解的命名。你可能认为它只是用来做动画的,但实际上它是从一个叫做Laye...
    小猫仔阅读 3,684评论 1 4
  • 在第四章“视觉效果”中,我们研究了一些增强图层和它的内容显示效果的一些技术,在这一章中,我们将要研究可以用来对图层...
    乐意先生阅读 1,765评论 0 2
  • 图层的几个坐标系 对于iOS来说,坐标系的(0,0)点在左上角,就是越往下,Y值越大。越往右,X值越大。 一个图层...
    LeeMystique阅读 16,672评论 1 53
  • 那时的自己,刚刚毕业,在激烈的厮杀中获得了一份急速锻炼自己能力的工作,经几年奋斗,获得被HR推荐提拔为总助。那段时...
    邊思文阅读 298评论 2 2