• iOS核心动画高级技巧之CALayer(一)


    iOS核心动画高级技巧之CALayer(一)

    iOS核心动画高级技巧之图层变换和专用图层(二)
    iOS核心动画高级技巧之核心动画(三)
    iOS核心动画高级技巧之性能(四)
    iOS核心动画高级技巧之动画总结(五)

      UIView和CALayer的关系
      在iOS中一个UIView对应着一个CALayer,视图的职责就是创建并管理这个图层,以确保当子视图在层级关系中添加或者被移除的时候,他们关联的图层也同样对应在层级关系树当中有相同的操作.实际上这些背后关联的图层才是真正用来在屏幕上显示和做动画,UIView仅仅是对它的一个封装,提供了一些iOS类似于处理触摸的具体功能,以及Core Animation底层方法的高级接口。(CALayer并不能响应事件,它提供了几个能判断一个触点时候再图层的范围之内)

      CALayer存在的意义
      之所以要提供CALayer和UIView这两个平行层级呢,一方面这样可以做到职责分离,可以避免很多重复的代码,另一方面由于iOS和Mac OS的界面其实没多大差别的,但是由于iOS的多点触摸的用户界面和Mac基于鼠标键盘有着本质区别,所以提供一个公用的CALayer来提供界面,分别提供UIView和NSView来提供事件

      CALayer的使用
      平时我们是这样使用UIView的:

    1 let blueView = UIView(frame: CGRectMake(100, 100, 100, 100))
    2 blueView.backgroundColor = UIColor.blueColor()
    3 self.view.addSubview(blueView)

      而我们可以这样使用CALayer,你可以直接用下面这段话直接替换掉上面的代码,程序在外观上不会有任何区别 

    1 let blueLayer = CALayer()
    2 blueLayer.frame = CGRectMake(100, 100, 100, 100)
    3 blueLayer.backgroundColor = UIColor.blueColor().CGColor
    4 self.view.layer.addSublayer(blueLayer)

      CALayer内容(contents)相关属性
    contents属性
      layer有一个contents属性,它需要传入一个id(AnyObject!)类型,这是由于它在iOS平台需要CGImage而Mac需要NSImage,在OC中你需要用id类型强转一下,在Swift中你只需要直接赋一个CGImage就可以了,因为任何一个Class类型的对象都能赋值给AnyObject,如果你传入其它对象,程序不会报错,只是图片不会显示出来,UIImageView之所以能显示图片内部也是使用了这个contents属性的缘故

    contentGravity属性
      contentGravity属性对应于view的contentMode属性,可以控制layer怎样对应和拉伸,虽然它的值是字符串,但是swift帮它提供了常量字符串把每个字符串对应了起来

    contentsScale属性
      contentsScale属性和UIView中的contentScaleFactor是对应的,它决定了一个图片和视图的比例,即屏幕一个点显示几个像素,这是iOS设备做屏幕适配的原理,一个UIImage是包含scale,direction等信息,而转化成CGImage会丢失这些信息,自己可以通过contentsScale属性把image.scale设置给它.

    maskToBounds属性
      maskToBounds属性对应于CALayer的masksToBounds属性,如果设置为true,外部就裁剪了

    contentsRect属性
      contentsRect属性允许我们在图层里显示图片的一部分,这个图片的裁剪区域就是这个属性,它是一个CGRect,利用它你可以做图片拼合(即把一套图片集合在成一张图片,再对这个图片裁剪处理了再使用),这样在内存使用/载入时间/渲染性能等方面都有优势,它的值是按比例的,最大是1.

    contentsCenter属性
      contentsCenter属性对应着UIImage的resizableImageWithCapInsets,它的值是一个CGRect,它代表的放大的区域,它的效果是党contentScale放大的时候,只放大contentsCenter区域,其它区域压缩

      iOS绘图
      CGImage并不是唯一可以赋值给contents属性的,也可以使用Core Graphics绘制寄宿图给它,如果你实现了drawRect方法,然后如果你调用setNeedsDisplay或者外观属性被改变时,它就会自动调用drawRect自动重绘,虽然drawRet是一个UIView方法,但是其实都是底层都是CALayer重绘保存了图片,如果你不需要自定义绘制就不要写一个空的drawRect方法,它很消耗cpu和内存资源
    CALayer有一个可选的delegate属性,如果设置了delegate,并主动调用了layer的displey方法(注意和drawRect不同这个重绘时机是开发者自己控制的,也可以调用setNeedsDisplay方法给系统自己找时机调用),它会调用displayLayer(layer:CALayer!)方法,在这里是设置contents属性的最后机会了,如果你没有实现这个方法,它会尝试去调用下面这个方法:drawLayer(layer:CALayer!,inContext ctx:CGContext!),如果你实现了displayLayer方法,下面这个方法就不会调用了,drawLayer这个方法里你可以做绘图

    1 override func drawLayer(layer: CALayer!, inContext ctx: CGContext!) {
    2     CGContextSetLineWidth(ctx, 10.0)
    3     CGContextSetStrokeColorWithColor(ctx, UIColor.redColor().CGColor);
    4     CGContextStrokeEllipseInRect(ctx, layer.bounds)d060557943
    5 }
    6 override func displayLayer(layer: CALayer!) {
    7     layer.contents = UIImage(named: "11.png")?.CGImage
    8 }

         由于一个UIView它会把它对应的CALayer的delegate设置为它自己,所以你不能再设置其它的layer的delegate为它,在UIView中都用drawRect方法,而delegate的使用只能单独的使用一个层.

       图层几何学

    UIView的frame/bounds/center对应CALayer的frame/bounds/position,center和position是对应父图层的anchorPoint的所在位置,UIView的frame/bounds/center仅仅是存取方法,操纵UIView的这几个属性其实是改变CALayer对应的这几个属性.而CALayer的frame属性又是个计算属性,它是根据bounds/position/transform三个属性计算出来的,而你改变frame的值也可能影响到其中的值,如果你做旋转和缩放后frame和bounds可能不再一致了,bounds就是宽高,而frame还要计算旋转后x和y轴占的空间,如下图

      CALayer通过anchorPoint(锚点)和center(position)对齐来控制UIView的位置,锚点是相对UIView的一个位置,而center就是一个点,由于anchorPoint属性对UIView是屏蔽的,而anchorPoint默认值又是{0.5,0.5},所以这个属性才叫center.而UIView和CALayertransform旋转也是围绕这anchorPoint旋转的,这时候如果是一个圆周运动(比如说时钟旋转)就需要设置锚点的值,让它正常旋转.如下图

                

      在CALayer和UIView都有一套可以把它相对于当前父图层的位置转换成相对其它图层(或view)的位置,Mac OS和iOS的坐标系统是相反的,iOS左上Mac OC左下,你可以用layer的geometryFlipped属性来适配,它可以翻转坐标系.layer的zPosition属性是设置它垂直坐标轴的位置的,默认都是0,所以你只要设置为1,就会显示在其它层的上面

      可以通过相对坐标转换判断点击的点是否在一个layer或view上,代码:

     1 var point = (touches as NSSet).anyObject()?.locationInView(self.view)
     2 point = blueLayer.convertPoint(point!, fromLayer: self.view.layer)
     3 
     4 if blueLayer.containsPoint(point!) {
     5     println("touch in blue")
     6     
     7     let yellowPoint = yellowLayer.convertPoint(point!, fromLayer: blueLayer)
     8     if yellowLayer.containsPoint(yellowPoint) {
     9         println("touch in yellow")
    10     }
    11     
    12     let redPoint = redLayer.convertPoint(point!, fromLayer: blueLayer)
    13     if redLayer.containsPoint(redPoint) {
    14         println("touch in red")
    15     }
    16 }

      hitTest可以获取你接触的那个图层:

    let point = (touches as NSSet).anyObject()?.locationInView(self.view)
    let layer = self.view.layer.hitTest(point!)
    
    if layer == blueLayer {
        println("touch in blue")
    }
    if layer == yellowLayer {
        println("touch in yellow")
    }
    if layer == redLayer {
        println("touch in red")
    }

      UIView可以通过autoresizingMask和constraints等属性做到自适应屏幕旋转,CaLayer也有对应的layoutManager属性和CAConstraintLayoutManager类,但是只能在Mac OS上使用,iOS上还不支持,如果你想使用这个特性你就不能单独使用layer,但是如果你想调整layer的大小还是可以通过设置layer的delegate,然后实现代理方法layoutSublayersOfLayer直接修改大小颜色之类,它也需要调用setNeedsLayout方法,它和UIView对应的layoutSubviews是一样的.

      视觉效果
    圆角
      layer的cornerRadius属性可以设置圆角曲率,如果曲率大小为边长的一半,圆角会内切于这条边,如果是个正方形最后的结果就是个圆形,如果是裁剪子视图也是直接按圆内裁剪

    边框
      layer的borderColor设置边框颜色,它是CGColorRef,borderWidth设置边框宽度,值得注意的是边框宽度占用的是layer的frame的宽度,它并不会在layer外面加一层边框而是在内部生成.而layer会被子layer覆盖但边框不会被覆盖,并且边框只是显示用的,它不会干扰触摸和事件,有没有边框都是一样的.

    阴影
      阴影至少需要shadowColor/shadowOffset/shadowOpacity三个属性才能起作用,shadowOffset的值是CGSize类型,两个正值是朝右下角.还有一个属性shadowRadius,它控制阴影的边界模糊程度,默认值是3,值为0则不模糊,值越大越模糊,模糊的结果是CGSize放心颜色重,其它几个方向也有对应的阴影,会有一个层次感.
    因为阴影是在layer外部的,所以如果要裁剪超出layer的子视图则需要使用maskToBounds属性,而这时阴影也会被裁剪掉,所以当maskToBounds和阴影共存时需要特殊处理下:

     1 blueLayer = CALayer()
     2 blueLayer.frame = CGRectMake(50, 100, 300, 300)
     3 blueLayer.backgroundColor = UIColor.blueColor().CGColor
     4 blueLayer.cornerRadius = blueLayer.bounds.size.width / 2.0
     5 blueLayer.borderColor = UIColor.purpleColor().CGColor
     6 blueLayer.borderWidth = 10.0
     7 
     8 blueLayer.masksToBounds = true
     9 
    10 let shadowLayer = CALayer()
    11 shadowLayer.frame = blueLayer.frame
    12 shadowLayer.cornerRadius = blueLayer.cornerRadius
    13 shadowLayer.shadowColor = UIColor.redColor().CGColor
    14 shadowLayer.shadowOffset = CGSizeMake(23 , 23.0)
    15 shadowLayer.shadowOpacity = 1
    16 shadowLayer.shadowRadius = 60
    17 shadowLayer.backgroundColor = UIColor.redColor().CGColor
    18 self.view.layer.addSublayer(shadowLayer)
    19 
    20 self.view.layer.addSublayer(blueLayer)

      需要注意的是阴影层需要有背景色,不然它的阴影显示不出来

      shadowPath属性可以设置阴影的形状,注意swift中的CGMutablePath并不会增加引用计数,你不需要relase它也没有方法可以提供给你release

    1  let  circlePath = CGPathCreateMutable()
    2  CGPathAddEllipseInRect(circlePath, nil, self.blueLayer.bounds)
    3  shadowLayer.shadowPath = circlePath

      当然你也可以使用UIBezierPath来设置更复杂的形状给shadowPath

    1 let path1 = UIBezierPath(roundedRect: CGRectMake(-75, -75, 200, 200), cornerRadius: 30).CGPath
    2 let path2 = UIBezierPath(arcCenter: CGPointMake(25, 40), radius: 100, startAngle: 0, endAngle: 3.14159265, clockwise: true).CGPath
    3 shadowLayer.shadowPath = path1
    4 shadowLayer.shadowPath = path2

      图片蒙板

      layer的mask属性可以设置一个layer给他,这个layer的contents应该是一个32位有alpha通道的png图片,你可以设置一个不规则的图片其它部分为透明,这样就对layer设置了一个蒙板,蒙板的颜色不重要,轮廓比较重要,最后被设置了蒙板的layer它只会显示mask蒙板形状的内容.

    1 let maskLayer = CALayer()
    2 maskLayer.frame = CGRectMake(0, 0, 100, 100)
    3 maskLayer.contents = UIImage(named: "111.png")?.CGImage
    4 blueLayer.mask = maskLayer

      拉伸

      如果设置的图片不需要拉伸有很多好处,即不需要拉伸图片,又能合理的使用内存和cpu,但是很多时候一张图片要多个位置使用,所以需要拉伸,iOS跟我们提供了3中拉伸方式kCAFilterLinear/kCAFilterNearest/kCAFilterTrilinear,设置拉伸方式只需要跟layer的这两个属性赋对应的拉伸方式就可以:minification(缩小图片)和magnification(放大图片),这个属性默认是kCAFilterLinear,它和kCAFilterTrilinear类似,他们都是线性的,意思就是它会取两个值的过度色,让对应点能顺滑的过渡,如果图片有渐变色和斜线比较多就需要用这个默认的,如果图片主要是单色而且主要是垂直方向的颜色,那么就需要kCAFilterNearest,它是暴力的直接取周围的颜色,那样又不会失真,也不会太消耗cpu

      组透明

      iOS8默认就是组透明,在应用透明度之前,它会把子图层和它整合成一个整体的图片,那样就没有透明度混合的问题了,目前没有测试出透明度出问题的情况,如果有可以设置layer的shouldRasterize的值为YES

  • 相关阅读:
    搭建vue项目脚手架
    vue项目中的iconfont图标下载及配置
    vue-awesome-swiper 轮播图的使用
    IDEA自动生成Mapper和实体文件
    云服务器通过IP如何访问项目
    社保,步入社会的第一步
    Memcached安装与启动
    IDEA提示非法字符,你不懂的UTF-8
    MyEclipse导入eclipse的web项目,将WebRoot切换为WebContent
    Myeclipse2017删除tomcat后,项目全部报错的解决办法
  • 原文地址:https://www.cnblogs.com/hkJser/p/4630784.html
Copyright © 2020-2023  润新知