最近有看到一些在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;
}];
}
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;
}];
}
综合案例
通过上述三种动画方式我们做一个可以旋转的立方体。
具体思路:我们需要通过CATransformLayer创建一个容器,然后通过CALayer创建六个页面,通过平移旋转的方式将它们组合到一起放到容器里,通过给立方体添加手势实现旋转,具体的效果如下:
具体代码如下:
-(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