Core Animation的学习 - 2

Core Animation ,学习底层绘画与动画

2016-05-18 | 阅读

布局控制

UIView有三个比较重要的属性:frame,boundscenter,而CALayer对象的有frame,boundsposition.两边属性的意义都是一样的.

  • frame : 表示外部坐标,即在父容器上的空间
  • bounds : 表示内部坐标
  • center和position : 表示anchorPoint相对于 父图层的anchorPoint的位置. 默认是{0,0}

UIView中的frame,boundscenter的属性仅仅是存取方法,实际操作是是改变视图中的CALayer的属性.

frame并不是一个独立的属性,其实是个虚拟的属性,是根据bounds,positiontransform计算而来的,所以当其中一个值改变时,frame就会改变,同样,改变frame的值,也会改变着三个值.

当对图层进行变换时,如旋转缩放,frame实际代表的是图层旋转后,整个轴对齐的矩形区域,即frame的宽高可能和bounds的不一致.如下图:

锚点anchorPoint

视图的center和图层的position属性指定了anchorPoint相对于父图层的位置.图层的anchorPoint通过position来控制frame的位置,可以将anchorPoint作为移动图层的把柄.

默认情况,anchorPoint是图层的中点,使用相对坐标即{0.5,0.5},默认情况下position是{0,0}. 所以如果设置anchorPoint为frame的左上角{0,0},则整个layer的frame将向右下角移动,以使新的anchorPoint与父图层的中点anchorPoint重合.

坐标系

图层在图层树种的坐标依赖于父图层的bounds,如果父图层发生移动,所有的子图层也会跟着移动.

CALayer给不同坐标系之间的图层位置转换:

- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer; 
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer; 
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;

Z坐标轴

UIView是在二维的,但是CALayer是存在于三维空间中的.CALayer还有两个属性zPositionanchorPointZ,表示在Z轴上的位置.图层是扁平的,所以没有一个厚度的概念,只有高度的概念.

通过zPosition可以改变视图的显示顺序,越大,越高,将出现在顶层.

hit Testing

CALayer并不关心响应链,不能直接处理触碰时间或者手势.但是可以通过containsPoint:-hitTest:来判断点击事件是否发生在图层上.

对于子类图层,虽然可以通过zPosition来改变在屏幕上显示的顺序,但是不能改变事件传递的顺序,即hitTest:严格按照图层树中的图层顺序来判断.

圆角

CALayer有一个cornerRadius的属性来控制图层的圆角,默认这个曲率只影响CALayer自身,不影响其子图层,所以也不能影响背景图片,所以要设置圆角,还要相应的设置maskToBounds为YES,以截取图层里所有的东西.

图层边框

borderWidthborderColor来定义边框的样式. 边框是沿着图层的bounds绘制的,当然包含图层的圆角.borderWidth默认是0,borderColor默认是黑色.

阴影

  • shadowOpacity : 阴影透明度 ,默认是0.
  • shadowColor :阴影颜色, 也是一个CGColorRef的类型,默认是黑色
  • shadowOffset : 控制阴影的方向和距离,是一个CGSize,宽度控制阴影横向的偏移,高度控制纵向的偏移.默认值是{0,-3},即在Y轴上有3个点的向上偏移,即阴影是向上的.

    为什么阴影是默认向上的呢? 因为一些代码是与Mac OS中共用的,但是MacOS与iOS的Y轴是颠倒的,所以在ios中阴影就是向上的,而Mac上确实正常的向下.

  • shadowRadius : 控制阴影的模糊度,如果只为0时,与视图有一个非常明确的边界线,值越大,边界线越模糊.

图层阴影继承自内容的外形,而不是根据边界和角半径来决定的.为了计算出阴影的形状,Core Animation会将寄宿图包括所有子视图考虑在内,通过这些来完美搭配图层形状从而创建一个阴影.但是,阴影通常在Layer的边界之外,如果开启了masksToBounds,所有从图层中突出的内容都会被裁剪掉,包括这些阴影,如下图所示:

这种情况下,处理方法是在创建一个CALayer,做两层CALayer,一层作为容器来包含其他子layer,然后设置masksToBounds,而另外一层作为容器来包含前面的这个CALayer,然后就可以以包含的CALayer的轮廓来产生阴影了.

shadowPath 属性

图层阴影通过图层形状进行计算出来的,但是实时计算阴影是一个非常消耗资源的事情,尤其是当有多个子图层的时候.建议是如果一个View要在其边框上做阴影,还是要使用两个CALayer,一个CALayer为容器,进行绘图和持有子layer,一个作为border,只绘制一个边框,与容器CALayer同级,都是UIView的直属CALayer的子layer,但进行阴影绘制,拥有阴影属性.

还有一种方式,如果你事先知道了阴影的形状,那就可以通过制定一个shadowPath来提高性能,这个属性是CGPathRef类型,CGPath是一个Core Graphics对象,用来制定任意一个的矢量图形,可以用这个属性独立于图层形状之外来制定阴影形状.如下创建一个矩形和一个圆形的阴影 :

  CGMutablePathRef squarePath = CGPathCreateMutable();
  CGPathAddRect(squarePath, NULL, self.layerView1.bounds);
  self.layerView1.layer.shadowPath = squarePath; CGPathRelease(squarePath);

  //create a circular shadow
  CGMutablePathRef circlePath = CGPathCreateMutable();
  CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds);
  self.layerView2.layer.shadowPath = circlePath; CGPathRelease(circlePath);

效果如下:

图层蒙板

有时候,想要展示的内容不是一个矩形,而是想展示一些其他形状的图片,如圆形,星型等.一般可以使用有透明色的图片来做出一个非矩形.

CALayer有一个属性叫mask可以解决这个问题,这个属性本身是是一个CALayer类型,有和其他图层一样的绘制和布局属性.类似于一个子图层,相对于父图层矩形布局,这个mask图层定义了父图层的部分可见区域.这个mask图层的Color属性无关紧要,重要的是图层的轮廓,图层实心部分会被保留下来,其他则被抛弃.如下图所示 :

任何图层都可以作为mask属性.

拉伸过滤

在显示图片时,要正确的显示图片,做到:

  • 能够显示最好的画质,像素既没有被压缩也没有被拉伸
  • 能更好的使用内存,不浪费内存存储不需要的图片
  • 最好的性能表现,不浪费计算用于图片的拉伸和压缩.

不过很多时候,我们需要显示非真实大小的图片,需要去拉伸压缩图片,CALayer提供了三种拉伸过滤方法,是

  • kCAFilterLinear
  • kCAFilterNearest
  • kCAFilterTrilinear

minification缩小图片和magnification放大图片默认的是kCAFilterLinear,这个过滤器采用双线性滤波算法,大多数情况下表现良好.这种算法通过多个像素取样最终生成新的值,得到一个平滑的表现不错的拉伸,但放大倍数比较大的时候,图片就模糊不清了.

kCAFilterTrilinearkCAFilterLinear相似,大部分情况下,两者都看不出差别,但是三线性滤波算法存储了多个大小情况下的图片,并三维取样,同时结合大图小图的存储进而得到最后的结果.也就是说,牺牲空间换取时间,通过存储多份不同大小的图片,而后通过这一系列的图片来取得解决最终大小的图片的结果,提高了性能,也避免了小概率因舍入错误引起的取样失灵的问题. 也就是说,适用的情况是,图片要进行多次拉伸,所以进行一定的缓存策略,以提高速度.

kCAFilterNearest是一种武断的方法,最近过滤,就是取样最近的单像素点,而不管其他的颜色,这样做会非常快,也不会使图片模糊.但最明显的效果,会使压缩图片变得更糟,图片放大后显得块状或是马赛克严重.

可以看到,对于没有斜线的小图,这种过滤算法,效果会非常好.所以,对于比较小的图或者是差异特别明显没有过多模糊,极少斜线的图,这种过滤算法,会保留这种差异明显的特质,以呈现更好的结果. 线性过滤保留了形状,而最近过滤保留了像素的差异.

组透明度

UIView中有一个叫做alpha的属性来确定视图的透明度,CALayer有一个等同的属性叫做opacity,这两个属性都是影响子层级的.

iOS中常常设置一个控件的alpha的值为0.5,以使控件看上去是不可用状态,对于独立视图,这样效果很正常,但是,对于有子视图的控件,就显得奇怪了,如下图中,一个内嵌了UILabel的自定义UIButton,右边设置的是50%透明度,显得很诡异:

这是由于透明度的叠加导致的. 当显示一个50%透明度的图层时,每个像素显示一半的图层本身的颜色,一半显示图层下面的背景色,这是正常的情况.但当图层同样包含一个50%透明度的子图层时,看到的像素上,50%来自子视图,25来自图层本身,25%来自于背景色.这样导致中间这个label的透明度变成了25%,没有周围的那样透明了.

可以通过在info.plist中设置UIViewGroupOpacity为YES来达到整个图层树统一的透明效果.这个属性默认是NO,且设置后会产生不良影响.

可以设置CALayer的shouldRasterize属性来设置组透明的效果,即设置为YES后,在应用透明度前,整个图层和子图层会被整合成一个整体的图片.在设置这个属性的同时,还要设置rasterizationScale属性,这个属性默认为1.0,需要将这个属性设置成屏幕的scale,以防止屏幕适配问题.但设置这两个属性的时候,性能问题又出现了.