iOS Core Animation - Advanced Techniques-学习笔记(三)

变换

仿射变换

  • UIView的transform属性,用于视图在二维空间做旋转,缩放和平移
  • 仿射变换的定义,是指无论变换矩阵用什么值,图层中平行的两条线在变换之后仍然保持平行
  • Core Graphics提供的实例化仿射变换的方法
    • CGAffineTransformMakeRotation(CGFloat angle) // 旋转
    • CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) // 缩放
    • CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty) // 平移
  • CALayer也有一个transform属性,但它的类型是CATransform3D,而不是CGAffineTransform
  • Core Graphics提供了在之前变换的基础上继续变换的方法
    • CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
    • CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
    • CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)
  • 以及一个单位矩阵常量CGAffineTransformIdentity
  • 我们可以利用这些函数,在必要的时候组合一个更加复杂的变化,这样的实现方式,是优雅的
- (void)viewDidLoad {
    [super viewDidLoad];
    //create a new transform
    CGAffineTransform transform = CGAffineTransformIdentity; 
    //scale by 50%
    transform = CGAffineTransformScale(transform, 0.5, 0.5);
    //rotate by 30 degrees
    transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0);
    //translate by 200 points
    transform = CGAffineTransformTranslate(transform, 200, 0);
    //apply transform to layer
    self.layerView.layer.affineTransform = transform;
}

3D变换

  • 和CGAffineTransform类似,CATransform3D也是一个矩阵,但是和2x3的矩阵不同,CATransform3D是一个可以在3维空间内做变换的4x4的矩阵,我们要关注zPosition

  • 3D变换的方法

    • CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
    • CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
    • CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)

    与仿射变化的旋转不同,CATransform3DMakeRotation是绕某一个(x,y,z)轴进行旋转

透视效果

  • 三维空间中,当物体原理我们时,会看起来比较小,远处的物体和近处的物体存在不同的缩放比例
  • 为了实现透视效果,我们需要修改m34值,m34的默认值是0,我们可以通过设置m34为(-1.0 / d)来实现透视效果,d代表了想象中视角相机和屏幕之间的距离,以像素为单位,通常500~1000就可以了。代码如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    //create a new transform
    CATransform3D transform = CATransform3DIdentity;
    //apply perspective
    transform.m34 = - 1.0 / 500.0;
    //rotate by 45 degrees along the Y axis
    transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0);
    //apply to layer
    self.layerView.layer.transform = transform;
}

灭点

  • 当在透视角度绘图的时候,远离相机视角的物体将会变小变远,当远离到一个极限距离,它们可能就缩成了一个点,于是所有的物体最后都汇聚消失在同一个点。这个点叫灭点
  • Core Animation定义的这个点位于变换前anchorPoint的位置
  • 当一个图层有多个3D变换的子图层,我们要想让整个视图绘制的更有3D效果,应该首先把它们都放置于屏幕中央,然后通过平移来把它移动到指定位置(而不是直接改变它的position),这样所有的3D图层都共享一个灭点

sublayerTransform属性

  • 如果图层有多个子图层都要做3D变换,那就要分别对m34进行设置,CALayer有一个属性叫做sublayerTransform,这个变换会作用于所有子图层
- (void)viewDidLoad {
    [super viewDidLoad];
    //apply perspective transform to container
    //容器视图变换,perspective将会影响layerView1和layerView2
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = - 1.0 / 500.0;
    self.containerView.layer.sublayerTransform = perspective;
   
    //rotate layerView1 by 45 degrees along the Y axis
    CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
    self.layerView1.layer.transform = transform1;
   
    //rotate layerView2 by 45 degrees along the Y axis
    CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
    self.layerView2.layer.transform = transform2;
}

图层的背面

  • 图层是双面绘制的,反面是一个镜像图片,这并不是一个很好的特性,会给用户造成困扰,同时也可能造成系统资源的浪费,试想我们不想看到背面,那为什么还要浪费GPU绘制它们?
  • doubleSided属性,用来控制图层背面是否绘制,默认为YES

专用图层

CALayer类具有一些非常有用的绘图和动画功能。但Core Animation不仅作用于图片和颜色,CALayer拓展了其他一些专用于某种功能的子类,以增强Core Animation的绘图能力。这里学习总结了几个常用的专用图层,其他的仅作了解

CAShapeLayer - 形状图层

  • 阴影可以使用CGPath来构建轮廓,图层也可以用这种方式构建
  • CAShapeLayer是一个通过矢量绘图的图层子类
  • 比较推荐的方式是使用UIBezierPath类来帮助创建图层,这样我们不用考虑人工释放CGPath资源
// 利用CAShapeLayer新建图层绘制一个火柴人
- (void)viewDidLoad {
  [super viewDidLoad];
  //create path
  UIBezierPath *path = [[UIBezierPath alloc] init];
  [path moveToPoint:CGPointMake(175, 100)];
  
  [path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES];
  [path moveToPoint:CGPointMake(150, 125)];
  [path addLineToPoint:CGPointMake(150, 175)];
  [path addLineToPoint:CGPointMake(125, 225)];
  [path moveToPoint:CGPointMake(150, 175)];
  [path addLineToPoint:CGPointMake(175, 225)];
  [path moveToPoint:CGPointMake(100, 150)];
  [path addLineToPoint:CGPointMake(200, 150)];

  //create shape layer
  CAShapeLayer *shapeLayer = [CAShapeLayer layer];
  shapeLayer.strokeColor = [UIColor redColor].CGColor;
  shapeLayer.fillColor = [UIColor clearColor].CGColor;
  shapeLayer.lineWidth = 5;
  shapeLayer.lineJoin = kCALineJoinRound;
  shapeLayer.lineCap = kCALineCapRound;
  shapeLayer.path = path.CGPath;
  //add it to our view
  [self.containerView.layer addSublayer:shapeLayer];
}
  • CAShapeLayer还有一个比较常用的用法,是绘制矩形的指定圆角,UIBezierPath有一个自动绘制圆角矩形的构造方法
  • 目前常用的做法是在UIView的分类添加以下方法,创建图层蒙版
- (void)addRoundedCorners:(UIRectCorner)corners radius:(CGFloat)radii{
    
    UIBezierPath* rounded = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(radii, radii)];
    CAShapeLayer* shape = [[CAShapeLayer alloc] init];
    [shape setPath:rounded.CGPath];
    
    self.layer.mask = shape;
}

这么做有两个问题,一是当我们同时需要创建指定圆角和阴影时,不可避免要添加一个新的Container图层;二是动态修改frame.size时,蒙版路径并不会更新(准确说pathRect就是初始bounds值)

在书中提出【我们可以把CAShapeLayer作为视图的宿主图层,而不是添加一个子视图】。经过实验,一个可行的方案是,我们重写UIView的+layerClass方法,返回指定图层类CAShapeLayer.class,并在图层布局的时候指定path,注意,如果要指定CAShapeLayer背景图层着色,要使用fillColor属性而不能直接写backgroundColor

+ (Class)layerClass{
    return CAShapeLayer.class;
}

- (void)layoutSublayersOfLayer:(CALayer *)layer{
    [super layoutSublayersOfLayer:layer];
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:_corners cornerRadii:CGSizeMake(10, 10)];
    _shapeLayer.path = path.CGPath;
}

- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        //custom layer
        _shapeLayer = (CAShapeLayer *)self.layer;
        _corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
    }
    return self;
}

CAGradientLayer - 渐变图层

  • CAGradientLayer是用来生成两种或更多颜色平滑渐变的。通常我们绘制渐变图,会用Core Graphics的CGContextDrawLinearGradient()方法生成一张图片,但相较于这种方式,CAGradientLayer使用了硬件加速使整个流程更加效率,并且代码上更加简洁优雅
  • 创建一个简单的渐变图层,此处注意接收渐变色彩数组的类型是CGColorRef,为了保证编译正常需要bridge转换
- (void)viewDidLoad {
  [super viewDidLoad];
  //create gradient layer and add it to our container view
  CAGradientLayer *gradientLayer = [CAGradientLayer layer];
  gradientLayer.frame = self.containerView.bounds;
  [self.containerView.layer addSublayer:gradientLayer];

  //set gradient colors
  gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor blueColor].CGColor];

  //set gradient start and end points
  gradientLayer.startPoint = CGPointMake(0, 0);
  gradientLayer.endPoint = CGPointMake(1, 1);
}

CAReplicatorLayer - 重复图层

  • CAReplicatorLayer的目的是为了高效生成许多相似的图层

  • CAReplicatorLayer的几个常用属性

    • instanceCount创建多少个指定图层的拷贝
    • instanceDelay两次拷贝间的延迟
    • instanceTransform基于上次拷贝的仿射变换
  • 一般和CAAnimationGroupCAShapeLayer组合使用

    书中并没有举很多实用性的例子,但其实这个图层使用还是挺广泛的,例如雷达,波纹,加载球球,咻一咻效果等。总的来说,这个图层的使用需要一些想象力

CAEmitterLayer - 粒子图层

  • CAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。实际应用比较广泛,比如微信的红包雨,直播间的小❤❤

    这是一个很实用的图层,之后我会专门写一篇博客来讲讲他的使用

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

推荐阅读更多精彩内容