iOS核心动画-CALayer常用方法
一、content属性的运用
1、通过content属性展示图片
代码:
CALayer *layer = [CALayer layer];
layer.backgroundColor = [UIColor clearColor].CGColor;
UIImage *image = [UIImage imageNamed:@"AppIcon120x120"];
// contents:需要用__bridge id桥接一下MAC OS遗留的问题
layer.contents = (__bridge id)image.CGImage;
layer.frame = CGRectMake(20, 20, 100, 100);
[self.subView.layer addSublayer:layer];
这样就能通过UIView的layer来展示image,而不用再创建一个uIImageView去展示图片了
contentsGravity
layer有个属性contentsGravity和UIView的contentModel类似,是为了设置contents拉伸效果准备的
kCAGravityCenter:按照图片像素展示,图片中心点和layer中心点相同
kCAGravityTop:按照图片像素展示,图片x轴中心与layer相同,底部和layer底部对齐
kCAGravityBottom:按照图片像素展示,图片x轴中心与layer相同,顶部和layer顶部对齐
kCAGravityLeft:按照图片像素展示,图片y轴中心与layer相同,左边和layer左边对齐
kCAGravityRight:按照图片像素展示,图片y轴中心与layer相同,右边和layer右边对齐
kCAGravityTopLeft:按照图片像素展示,图片左下角和layer左下角对齐
kCAGravityTopRight:按照图片像素展示,图片右下角和layer右下角对齐
kCAGravityBottomLeft:按照图片像素展示,图片左上角和layer左上角对齐
kCAGravityBottomRight:按照图片像素展示,图片右上角和layer右上角对齐
kCAGravityResize:填充满layer,拉伸图片
kCAGravityResizeAspect:按照layer的最短边充满layer
kCAGravityResizeAspectFill:按照layer的最长边充满layer,不拉伸图片
contentsScale
该属性定义了寄宿图的像素尺寸和视图大小的比例,默认情况下它 是一个值为1.0的浮点数
-
设置为1.0,将会以每个点1个像素绘制图片
-
如果设置为 2.0,则会以每个点2个像素绘制图片,这就是我们熟知的Retina屏幕
当contentsGravity 等于后面三个值时候,设置该属性没有任何效果, 因为kCAGravityResizeAspect就是拉伸 图片以适应图层而已,根本不会考虑到分辨率问题。
但是如果我们把 contentsGravity 设置为kCAGravityCenter这种不会拉伸图片的值的话,图片展示的大小就是图片像素除以contentsScale的大小
当用contents的方式来处理寄宿图的时候,一定要记住要手动的设置图层的 contentsScale 属性,否则,你的图片在Retina设备上就显示得不正确,需要加上下面这一句
layer.contentsScale = [UIScreen mainScreen].scale;
contentsRect
属性允许我们在图层边框里显示寄宿图的一个子域,比contentsGravity属性更灵活,但是里面值是按照单位坐标来计算的,它使用了单位坐标,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)。所以他们是相对与寄宿图的尺寸的
默认的 contentsRect 是{0, 0, 1, 1},这意味着整个寄宿图默认都是可见的,如果 我们指定一个小一点的矩形{0, 0, 0.5, 0.5},图片就会被裁剪。
事实上给contentsRect 设置一个负数的原点或是大于{1, 1}的尺寸也是可以的。这种情况下,最外面的像素会被拉伸以填充剩下的区域。
正常情况下:
image.pngself.layer.contentsRect = CGRectMake(0, 0, 2, 2);情况下
image.pngself.layer.contentsRect = CGRectMake(0, 0, 0.5, 0.5);情况下
image.png
self.layer.contentsRect = CGRectMake(0.5, 0.5, 1, 1);情况下
image.png
所以设置contentsRect不会对layer的frame造成影响,影响的至少contents里面的图片的哪部分在layer的视口中
CALayerDelegate
layer也有自己的代理CALayerDelegate
self.layer.delegate = self;
当设置好代理后,通过代码
[self.layer display];
就可以调用下面方法, 如果代理不实现 -displayLayer:方法,系统就会调drawLayer:inContext方法,两个方法只会回调一个
-(void)displayLayer:(CALayer *)layer{
static int i = 0;
layer.frame = CGRectMake(0, 0, layer.frame.size.width + 10, layer.frame.size.height);
NSLog(@"%@ %@",layer,self.layer);
if (i == 0) {
layer.backgroundColor = [UIColor yellowColor].CGColor;
}else{
layer.backgroundColor = [UIColor orangeColor].CGColor;
}
i++;
}
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
//draw a thick red circle
CGContextSetLineWidth(ctx, 10.0f);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextStrokeEllipseInRect(ctx, layer.bounds);
}
borderColor
-(void)testBorder{
// self.layer.cornerRadius = 10;
// //如果把masksToBounds设置成YES的话,图层里面的所有东西都会被截取
// self.layer.masksToBounds = YES;
self.layer.borderColor = [UIColor orangeColor].CGColor;
self.layer.borderWidth = 5;
}
阴影shadow
/*
阴影通常就是在 Layer的边界之外,如果你开启了 masksToBounds属性,
所有从图层中突出来的 内容都会被才剪掉
如果你想沿着内容裁切,你需要用到两个图层:一个只画阴影的空的外图层,和一个用masksToBounds 裁剪内容的内图层。
我们只把阴影用在最外层的视图上,内层视图进行裁剪。
*/
-(void)testShadow{
CALayer *layer = [CALayer layer];
layer.backgroundColor = [UIColor clearColor].CGColor;
layer.frame = self.layer.bounds;
UIImage *image = [UIImage imageNamed:@"AppIcon120x120"];
layer.contents = (__bridge id)image.CGImage;
[self.layer addSublayer:layer];
layer.cornerRadius = 10;
layer.masksToBounds = YES;
/* 给shadowOpacity 属性一个大于默认值(也就是0)的值,阴影就可以显示在任意图层之下
shadowOpacity是一个必须在0.0(不可见)和1.0(完全不透 明)之间的浮点数。
如果设置为1.0,将会显示一个有轻微模糊的黑色阴影稍微在图层之上
如果设置为小于1,那么阴影会比较浅
*/
self.layer.shadowOpacity = 1;
//阴影颜色
self.layer.shadowColor = [UIColor yellowColor].CGColor;
/*
宽度控制这阴影横向的位移,高度控制着纵向的位移。
shadowOffset 的默认值是 {0, -3},意即阴影相对于Y轴有3个点的向上位移。
当设置为{0,0}时候layer的四种会均匀的显示出阴影
*/
self.layer.shadowOffset = CGSizeMake(10, 10);
/*
属性控制着阴影的模糊度,当它的值是0的时候,阴影就和视图 一样有一个非常确定的边界线。
当值越来越大的时候,边界线看上去就会越来越模 糊和自然
*/
self.layer.shadowRadius = 10;
}
阴影路径 shadowPath
/*
我们已经知道图层阴影并不总是方的,而是从图层内容的形状继承而来。这看上
去不错,但是实时计算阴影也是一个非常消耗资源的,尤其是图层有多个子图层,
每个图层还有一个有透明效果的寄宿图的时候。
如果你事先知道你的阴影形状会是什么样子的,你可以通过指定一个shadowPath 来提高性能。
shadowPath 是一个 CGPathRef 类型(一个指向CGPath 的指针)。
CGPath 是一个Core Graphics对象,用来指定任意的一个 矢量图形。
我们可以通过这个属性单独于图层形状之外指定阴影的形状
如果是一个矩形或者是圆,用 CGPath会相当简单明了。
但是如果是更加复杂一 点的图形,
UIBezierPath类会更合适,它是一个由UIKit提供的在CGPath基础上 的Objective-C包装类。
*/
-(void)testShowPath{
//enable layer shadows
self.layer.shadowOpacity = 0.5f;
self.layer.shadowOpacity = 0.5f;
//创建一个方形阴影
CGMutablePathRef squarePath = CGPathCreateMutable();
CGPathAddRect(squarePath, NULL, CGRectMake(-25, -25, self.layer.frame.size.width + 50, self.layer.frame.size.height + 50));
self.layer.shadowPath = squarePath;
CGPathRelease(squarePath);
//创建一个圆形阴影
// CGMutablePathRef circlePath = CGPathCreateMutable();
// CGPathAddEllipseInRect(circlePath, NULL, CGRectMake(-25, -25, self.layer.frame.size.width + 50, self.layer.frame.size.height + 50));
// self.layer.shadowPath = circlePath;
// CGPathRelease(circlePath);
}
蒙层- masks
/*
CALayer有一个属性叫做 mask可以解决这个问题。这个属性本身就是个 CALayer类型,有和其他图层一样的绘制和布局属性。它类似于一个子图层,相对于父图层(即拥有该属性的图层)布局,但是它却不是一个普通的子图层。不同于那些绘制在父图层中的子图层,mask 图层定义了父图层的部分可见区域。
mask图层的 Color属性是无关紧要的,真正重要的是图层的轮廓。
mask 属 性就像是一个饼干切割机, mask 图层实心的部分会被保留下来,其他的则会被抛 弃。
如果mask图层比父图层要小,只有在mask图层里面的内容才是它关心的,除此以外的一切都会被隐藏起来
CALayer蒙板图层真正厉害的地方在于蒙板图不局限于静态图。
任何有图层构成的都可以作为mask属性,
这意味着你的蒙板可以通过代码甚至是动画实时生成
*/
-(void)testMasks{
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = CGRectMake(10, 10, self.layer.frame.size.width+20, self.layer.frame.size.height+20);
maskLayer.backgroundColor = [UIColor yellowColor].CGColor;
// UIImage *maskImage = [UIImage imageNamed:@"AppIcon120x120"];
// maskLayer.contents = (__bridge id)maskImage.CGImage;
//apply mask to image layer
self.subView.layer.mask = maskLayer;
}
zPosition
-(void)testZPosition{
CALayer *layer1 = [CALayer layer];
layer1.backgroundColor = [UIColor redColor].CGColor;
layer1.frame = CGRectMake(20, 20, 100, 100);
[self.subView.layer addSublayer:layer1];
self.layer1 = layer1;
CALayer *layer2 = [CALayer layer];
layer2.backgroundColor = [UIColor yellowColor].CGColor;
layer2.frame = CGRectMake(40, 40, 100, 100);
[self.subView.layer addSublayer:layer2];
self.layer2 = layer2;
/*
正常情况下layer1在layer2的下面,但是当把layer1的zPosition设置为1的时候。layer1就在layer2的上面
zPosition代表Y轴的坐标,默认是0,值越大代表越在上面
这个值跟深度测试相关
*/
layer2.zPosition = 2;
layer1.zPosition = 1;
}
hitTest
CALayer并不关心任何响应链事件,所以不能直接处理触摸事件或者手势。但是 它有一系列的方法帮你处理事件: -containsPoint:和-hitTest: 。
-containsPoint:接受一个在本图层坐标系下的CGPoint ,如果这个点在图层frame范围内就返回 YES。
-hitTest: 方法同样接受一个 CGPoint类型参数,而不是BOOL 类型,它返回图层本身,或者包含这个坐标点的叶子节点图层。这意味着不再需要像使用 - containsPoint: 那样,人工地在每个子图层变换或者测试点击的坐标。如果这个点在最外面图层的范围之外,则返回nil。具体使用 -hitTest: 方法被点击图层的 代码如所示
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//get touch position
CGPoint point = [[touches anyObject] locationInView:self.view];
//get touched layer
CALayer *layer = [self.layerView.layer hitTest:point];
//get layer using hitTest
if (layer == self.blueLayer) {
[[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer"
message:nil
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
} else if (layer == self.layerView.layer) {
[[[UIAlertView alloc] initWithTitle:@"Inside White Layer"
message:nil
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
} }
另外在UIView中有这个方法:
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
当用户点击到view时候,我们可以在这个方法里面做事件的转发
/*
point:在view的局部坐标系中的点
event:系统保证调用此方法的事件
return:如果此视图包含这个点,则返回自己,如果处理不了就会返回父view,如果返回nil则把事件丢弃
hit test调用顺序:
touch --- UIApplication UIWindow UIViewController UIView subView...合适的view
事件的传递顺序刚好与之相反:
常见UIView不响应事件的处理有哪些:
1、隐藏
2、userInteractionEnabled=NO
3、透明度< 0.05
4、view超过父view
*/
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
if(self.userInteractionEnabled == NO||
self.alpha < 0.05||
self.hidden == YES){
return nil;
}
//如果点击地方在self的bounds内
if([self pointInside:point withEvent:event]){
for(UIView *subView in self.subviews){
//进行坐标转换
CGPoint coverPoint = [subView convertPoint:point toView:self];
//调用子视hitTest 图的hitTest重复上面步骤,找到了,返回hitTest view,没找到返回自身处理
UIView *hitTestView = [subView hitTest:coverPoint withEvent:event];
if(hitTestView){
return hitTestView;
}
}
return nil;
}
return self;
}