绘制

iOS核心动画-CALayer常用方法

2020-08-09  本文已影响0人  海浪萌物

一、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的浮点数

     layer.contentsScale = [UIScreen mainScreen].scale;

contentsRect

属性允许我们在图层边框里显示寄宿图的一个子域,比contentsGravity属性更灵活,但是里面值是按照单位坐标来计算的,它使用了单位坐标,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)。所以他们是相对与寄宿图的尺寸的

默认的 contentsRect 是{0, 0, 1, 1},这意味着整个寄宿图默认都是可见的,如果 我们指定一个小一点的矩形{0, 0, 0.5, 0.5},图片就会被裁剪。

事实上给contentsRect 设置一个负数的原点或是大于{1, 1}的尺寸也是可以的。这种情况下,最外面的像素会被拉伸以填充剩下的区域。

正常情况下:

image.png

self.layer.contentsRect = CGRectMake(0, 0, 2, 2);情况下

image.png

self.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;
}
上一篇下一篇

猜你喜欢

热点阅读