美文网首页绘制
iOS核心动画-CALayer常用方法

iOS核心动画-CALayer常用方法

作者: 海浪萌物 | 来源:发表于2020-08-09 11:33 被阅读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的浮点数

    • 设置为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.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;
    }
    

    相关文章

      网友评论

        本文标题:iOS核心动画-CALayer常用方法

        本文链接:https://www.haomeiwen.com/subject/azhldktx.html