Cowboy Tech

CALayer的使用

CALayer简介

  1. CALayer一般作为UIView的容器而使用
  2. CALayer是一个管理着图片载体(image-based content)的层结构, UIView中CALayer的值设定(如frame, backgroundColor)可以覆盖其UIView中的设定
  3. 直接修改单独创建出的CALayer的属性可以触发隐式动画
  4. UIView中的CALayer动画必须显式触发才能生效

用CALayer定制下载进度条控件

  1. 单独创建出CALayer
  2. 直接修改CALayer的frame值执行隐式动画,实现进度条效果
  3. 用定时器(NSTimer)模拟网络下载时提供的百分比数据
  4. 将CALayer封装进UIView子类中定制进度条控件

隐式动画进度条

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) NSTimer *timer; // 定时器
@property (nonatomic, strong) CALayer *layer; // layer

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

// 设置背景色
self.view.backgroundColor = [UIColor blackColor];

// CALayer
_layer                 = [CALayer layer];
_layer.frame           = CGRectMake(50, 50, 200, 2);
_layer.backgroundColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:_layer];

// 定时器
_timer = [NSTimer scheduledTimerWithTimeInterval:1.f
                                          target:self
                                        selector:@selector(timerEvent)
                                        userInfo:nil
                                         repeats:YES];
                                     }


- (void)timerEvent {
// 执行layer的隐式动画(取随机值模拟下载进度)
_layer.frame = CGRectMake(50, 50, arc4random()%200, 2);
}

@end

封装UIView实现进度条

progressView.h

#import <UIKit/UIKit.h>

@interface ProgressView : UIView

@property (nonatomic, assign) CGFloat  progress;   // 进度参数(取值范围为 %0 ~ %100)
@property (nonatomic, strong) UIColor *layerColor; // 修改layer的颜色

@end

progressView.m

#import "ProgressView.h"

@interface ProgressView ()

@property (nonatomic, strong) CALayer *progressLayer;
@property (nonatomic, assign) CGFloat  currentViewWidth;

@end

@implementation ProgressView

- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
    self.progressLayer       = [CALayer layer];
    self.progressLayer.frame = CGRectMake(0, 0, 0, frame.size.height);
    self.progressLayer.backgroundColor = [UIColor redColor].CGColor;
    [self.layer addSublayer:self.progressLayer];

    // 存储当前view的宽度值
    self.currentViewWidth = frame.size.width;
}
return self;
}

#pragma mark - 重写setter,getter方法
@synthesize progress = _progress;

- (void)setProgress:(CGFloat)progress {
_progress = progress;

if (progress <= 0) {
    self.progressLayer.frame = CGRectMake(0, 0, 0, self.frame.size.height);
} else if (progress <= 1) {
    self.progressLayer.frame = CGRectMake(0, 0,
                                          progress * self.currentViewWidth,
                                          self.frame.size.height);
} else {
    self.progressLayer.frame = CGRectMake(0, 0, self.currentViewWidth,
                                          self.frame.size.height);
}
}

- (CGFloat)progress {
return _progress;
}

@synthesize layerColor = _layerColor;

- (void)setLayerColor:(UIColor *)layerColor {
_layerColor = layerColor;
self.progressLayer.backgroundColor = layerColor.CGColor;
}

- (UIColor *)layerColor {
return _layerColor;
}


@end

viewController.m

#import "ViewController.h"
#import "ProgressView.h"
@interface ViewController ()
@property (nonatomic, strong) ProgressView *progressView;
@property (nonatomic, strong) NSTimer      *timer;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

self.view.backgroundColor = [UIColor blackColor];

self.progressView            = [[ProgressView alloc] initWithFrame:CGRectMake(20, 20, 290, 3)];
self.progressView.layerColor = [UIColor yellowColor];
[self.view addSubview:self.progressView];

// 创建定时器,每一秒执行一回
self.timer = [NSTimer scheduledTimerWithTimeInterval:1
                                              target:self
                                            selector:@selector(layerAnimation)
                                            userInfo:nil
                                             repeats:YES];
                                         }

- (void)layerAnimation {
// 随机获取 0% ~ 100% 给layer赋值
self.progressView.progress = arc4random() % 100 / 100.f;
}

@end

用CALayer定制UIImageView淡入淡出切换图片效果

  1. 操作UIImageView的CALayer修改其bounds值进行显式动画
  2. 修改UIImageView的CALayer中的contents属性实现切换图片的动画
  3. 用CAAnimationGroup将bounds动画与contents动画组合起来
  4. 将上述效果封装进UIView的子类中生成控件

代码

#import "ViewController.h"

#define NO_EXECUTE  0  // 不执行
#define EXECUTE     1  // 执行


@interface ViewController ()
@property (nonatomic, strong) CALayer *imageLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

// 创建出独立的layer
self.imageLayer          = [CALayer layer];
self.imageLayer.frame    = CGRectMake(0, 0, 302, 707);
// 给layer的contents属性设置图片
self.imageLayer.contents = (__bridge id)([UIImage imageNamed:@"起始图片"].CGImage);
[self.view.layer addSublayer:self.imageLayer];


// 3s后执行layer动画
[self performSelector:@selector(layerAnimation)
           withObject:nil
           afterDelay:3.f];
       }

- (void)layerAnimation {

if (NO_EXECUTE) {
    // 执行隐式动画(你自己无法控制持续时间等等的参数)
    self.imageLayer.contents = (__bridge id)([UIImage imageNamed:@"结束图片"].CGImage);
}

if (NO_EXECUTE) {
    // 执行显式动画

    // 设定基本动画参数
    CABasicAnimation *contentsAnimation = [CABasicAnimation animationWithKeyPath:@"contents"];
    contentsAnimation.fromValue         =  self.imageLayer.contents;
    contentsAnimation.toValue           =  (__bridge id)([UIImage imageNamed:@"结束图片"].CGImage);
    contentsAnimation.duration          = 3.f;

    // 设定layer动画结束后的contents值
    self.imageLayer.contents         = (__bridge id)([UIImage imageNamed:@"结束图片"].CGImage);

    // 让layer开始执行动画
    [self.imageLayer addAnimation:contentsAnimation forKey:nil];
}

if (EXECUTE) {
    // 执行显式动画

    // 基于图片的动画
    CABasicAnimation *contentsAnimation = [CABasicAnimation animationWithKeyPath:@"contents"];
    contentsAnimation.fromValue         =  self.imageLayer.contents;
    contentsAnimation.toValue           =  (__bridge id)([UIImage imageNamed:@"结束图片"].CGImage);
    contentsAnimation.duration          = 0.5f;

    // 基于bounds的动画
    CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"];
    boundsAnimation.fromValue         = [NSValue valueWithCGRect:self.imageLayer.bounds];
    boundsAnimation.toValue           = [NSValue valueWithCGRect:CGRectMake(0, 0, 302/2.f, 707/2.f)];
    boundsAnimation.duration          = 0.5f;

    // 将基于图片的动画与基于bounds的动画组合起来
    CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
    groupAnimation.animations        = @[contentsAnimation, boundsAnimation];
    groupAnimation.duration          = 0.5f;

    // 设定layer动画结束后的contents值
    self.imageLayer.contents = (__bridge id)([UIImage imageNamed:@"结束图片"].CGImage);
    self.imageLayer.bounds   = CGRectMake(0, 0, 302/2.f, 707/2.f);

    // 让layer开始执行动画
    [self.imageLayer addAnimation:groupAnimation forKey:nil];
}
}

用CALayer实现复杂遮罩效果

  1. 遮罩原理的分析
  2. 用png图片作为CALayer中mask属性的遮罩Layer
  3. 移动该CALayer的mask的frame值实现遮罩动画效果

CALayer实现遮罩淡化滤镜

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) CALayer *imageLayer;
@property (nonatomic, strong) CALayer *maskLayer;

@property (nonatomic, strong) UIImage *contentImage;
@property (nonatomic, strong) UIImage *maskImage;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

self.view.backgroundColor = [UIColor blackColor];

// 处理图片
self.contentImage = [UIImage imageNamed:@"原始图片"];
self.maskImage    = [UIImage imageNamed:@"maskLayerContents"];


// 生成maskLayer
self.maskLayer          = [CALayer layer];
self.maskLayer.frame    = CGRectMake(0, 0, 427/2.f, 427/2.f);
self.maskLayer.contents = (__bridge id)(self.maskImage.CGImage);


// 生成contentsLayer
self.imageLayer          = [CALayer layer];
self.imageLayer.frame    = CGRectMake(0, 0, 427/2.f, 427/2.f);
self.imageLayer.contents = (__bridge id)(self.contentImage.CGImage);


// 给contentsLayer设定mask值
self.imageLayer.mask     = self.maskLayer;


// 将contentsLayer添加到layer中
[self.view.layer addSublayer:self.imageLayer];
}

CALayer实现遮罩平移

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) CALayer *imageLayer;
@property (nonatomic, strong) CALayer *maskLayer;

@property (nonatomic, strong) UIImage *imageContents;
@property (nonatomic, strong) UIImage *maskContents;


@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

self.view.backgroundColor = [UIColor blackColor];

// 获取图片
self.imageContents = [UIImage imageNamed:@"原始图片"];
self.maskContents  = [UIImage imageNamed:@"maskLayerContents"];

// 创建出图片layer
self.imageLayer          = [CALayer layer];
self.imageLayer.frame    = CGRectMake(50, 50, 200, 200);
self.imageLayer.contents = (__bridge id)(self.imageContents.CGImage);
[self.view.layer addSublayer:self.imageLayer];

// 创建出遮罩layer
self.maskLayer          = [CALayer layer];
self.maskLayer.frame    = self.imageLayer.bounds;
self.maskLayer.contents = (__bridge id)(self.maskContents.CGImage);
self.maskLayer.backgroundColor = [UIColor whiteColor].CGColor;

// 给图片layer提供遮罩的layer
self.imageLayer.mask = self.maskLayer;

// 3秒钟之后做maskLayer动画
[self performSelector:@selector(maskLayerAnimation)
           withObject:nil
           afterDelay:3.f];
       }

- (void)maskLayerAnimation {
self.maskLayer.frame = CGRectMake(50, 50, 200, 200);
}

@end

定时执行某方法

// 定时器
_timer = [NSTimer scheduledTimerWithTimeInterval:1.f
                                          target:self
                                        selector:@selector(timerEvent)
                                        userInfo:nil
                                         repeats:YES];

随机取值

//随机取0-200中的值
arc4random()%200

进度参数

//(取值范围为 %0 ~ %100)
@property (nonatomic, assign) CGFloat  progress;  

方法执行开关

#define NO_EXECUTE  0  // 不执行
#define EXECUTE     1  // 执行

// 不执行
if (NO_EXECUTE) {
....
}

// 执行
if (EXECUTE) {
.....
}

动画执行与最终设定

此类动画只是一种提交,并没有最终改变layer的设定。如果结束后,会很快返回到原先的设定。所以要在动画执行前设定好layer值

// 设定layer动画结束后的contents值
self.imageLayer.contents = (__bridge id)([UIImage imageNamed:@"结束图片"].CGImage);
self.imageLayer.bounds   = CGRectMake(0, 0, 302/2.f, 707/2.f);

// 让layer开始执行动画
[self.imageLayer addAnimation:groupAnimation forKey:nil];