iOS Core Animation Advanced Techniques学习笔记(1)

图层树

Core Animation是一个复合引擎,它的职责是尽可能快地组合屏幕上不同的可视内容。这些内容被分解成独立的图层,存储在一个叫图层树的体系中。在屏幕上所看见的一切内容,在底层的实现其实就是一个或多个图层树。

图层与视图

真正现在屏幕上显示和做动画的并不是UIView,而是UIVIew所对应的一个CALayer实例(backing layer)。UIview的职责只是创建并管理这个layer,以确保当子视图在层级关系中添加或者被移除的时候,它们对应的图层也在图层树中有相应的操作。UIView仅仅是对CALayer的一个封装,它提供了对用户交互的处理和Core Animation底层方法的高级接口。

事实上除了视图层级图层树之外,还存在呈现树渲染树,它们每一个都扮演着不同的角色。

图层的能力

  • 阴影,圆角,带颜色的边框
  • 3D变换
  • 非矩形范围
  • 透明遮罩
  • 多级非线性动画

寄宿图

contents属性

为layer添加一张寄宿图:

layer.contents = (__bridge id)image.CGImage;

设置寄宿图显示模式:

layer.contentsGravity = kCAGravityResizeAspect;

设置图片缩放比:

layer.contentsScale = [UIScreen mainScreen].scale;

设置超出部分是否显示:

layer.masksToBounds = YES;

设置图片显示区域:

layer.contentsRect = CGRectMake(0, 0, 0.5, 0.5);

设置固定边框和拉伸区域:

layer.contentsCenter = CGRectMake(0.25, 0.25, 0.5, 0.5);

自定义绘制

  1. 实现UIView的-drawRect:方法
    当视图显示在屏幕上的时候-drawRect:方法就会被调用,它当中的代码就会利用Core Graphics绘制一个寄宿图,然后内容将会缓存起来直到它需要被更新(开发者调用了setNeedsDisplay方法,或者影响视图表象效果的属性被改变时,视图将会被自动重绘,如bounds属性)。

  2. 实现CALayer的非正式协议CALayerDelegate

    当CALayer需要被重绘时,它会通过调用下面的方法来请求它的代理给它一个寄宿图来显示。

     (void)displayLayer:(CALayerCALayer *)layer;
    

    在上面的代理方法中,通过设置layer的contents属性,就可以设置一个寄宿图。如果代理没有实现displayLayer方法,CALayer会转而尝试调用下面这个方法:

     - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
    

    在调用这个方法之前,CALayer创建了一个合适尺寸和空寄宿图(尺寸有bounds和contentsScale决定)和一个Core Graphics的绘制上下文环境,为绘制寄宿图做准备。

     @implementation ViewController
     - (void)viewDidLoad
     {
       [super viewDidLoad];
       
       //create sublayer
       CALayer *blueLayer = [CALayer layer];
       blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
       blueLayer.backgroundColor = [UIColor blueColor].CGColor;
     
       //set controller as layer delegate
       blueLayer.delegate = self;
     
       //ensure that layer backing image uses correct scale
       blueLayer.contentsScale = [UIScreen mainScreen].scale; 
       
       //add layer to our view
       [self.layerView.layer addSublayer:blueLayer];
     
       //force layer to redraw
       [blueLayer display];
     }
     
     - (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);
     }
     @end
    

    需要注意的事情:

    1. 当图层显示在屏幕上时,CALayer不会自动重绘它的内容,需要开发者显示调用-display方法。
    2. 当使用CALayerDelegate绘制寄宿图时,不会对超出图层边界外的内容提供绘制支持。

图层几何学

布局

与UIView的center属性对应的是CALayer中的position属性,他们都代表了相对于父图层anchorPoint所在的位置。而不是视觉上所观察到的图层中心的位置,在anchorPoint的值不为图层的中心时,centerposition的值将会和视觉上图层中心点的值不一致。

对于视图和图层来说,frame其实是一个虚拟的属性,是根据boundspositiontransform计算而来,所以当其中任何一个值发生改变,frame都会变化。相反,改变frame的值同样会影响它们的值。

当对图层做变换的时候(如旋转/缩放),frame实际上代表了在图层旋转之后整个轴对其的矩形区域。此时,frame的宽高和bounds的宽高并不一致。

锚点(anchorPoint)

图层的anchorPoint属性可以看做是图层上一个不动的点,当图层进行旋转等操作的时候都是以此点为圆心进行的,类似于用一个钉子将纸钉在平面上,无论如何旋转,钉子的位置是不会动的。在默认情况下anchorPoint位于图层的中心(0.5, 0.5)。

坐标系

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;

这些方法可以把定义在一个图层坐标系下的点或者矩形转换成另一个图层坐标系下的点或者矩形。

垂直翻转子图层:

layer.geometryFlipped = YES;

Hit Testing

判断触摸点是否在图层内:

BOOL isContain = [layer containsPoint:point];

返回图层本身,或者包含这个坐标点的叶子节点图层:

CALayer *layer = [layer hitTest:point];

自动布局

当使用视图的时候,可以充分利用UIView类接口暴露出来的UIViewAutoresizingMask和NSLayoutConstraintAPI,但如果想随意控制CALayer的布局,就需要手工操作。

图层手动布局:

- (void)layoutSublayersOfLayer:(CALayer *)layer;

当图层的bounds发生改变,或者图层的-setNeedsLayout方法被调用的时候,这个函数将会被执行。这使得我们可以手动地重新摆放或者重新调整子图层的大小,但是不能像UIView的autoresizingMask和constraints属性做到自适应屏幕旋转。


视觉效果

圆角

设置图层角曲率(默认为0):

layer.conrnerRadius = 5.0f

图层边框

设置图层边框宽度和颜色:

layer.borderWidth = 3.0f;
layer.borderColor = [UIColor greenColor].CGColor;

阴影

设置图层阴影颜色,方向和距离,模糊度

layer.shadowColor = [UIColor orangeColor].CGColor;
layer.shadowOffset = CGSizeMake(50, 50);
layer.shadowRadius = 10;

当图层的masksToBounds属性值设置为YES时,所有从图层中突出来的内容都会被剪裁掉。因此阴影效果将会失效。想要为这样的图层添加阴影,就需要在要添加阴影的图层范围上覆盖一个只画阴影的空图层。

使用shadowPath指定任意形状的阴影:

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *layerView1;
@property (nonatomic, weak) IBOutlet UIView *layerView2;
@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  //enable layer shadows
  self.layerView1.layer.shadowOpacity = 0.5f;
  self.layerView2.layer.shadowOpacity = 0.5f;

  //create a square shadow
  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);
}
@end

图层蒙版

设置图层蒙版:

//create mask layer
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = self.layerView.bounds;
UIImage *maskImage = [UIImage imageNamed:@"Cone.png"];
maskLayer.contents = (__bridge id)maskImage.CGImage;

//apply mask to image layer
self.imageView.layer.mask = maskLayer;

拉伸过滤

设置图层拉伸过滤算法:

view.layer.magnificationFilter = kCAFilterNearest;

透明度

透明度混合叠加问题:当现实一个透明度为50%的图层时,图层的每个像素都会一半现实自己的颜色,另一半显示图层下面的颜色。当图层包含一个同样50%透明的子图层时,所看到的视图,50%来自子视图,25%来自图层本身的颜色,另外25%则来自背景色。这时子视图的可见的则为75%,显示效果将会相当糟糕。

理想状态下,设置一个图层的透明度,是希望它所包含的图层树向一个整体一样的透明效果。这可以通过设置Info.plist文件中的UIViewGroupOpacity为YES来达到这个效果,但是这个设置会影响到这个应用,整个app可能会受到不良影响。

另一个方法就是设置CALayer的一个叫做shouldRasterize属性来实现组透明的效果,如果它被设置为YES,在应用透明度实现之前,图层及其子图层都会被整合成一个整体的图片,这样就没有透明度混合的问题了。

为了启用shouldRasterize属性,需要图层的rasterizationScale属性。默认情况下,所有图层拉伸都是1.0, 所以如果使用了shouldRasterize属性,就要确保你设置了rasterizationScale属性去匹配屏幕,以防止出现Retina屏幕像素化的问题。

layer.shouldRasterize = YES;
layer.rasterizationScale = [UIScreen mainScreen].scale;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容