iOS 为CALayer添加可动画的属性(以二维码切换扫描区域为例)

原创Blog,转载请注明出处
http://blog.csdn.net/hello_hwc?viewmode=list
我的stackoverflow

profile for Leo on Stack Exchange, a network of free, community-driven Q&A sites

我的Github
https://github.com/LeoMobileDeveloper


前言:当我们去修改一个CALayer的属性的时候,通常是会自动创建渐变的动画。但是,很多时候,默认的属性并不能满足我们的要求,我们希望自定义的属性也有动画。这就是本文要讲解的内容


先看看效果

动画
iOS 为CALayer添加可动画的属性(以二维码切换扫描区域为例)_第1张图片
在实际设备上如图

iOS 为CALayer添加可动画的属性(以二维码切换扫描区域为例)_第2张图片


核心步骤

添加一个自定义的属性

本文中,就是感兴趣的扫描区域

@property (nonatomic)CGRect focusRect;

设置为@dynamic ,这样做是为了让CoreAnimation自动帮我门计算,在切换动画的时候每一帧对应focusRect的值

@implementation LHBackgroundLayer

@dynamic focusRect;
//****
@end

调用drawInContext或者display来绘制想要的图形

在这个例子中,四个边角,包括绘制明暗相间的区域,都是调用CoreGraphics的代码绘制的

-(void)drawInContext:(CGContextRef)ctx{
    UIBezierPath * focusPath = [self createBezierPathWithBounds:self.bounds focusRect:self.focusRect];
    CGContextAddPath(ctx, focusPath.CGPath);
    CGContextSetFillColorWithColor(ctx, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5].CGColor);
    CGContextEOFillPath(ctx);

    CGContextSetStrokeColorWithColor(ctx, [UIColor greenColor].CGColor);
    CGContextSetLineWidth(ctx,2.0);


    CGContextMoveToPoint(ctx, self.focusRect.origin.x + CORNERLENGTH, self.focusRect.origin.y);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x,self.focusRect.origin.y);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x, self.focusRect.origin.y + CORNERLENGTH);
    CGContextStrokePath(ctx);

    CGContextMoveToPoint(ctx, self.focusRect.origin.x + self.focusRect.size.width - CORNERLENGTH, self.focusRect.origin.y);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x + self.focusRect.size.width, self.focusRect.origin.y);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x + self.focusRect.size.width, self.focusRect.origin.y + CORNERLENGTH);
    CGContextStrokePath(ctx);

    CGContextMoveToPoint(ctx, self.focusRect.origin.x + self.focusRect.size.width, self.focusRect.origin.y + self.focusRect.size.height - CORNERLENGTH);
    CGContextAddLineToPoint(ctx,self.focusRect.origin.x + self.focusRect.size.width,self.focusRect.origin.y + self.focusRect.size.height);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x + self.focusRect.size.width - CORNERLENGTH, self.focusRect.origin.y + self.focusRect.size.height);
    CGContextStrokePath(ctx);

    CGContextMoveToPoint(ctx, self.focusRect.origin.x, self.focusRect.origin.y - CORNERLENGTH + self.focusRect.size.height);
    CGContextAddLineToPoint(ctx,self.focusRect.origin.x,self.focusRect.origin.y+self.focusRect.size.height);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x + CORNERLENGTH, self.focusRect.origin.y + self.focusRect.size.height);
    CGContextStrokePath(ctx);

    UIBezierPath * beziper = [UIBezierPath bezierPathWithRect:self.focusRect];
    CGContextAddPath(ctx, beziper.CGPath);
    CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
    CGContextSetLineWidth(ctx, 0.5);
    CGContextStrokePath(ctx);
}
-(UIBezierPath *)createBezierPathWithBounds:(CGRect)bounds focusRect:(CGRect)focusRect{
    UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:bounds];
    UIBezierPath * focusPath = [UIBezierPath bezierPathWithRect:focusRect];
    [bezierPath appendPath:focusPath];
    return bezierPath;
}

小的tips

明暗相间的效果是通过 CGContextEOFillPath(ctx);奇偶fill的方式来绘制的


重写needsDisplayForKey,让自定义属性改变的时候,layer重新绘制

+(BOOL)needsDisplayForKey:(NSString *)key{
    if ([key isEqualToString:@"focusRect"]) {
        return true;
    }
    return [super needsDisplayForKey:key];
}

重写-(id)actionForKey:(NSString *)event,返回属性切换的时候的动画

-(id)actionForKey:(NSString *)event{
    if ([event isEqualToString:@"focusRect"]) {
        CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:event];
        anim.fromValue = [[self presentationLayer] valueForKey:event];
        anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        anim.delegate = self;
        anim.duration = self.animateDurationWhenFocusChange;
        [anim setValue:@"leoStateChageAnimation" forKey:@"lhCodeKey"];
        return anim;
    }
    return [super actionForKey:event];
}

小的tips

关于Layer有三个model tree,其中一个是CoreAnimation私有的,一般无需考虑。还有两个,其中一个是presentationLayer,一个是实际的model layer,其中通过presentationLayer可以获得动画的过程中每一帧layer的属性值

到这里,为calayer添加一个可动画的属性部分就完成了


最后,贴上所有这个文件的代码,

头文件

//
//  LHBackgroundLayer.h
//  LeoCodeScanner
//
//  Created by huangwenchen on 16/1/11.
//  Copyright © 2016年 LeoHuang. All rights reserved.
//

#import 
#import 
@interface LHBackgroundLayer : CALayer

-(instancetype)initWithBounds:(CGRect)bounds BackgroundColor:(UIColor *)backgroundColor focusRect:(CGRect)focusRect;


-(void)updateFocus:(CGRect)focusRect Completion:(void(^)(void))completion;

@property (assign,nonatomic)CGFloat animateDurationWhenFocusChange;

@end

.m文件

//
//  LHBackgroundLayer.m
//  LeoCodeScanner
//
//  Created by huangwenchen on 16/1/11.
//  Copyright © 2016年 LeoHuang. All rights reserved.
//

#import "LHBackgroundLayer.h"
#import "CALayer+LHHelper.h"
#import "LHGradientLineLayer.h"

#define CORNERLENGTH 15

@interface LHBackgroundLayer()

@property (strong,nonatomic)LHGradientLineLayer * lineLayer;

@property (nonatomic)CGRect focusRect;
@property (copy,nonatomic)void(^completion)(void);
@end


@implementation LHBackgroundLayer

@dynamic focusRect;

-(instancetype)initWithBounds:(CGRect)bounds BackgroundColor:(UIColor *)backgroundColor focusRect:(CGRect)focusRect{
    if (self = [super init]) {
        self.anchorPoint = CGPointZero;
        self.position = CGPointZero;
        self.bounds = bounds;
        self.animateDurationWhenFocusChange = 0.4;
        self.contentsScale = [UIScreen mainScreen].scale;
        self.needsDisplayOnBoundsChange = true;
        self.focusRect = focusRect;
        self.lineLayer = [[LHGradientLineLayer alloc] initWithColor:[UIColor greenColor] Frame:CGRectMake(focusRect.origin.x, focusRect.origin.y, focusRect.size.width, 1)];
        [self addSublayer:self.lineLayer];
        [self setNeedsDisplay];
        [self startAnimate];
    }
    return self;
}

+(BOOL)needsDisplayForKey:(NSString *)key{
    if ([key isEqualToString:@"focusRect"]) {
        return true;
    }
    return [super needsDisplayForKey:key];
}
-(id)actionForKey:(NSString *)event{
    if ([event isEqualToString:@"focusRect"]) {
        CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:event];
        anim.fromValue = [[self presentationLayer] valueForKey:event];
        anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        anim.delegate = self;
        anim.duration = self.animateDurationWhenFocusChange;
        [anim setValue:@"leoStateChageAnimation" forKey:@"lhCodeKey"];
        return anim;
    }
    return [super actionForKey:event];
}
-(void)animationDidStart:(CAAnimation *)anim{
    if ([[anim valueForKey:@"lhCodeKey"] isEqualToString:@"leoStateChageAnimation"]) {
        [self stopAnimate];
    }
}
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    if ([[anim valueForKey:@"lhCodeKey"] isEqualToString:@"leoStateChageAnimation"]) {
        [self startAnimate];
        if (self.completion) {
            self.completion();
            self.completion = nil;
        }
    }
}
-(void)updateFocus:(CGRect)focusRect Completion:(void (^)(void))completion{
    self.focusRect = focusRect;
    self.completion = completion;
}

-(void)drawInContext:(CGContextRef)ctx{
    UIBezierPath * focusPath = [self createBezierPathWithBounds:self.bounds focusRect:self.focusRect];
    CGContextAddPath(ctx, focusPath.CGPath);
    CGContextSetFillColorWithColor(ctx, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5].CGColor);
    CGContextEOFillPath(ctx);

    CGContextSetStrokeColorWithColor(ctx, [UIColor greenColor].CGColor);
    CGContextSetLineWidth(ctx,2.0);


    CGContextMoveToPoint(ctx, self.focusRect.origin.x + CORNERLENGTH, self.focusRect.origin.y);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x,self.focusRect.origin.y);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x, self.focusRect.origin.y + CORNERLENGTH);
    CGContextStrokePath(ctx);

    CGContextMoveToPoint(ctx, self.focusRect.origin.x + self.focusRect.size.width - CORNERLENGTH, self.focusRect.origin.y);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x + self.focusRect.size.width, self.focusRect.origin.y);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x + self.focusRect.size.width, self.focusRect.origin.y + CORNERLENGTH);
    CGContextStrokePath(ctx);

    CGContextMoveToPoint(ctx, self.focusRect.origin.x + self.focusRect.size.width, self.focusRect.origin.y + self.focusRect.size.height - CORNERLENGTH);
    CGContextAddLineToPoint(ctx,self.focusRect.origin.x + self.focusRect.size.width,self.focusRect.origin.y + self.focusRect.size.height);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x + self.focusRect.size.width - CORNERLENGTH, self.focusRect.origin.y + self.focusRect.size.height);
    CGContextStrokePath(ctx);

    CGContextMoveToPoint(ctx, self.focusRect.origin.x, self.focusRect.origin.y - CORNERLENGTH + self.focusRect.size.height);
    CGContextAddLineToPoint(ctx,self.focusRect.origin.x,self.focusRect.origin.y+self.focusRect.size.height);
    CGContextAddLineToPoint(ctx, self.focusRect.origin.x + CORNERLENGTH, self.focusRect.origin.y + self.focusRect.size.height);
    CGContextStrokePath(ctx);

    UIBezierPath * beziper = [UIBezierPath bezierPathWithRect:self.focusRect];
    CGContextAddPath(ctx, beziper.CGPath);
    CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
    CGContextSetLineWidth(ctx, 0.5);
    CGContextStrokePath(ctx);
}
-(UIBezierPath *)createBezierPathWithBounds:(CGRect)bounds focusRect:(CGRect)focusRect{
    UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:bounds];
    UIBezierPath * focusPath = [UIBezierPath bezierPathWithRect:focusRect];
    [bezierPath appendPath:focusPath];
    return bezierPath;
}
-(void)startAnimate{
    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue
                     forKey:kCATransactionDisableActions];
    self.lineLayer.frame = CGRectMake(self.focusRect.origin.x, self.focusRect.origin.y, self.focusRect.size.width, 1);
    self.lineLayer.hidden = false;
    [CATransaction commit];

    CABasicAnimation * animation = [CABasicAnimation animation];
    animation.keyPath = @"position.y";
    animation.toValue = @(self.focusRect.origin.y + self.focusRect.size.height);
    animation.duration = 1.5;
    animation.repeatCount = HUGE_VALF;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [self.lineLayer addAnimation:animation forKey:@"linePositionAnimation"];
}
-(void)stopAnimate{
    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue
                     forKey:kCATransactionDisableActions];
    self.lineLayer.hidden = true;
    [CATransaction commit];
    [self.lineLayer removeAllAnimations];
}
@end

你可能感兴趣的:(CoreAnimation,iOS进阶)