CALayer透视投影修改
动画案例准备工作:新建空工程CoreAnimation-Transform
,在Main.storyboard
文件中拖入一个UIImageView
,添加图片资源进行配置并关连,命名为layerView
案例一:下面实现一张图片
围绕一个Y轴
旋转
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *layerView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// CATransform3D矩阵可以是一个旋转矩阵、平移矩阵、缩放矩阵、投影矩阵
// 参数一:围绕角度
// 参数二:围绕x轴
// 参数三:围绕y轴
// 参数四:围绕z轴
CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.layerView.layer.transform = transform;
}
@end
运行工程,我们查看图片发现并没有旋转效果
,图片甚至发生了变形。
案例二:修改代码围绕Z轴旋转并查看效果
CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
通过对比发现,围绕Z轴
可以发生旋转,而围绕Y轴
我们并没有查看出旋转效果。是什么原因造成图片没有办法围绕Y轴旋转呢
?
原因是在投影的时候,图片是平面的;对Y轴
进行投影,我们没有考虑投影方式,如果我们要想呈现立体效果,我们就要设置透视投影
的方式。
案例三:设置透视投影方式
- (void)viewDidLoad {
[super viewDidLoad];
//投影方式: 2种.正投影,透视投影;
//CATransform3DIdentity表示单元矩阵
CATransform3D transform1 = CATransform3DIdentity;
//m34表示透视投影,500表示投影的距离,一般为500/1000
transform1.m34 = -1.0/500.0;
/*
CATransform3D CATransform3DRotate (CATransform3D t, CGFloat angle,
CGFloat x, CGFloat y, CGFloat z)
参数1: t,矩阵CATransform3D对象
参数2: 角度(弧度为单位,如果是度数需要做转化)
参数3: x, (1=旋转,0=不旋转)
参数4: y,
参数5: z,
*/
transform1 = CATransform3DRotate(transform1, M_PI_4, 0, 1, 0);
self.layerView.layer.transform = transform1;
}
我们在做3D旋转
的时候,如果针对的是一个平面图形,我们要想呈现立体效果
,就要设置透视投影
的属性。
注意:设置投影方式跟围绕哪个轴旋转并没有关系
,只是让图形呈现出立体效果
;如果对图形平移并且想要呈现立体效果,也需要设置投影方式。
灭点
:在透视投影中,一束平行于投影面的平行线的投影可以保持平行,而不平行于投影面的平行线的投影会聚集到一个点,这个点称为灭点;灭点可以看作是无限远处的一点在投影面上的投影
。
案例四:两张图片都呈现透视投影
的方式(同时可以引申为多张图片)
Main.storyboard
文件中拖入一个UIView
并命名为containerView
,在containerView
上分别拖入两个UIImageView
并命名为layerView1
、layerView2
代码如下
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (weak, nonatomic) IBOutlet UIImageView *layerView1;
@property (weak, nonatomic) IBOutlet UIImageView *layerView2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0/500;
//sublayerTransform 子图层上仿射变换(会影响添加在此图层上的子图层)
self.containerView.layer.sublayerTransform = perspective;
CATransform3D transform2 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
CATransform3D transform3 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
self.layerView1.layer.transform = transform2;
self.layerView2.layer.transform = transform3;
}
@end
运行工程,两张图片都发生了旋转
,效果图如下
案例五:图片旋转180度
,查看图片背面效果
- (void)viewDidLoad {
[super viewDidLoad];
CATransform3D transform1 = CATransform3DIdentity;
transform1.m34 = -1.0/500.0;
transform1 = CATransform3DRotate(transform1, M_PI, 0, 1, 0);
self.layerView.layer.transform = transform1;
}
运行工程,我们发现可以看到图片背面的效果
,原因是图层默认开启正背面渲染
。
举例:如果想要呈现一个正方体
的立体效果,默认情况下最多可以看到三个面,剩下看不到的三个面是不需要画出来的,OpenGL ES
不会去画看不到的三个面,这样的话性能就会提高50%
。所以这里我们可以根据业务场景关闭图层的正背面渲染属性
,以提高渲染性能。
案例六:关闭图层的正背面渲染
- (void)viewDidLoad {
[super viewDidLoad];
CATransform3D transform1 = CATransform3DIdentity;
transform1.m34 = -1.0/500.0;
transform1 = CATransform3DRotate(transform1, M_PI, 0, 1, 0);
self.layerView.layer.transform = transform1;
//正背面渲染--> 渲染技术正背面剔除,判断用户可见/不可见
self.layerView.layer.doubleSided = NO;
}
再次运行工程,我们就看不到图片的背面效果。
透视投影实现立体盒子
动画案例准备工作:新建空工程CoreAnimation5
,在Main.storyboard
文件中拖入两个UIView
,其中一个view
是另一个view
的子视图;父视图view
命名为outerView
,背景色设置为红色,子视图view
命名为innerView
,背景色设置为蓝色。
案例七:父视图围绕Z轴
顺时针旋转45度
,子视图围绕Z轴
逆时针旋转45度
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *outerView;
@property (weak, nonatomic) IBOutlet UIView *innerView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CATransform3D outer = CATransform3DIdentity;
//透视投影
outer.m34 = -1.0/500;
//旋转变换,围绕Z轴.45度
outer = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
self.outerView.layer.transform = outer;
CATransform3D inner = CATransform3DIdentity;
inner.m34 = -1.0/500;
inner = CATransform3DMakeRotation(-M_PI_4, 0, 0, 1);
self.innerView.layer.transform = inner;
}
@end
疑问:为什么没有达到我们预期的效果呢?
因为手机屏幕是平面的,红色view
往内旋转,蓝色view
往外旋转,旋转的轴就会相悖,这时候就需要一个立体空间
来解决;如果要想实现预期效果,就要使用3D图层
;即CALayer
无法实现,需要专门的3D图层CATransformLayer
来实现。
案例八:使用CATransformLayer
图层实现立体效果
新建带xib
文件的CCViewController
页面,并在CCViewController
页面的View
上拖入一个UIView
命名为containerView
,背景色设置为黑色
,然后再分别拖入6个UIView
,分别命名为view0
、view1
、view2
、view3
、view4
、view5
如下图所示
#import "CCViewController.h"
@interface CCViewController ()
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) IBOutlet UIView *view0;
@property (strong, nonatomic) IBOutlet UIView *view1;
@property (strong, nonatomic) IBOutlet UIView *view2;
@property (strong, nonatomic) IBOutlet UIView *view3;
@property (strong, nonatomic) IBOutlet UIView *view4;
@property (strong, nonatomic) IBOutlet UIView *view5;
@property(nonatomic,strong)NSArray *faces;
@end
@implementation CCViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.faces = @[_view0,_view1,_view2,_view3,_view4,_view5];
//我们不用修改子视图,只修改父视图
//父View的layer图层
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0 / 500.0;
//父视图旋转之后查看立体效果,沿X轴、Y轴分别旋转一次就能看到正方体的三个面
perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);
perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);
self.containerView.layer.sublayerTransform = perspective;
//add cube face 1
//Z轴平移100
CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);
[self addFace:0 withTransform:transform];
//add cube face 2
transform = CATransform3DMakeTranslation(100, 0, 0);
transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
[self addFace:1 withTransform:transform];
//add cube face 3
transform = CATransform3DMakeTranslation(0, -100, 0);
transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
[self addFace:2 withTransform:transform];
//add cube face 4
transform = CATransform3DMakeTranslation(0, 100, 0);
transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);
[self addFace:3 withTransform:transform];
//add cube face 5
transform = CATransform3DMakeTranslation(-100, 0, 0);
transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
[self addFace:4 withTransform:transform];
//add cube face 6
transform = CATransform3DMakeTranslation(0, 0, -100);
transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);
[self addFace:5 withTransform:transform];
}
- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform
{
//获取face视图并将其添加到容器中
UIView *face = self.faces[index];
[self.containerView addSubview:face];
//将face视图放在容器的中心
CGSize containerSize = self.containerView.bounds.size;
face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
//添加transform
face.layer.transform = transform;
}
@end
ViewController
页面添加跳入CCViewController
页面的逻辑,运行工程查看立体效果
父视图containerView
本身的transform
并没有发生变化,只是一个平面,只是其子视图发生了透视投影
,让其子视图发生位置与角度的变化。
CAShaperLayer
CoreAnimation
专用图层
CAShaperLayer
CAShaperLayer
是⼀个通过⽮量图形指定诸如颜⾊和线宽等属性,⽤CGPath
来定义想要绘制的图形,最后CAShaperLayer
就能渲染出现
-
渲染快速
。CAShapeLayer
使⽤了硬件加速器,绘制同⼀图形会⽐用Core Graphics
快很多。 -
⾼效使用内存
。⼀个CAShapeLayer
不需要像普通CALayer
一样创建⼀个寄宿图形,所以⽆论有多大,都不会占⽤用太多的内存。 -
不会被图层边界剪裁掉
。⼀个CAShapeLayer
可以在边界之外绘制。你的图层路径不会像在使用Core Graphics
的普通CALayer
一样被剪裁掉。 -
不会出现像素化
。当你给CAShapeLayer
做3D变换时,它不像⼀个有寄宿图的普通图层一样变得像素化。
案例九:使用CAShaperLayer
绘制火柴人
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(175, 100)];
//绘制一个圆形
[path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES];
[path moveToPoint:CGPointMake(150, 125)];
[path addLineToPoint:CGPointMake(150, 175)];
[path addLineToPoint:CGPointMake(125, 225)];
[path moveToPoint:CGPointMake(150, 175)];
[path addLineToPoint:CGPointMake(175, 225)];
[path moveToPoint:CGPointMake(100, 150)];
[path addLineToPoint:CGPointMake(200, 150)];
//create shape layer
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
//stroke color 画笔颜色
shapeLayer.strokeColor = [UIColor redColor].CGColor;
//fill color 填充颜色
shapeLayer.fillColor = [UIColor blueColor].CGColor;
//line width 线段宽度
shapeLayer.lineWidth = 5;
//形状路径连线样式
shapeLayer.lineJoin = kCALineJoinRound;
//形状路径线帽样式
shapeLayer.lineCap = kCALineCapRound;
//shaperLayer 绘制图形路径
shapeLayer.path = path.CGPath;
//add it to our view
[self.view.layer addSublayer:shapeLayer];
}
@end
使用CAShaperLayer
绘制圆角,也可以实现部分圆角
- (void)viewDidLoad {
[super viewDidLoad];
//define path parameters
CGRect rect = CGRectMake(50, 50, 100, 100);
CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners1 = UIRectCornerBottomRight | UIRectCornerBottomLeft;
UIRectCorner corners2 = UIRectCornerTopLeft | UIRectCornerTopRight;
//create path
UIBezierPath *path2 = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners2 cornerRadii:radii];
//create shape layer
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
//stroke color 画笔颜色
shapeLayer.strokeColor = [UIColor redColor].CGColor;
//fill color 填充颜色
shapeLayer.fillColor = [UIColor blueColor].CGColor;
//line width 线段宽度
shapeLayer.lineWidth = 5;
//形状路径连线样式
shapeLayer.lineJoin = kCALineJoinRound;
//形状路径线帽样式
shapeLayer.lineCap = kCALineCapRound;
//shaperLayer 绘制图形路径
shapeLayer.path = path2.CGPath;
//add it to our view
[self.view.layer addSublayer:shapeLayer];
}
CATextLayer图层讲解
如果你想在一个图层⾥面显示⽂字,完全可以借助图层代替字符串,可以使⽤CATextLayer
将内容写入图层;UILabel
的原理是通过Core Graphics
写入内容,CATextLayer
几乎包含了所有UILabel
的绘制特性,而且比UILabel
渲染要快很多。
案例:使用CATextLayer
来渲染文字
- (void)viewDidLoad {
[super viewDidLoad];
//create a text layer
CATextLayer *textLayer = [CATextLayer layer];
textLayer.frame = CGRectMake(10,100 , self.view.frame.size.width -20, 200);
[self.view.layer addSublayer:textLayer];
//set text attributes(设置text的属性)
//字体颜色
textLayer.foregroundColor = [UIColor blackColor].CGColor;
//对齐方式
textLayer.alignmentMode = kCAAlignmentCenter;
//环绕在边界范围内
textLayer.wrapped = YES;
//choose a font (选择字体)
UIFont *font = [UIFont systemFontOfSize:15];
//set layer font (设置图层字体)
//将font改成图层字体
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
textLayer.font = fontRef;
textLayer.fontSize = font.pointSize;
//模糊的原因是,没有以Retina来渲染,contentsScale默认值为1,所以需要传合适的值
textLayer.contentsScale = [UIScreen mainScreen].scale;
//fontRef使用完要记着释放
CGFontRelease(fontRef);
//choose some text 文字信息
NSString *text = @"Hello World";
//set layer text 设置layer的文字信息
textLayer.string = text;
}
CATextLayer
其实比UILabel
更加好用,而且能够支持富文本
。
案例:下面我们使用CATextLayer
来自定义UILabel
<!-- LayerLabel.h文件 -->
#import <UIKit/UIKit.h>
@interface LayerLabel : UILabel
@end
<!-- LayerLabel.m文件 -->
#import "LayerLabel.h"
@implementation LayerLabel
+ (Class)layerClass
{
return [CATextLayer class];
}
- (CATextLayer *)textLayer
{
return (CATextLayer *)self.layer;
}
- (void)setUp
{
//set defaults from UILabel settings
self.text = self.text;
self.textColor = self.textColor;
self.font = self.font;
[self textLayer].alignmentMode = kCAAlignmentJustified;
[self textLayer].wrapped = YES;
[self.layer display];
}
- (id)initWithFrame:(CGRect)frame
{
//called when creating label programmatically
if (self = [super initWithFrame:frame]) {
[self setUp];
}
return self;
}
- (void)awakeFromNib
{
//called when creating label using Interface Builder
[self setUp];
}
- (void)setText:(NSString *)text
{
super.text = text;
//set layer text
[self textLayer].string = text;
}
- (void)setTextColor:(UIColor *)textColor
{
super.textColor = textColor;
//set layer text color
[self textLayer].foregroundColor = textColor.CGColor;
}
- (void)setFont:(UIFont *)font
{
super.font = font;
//set layer font
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
[self textLayer].font = fontRef;
[self textLayer].fontSize = font.pointSize;
CGFontRelease(fontRef);
}
@end
Main.storyboard
拖入LayerLabel
控件,在ViewController.m
文件的viewDidLoad
方法中设置layerLabel
的text
值。
@interface ViewController ()
@property (weak, nonatomic) IBOutlet LayerLabel *LLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.LLabel.text = @"Hello I'm LayerLabel";
}
@end
LayerLabel
的原理是替换UILabel
的图层,其性能比原来的UILabel
更好,渲染更快。
CATransformLayer图层讲解
CATransformLayer
是专⻔用来创建三维视图的,上面我们使用的layer.sublayerTransform
实现的立体效果,下面我们使用CATransformLayer
来实现;OpenGL ES
也可以实现,只是会很麻烦。
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *containerView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
//set up the perspective transform(设置投影矩阵)
//CATransformLayer的使用也是需要设置透视投影的
//如果想要有一个立体的投影效果,必须要设置投影方式,否则没有立体效果
CATransform3D pt = CATransform3DIdentity;
pt.m34 = -1.0 / 500.0;
//设置子图层是有透视效果的
self.containerView.layer.sublayerTransform = pt;
//set up the transform for cube 1 and add it
CATransform3D c1t = CATransform3DIdentity;
c1t = CATransform3DTranslate(c1t, -100, 0, 0);
CALayer *cube1 = [self cubeWithTransform:c1t];
[self.containerView.layer addSublayer:cube1];
//set up the transform for cube 2 and add it
CATransform3D c2t = CATransform3DIdentity;
c2t = CATransform3DTranslate(c2t, 100, 0, 0);
c2t = CATransform3DRotate(c2t, -M_PI_4, 1, 0, 0);
c2t = CATransform3DRotate(c2t, -M_PI_4, 0, 1, 0);
CALayer *cube2 = [self cubeWithTransform:c2t];
[self.containerView.layer addSublayer:cube2];
}
- (CALayer *)faceWithTransform:(CATransform3D)transform
{
//create cube face layer
CALayer *face = [CALayer layer];
face.frame = CGRectMake(-50, -50, 100, 100);
//apply a random color
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;
face.transform = transform;
return face;
}
- (CALayer *)cubeWithTransform:(CATransform3D)transform
{
//create cube layer
CATransformLayer *cube = [CATransformLayer layer];
//add cube face 1
CATransform3D ct = CATransform3DMakeTranslation(0, 0, 50);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 2
ct = CATransform3DMakeTranslation(50, 0, 0);
ct = CATransform3DRotate(ct, M_PI_2, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 3
ct = CATransform3DMakeTranslation(0, -50, 0);
ct = CATransform3DRotate(ct, M_PI_2, 1, 0, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 4
ct = CATransform3DMakeTranslation(0, 50, 0);
ct = CATransform3DRotate(ct, -M_PI_2, 1, 0, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 5
ct = CATransform3DMakeTranslation(-50, 0, 0);
ct = CATransform3DRotate(ct, -M_PI_2, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//add cube face 6
ct = CATransform3DMakeTranslation(0, 0, -50);
ct = CATransform3DRotate(ct, M_PI, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
//center the cube layer within the container(将立方体层至于容器中心)
CGSize containerSize = self.containerView.bounds.size;
cube.position = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
//apply the transform and return
cube.transform = transform;
return cube;
}
@end
CAGradientLayer图层讲解
CAGradientLayer
是⽤来⽣成两种或更多种颜⾊平滑渐变,下面使用CAGradientLayer
来实现一个渐变色效果:
- (void)viewDidLoad {
[super viewDidLoad];
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = CGRectMake(10, 50, self.view.frame.size.width - 20, 100);
// locations数量要与colors一致,设置颜色位置
gradientLayer.locations = @[@0.25,@0.5,@0.25];
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor blueColor].CGColor,(__bridge id)[UIColor purpleColor].CGColor];
//startPoint,endPoint 决定渐变方向,默认左上角(0, 0) 右下角坐标(1, 1)
//左上角的位置
gradientLayer.startPoint = CGPointMake(0, 0);
//右下角的位置
gradientLayer.endPoint = CGPointMake(1, 1);
[self.view.layer addSublayer:gradientLayer];
}
课后练习:尝试实现一个环形的渐变效果?
CARelicatorLayer图层讲解
CAReplicatorLayer
的目的就是为了高效⽣成许多相似的图层,他会绘制一个或者多个图层的子图层,并在每个复制体上应⽤不同的变换。
下面我们使用CAReplicatorLayer
来生成一个图层的倒影
<!-- ReflectionView.h文件 -->
#import <UIKit/UIKit.h>
@interface ReflectionView : UIView
@end
<!-- ReflectionView.m文件 -->
#import "ReflectionView.h"
@implementation ReflectionView
+ (Class)layerClass
{
return [CAReplicatorLayer class];
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setUp];
}
return self;
}
- (void)awakeFromNib
{
[self setUp];
}
-(void)setUp
{
CAReplicatorLayer *layer = (CAReplicatorLayer *)self.layer;
// 复制数量为2
layer.instanceCount = 2;
CATransform3D transform = CATransform3DIdentity;
//间隔,创建的两个图层之间的间隔
CGFloat veticalOffset = self.bounds.size.height + 2;
//平移的距离为veticalOffset
transform = CATransform3DTranslate(transform, 0, veticalOffset, 0);
//翻转
transform = CATransform3DScale(transform, -1, -1, 0);
layer.instanceTransform = transform;
//K-0.7= 0.3,让layer图层的透明度为0.3
// 这里如果设置值为0.3,透明度就相当于是 1 + 0.3 = 1.3
layer.instanceAlphaOffset = -0.7;
}
@end
Main.storyboard
文件中拖入一个ReflectionView
图层,并在ReflectionView
上面添加一个UIImageView
且设置好图片,运行工程查看效果如下