美文网首页iOS DeveloperiOSiOS程序猿
IOS 自定义Animation属性动画

IOS 自定义Animation属性动画

作者: SumLin | 来源:发表于2016-09-20 17:35 被阅读0次

    前言

    在前端开发中少不了动画的元素,官方提供了我们很多不错的属性动画,但有时满足不了策划的需求,这时就需要我们自定义动画,自定义属性动画。既然要自定义动画,就少不了要对CAAnimation 子类的了解


    CAAnimation is an abstract animation class. It provides the basic support for the CAMediaTiming and CAAction protocols. To animate Core Animation layers or Scene Kit objects, create instances of the concrete subclasses CABasicAnimation, CAKeyframeAnimation, CAAnimationGroup, or CATransition.

    以上官方描述 CAAnimation是一个抽象动画类,遵行协议CAMediaTiming 和CAAction,实现的子类CABasicAnimation, CAKeyframeAnimation, CAAnimationGroup, CATransition.

    由于本文只用到关键帧动画,所以适当的对CAKeyframeAnimation进行下说明,如果要详细同学可以自行学习下
    CAKeyframeAnimation 比较关键两个属性 valuses 和path ,其它常用的属性可以查看CAMediaTiming 协议

    The keyframe values represent the values through which the animation must proceed. The time at which a given keyframe value is applied to the layer depends on the animation timing, which is controlled by the calculationMode, keyTimes, and timingFunctions properties. Values between keyframes are created using interpolation, unless the calculation mode is set to kCAAnimationDiscrete.

    Depending on the type of the property, you may need to wrap the values in this array with an NSNumber of NSValue object. For some Core Graphics data types, you may also need to cast them to id before adding them to the array.

    The values in this property are used only if the value in the path property is nil.

    以上valuse属性官方描述 关键是看最后一句 valuses 仅仅只是用于属性动画 也就是通过属性可以拿到valuses 的值path 则不能

    准备工作

    自定义Animation最终实现要实现的效果图



    在编写代码之前我们还需要准备一张图片


    CustomLayer IMP的实现

    <pre>
    @interface CustomLayer : CALayer
    @property CGFloat lighting;//自定义动画属性
    @end
    //CustomLayer.m
    -(instancetype) initWithLayer:(id)layer{
    if(self=[super initWithLayer:layer]){
    if([self isKindOfClass:[CustomLayer class]]){
    self.lighting=((CustomLayer*)layer).lighting;
    }
    }
    return self;
    }
    // layer首次加载时会调用 needsDisplayForKey:(NSString *)key方法来判断当前指定的属性key改变时 是否需要重新绘制。
    // 返回YES便会自动调用setNeedsDisplay方法,触发重绘
    +(BOOL)needsDisplayForKey:(NSString *)key{
    if([key isEqualToString:@"lighting"]){
    return YES;
    }
    return [super needsDisplayForKey:key];
    }

    CustomImageView.h
    @interface CustomImageView : UIView
    @property CGFloat cred,cgreed,cblue;
    @property (nonatomic,strong) UIImage *image;
    @property CGContextRef currentContext;
    @property (nonatomic,strong) UIColor *color;
    -(void)setAnimation;
    @end
    CustomImageView.m

    import "CustomImageView.h"

    import "CustomLayer.h"

    @implementation CustomImageView
    //重新下layerClass 将CustomImageView CALayer --> 换成CustomLayer

    • (Class)layerClass {
      return [CustomLayer class];
      }
    • (instancetype)init {
      if (self = [super init]) {
      self.layer.opaque = NO;
      [self setColor:[UIColor whiteColor]];
      }
      return self;
      }
    • (UIImage *)image {
      if (!_image) {
      _image = [UIImage imageNamed:@"bulb.png"];
      }
      return _image;
      }
      //设置灯泡颜色
    • (void)setColor:(UIColor *)color {
      _color = color;
      CGColorRef cgColor = color.CGColor;
      const CGFloat *colors = CGColorGetComponents(cgColor);
      self.cred = *colors * 255.0;
      self.cgreed = *(colors + 1) * 255.0;
      self.cblue = *(colors + 2) * 255.0;
      //基础动画
      //[self setAnimationFrom:0.0 To:255];
      }
      //BasicAnimation 实现
    • (void)setAnimationFrom:(CGFloat)begin To:(CGFloat)end {
      CABasicAnimation *theAnimation = [CABasicAnimation animation];
      theAnimation.duration = 1.0;
      theAnimation.timingFunction =
      [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
      theAnimation.fromValue = @(begin);
      theAnimation.toValue = @(end);
      theAnimation.removedOnCompletion=YES;
      [self.layer addAnimation:theAnimation forKey:@"lighting"];
      }
      // 关键帧动画
      -(void)setAnimation{
      CAKeyframeAnimation *thenAnimation=[CAKeyframeAnimation animation];
      thenAnimation.duration=1;
      thenAnimation.values=@[@(0),@(255),@(0),@(255),@(0),@(255)];
      thenAnimation.repeatCount=MAXFLOAT;
      [self.layer addAnimation:thenAnimation forKey:@"lighting"];
      }
      // 细心码猿也许会发现,这个明显为空,为毛还要写在这里。待会,后面进行说明
    • (void)drawRect:(CGRect)rect {

    }
    //图片绘制

    • (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
      CGFloat lighting = ((CustomLayer *)layer).lighting;
      CGFloat red = 255 - self.cred;
      CGFloat greed = 255 - self.cgreed;
      CGFloat blue = 255 - self.cblue;
      CGFloat curRed = self.cred + red * (lighting / 255.0f);
      CGFloat curGreed = self.cgreed + greed * (lighting / 255.0f);
      CGFloat curBlue = self.cblue + blue * (lighting / 255.0f);
      //创建一个基于位图的上下文
      UIGraphicsBeginImageContextWithOptions(self.image.size, YES, 0);
      //获取当前位图上下文
      self.currentContext = UIGraphicsGetCurrentContext();
      CGRect rect = CGContextGetClipBoundingBox(self.currentContext);
      UIColor *color = [UIColor colorWithRed:curRed / 255.0f
      green:curGreed / 255.0f
      blue:curBlue / 255.0f
      alpha:1.0];
      [color set];
      UIBezierPath *path = [UIBezierPath bezierPathWithRect:rect];
      // 颜色填充
      [path fill];
      CGContextDrawImage(self.currentContext, rect, _image.CGImage);
      //从当前位图上下文,获取绘制的图片
      UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
      //结束图片绘制
      UIGraphicsEndImageContext();
      const CGFloat maskingColors[6] = { 255.0, 255.0, 255.0, 255.0, 255.0, 255.0 };
      //创建一个颜色遮的图片
      CGImageRef finalImage = CGImageCreateWithMaskingColors( image.CGImage, maskingColors );

      CGContextDrawImage(ctx, self.bounds, finalImage);
      //回收CGImage
      CGImageRelease(finalImage);
      }
      </pre>

    CustomImageView.m IMP文件中,重写了drawRect 方法,但又什么代码都没有,能否去掉??

    先看下正常情况下UIView 自绘流程图

    • 通过流程UIView 只要重写drawRect: 才会自动执行layer 重绘工作,UIView 可以看作操作的画笔,CALayer是UIView的画布
    • 由于UIView 在创建CALayer 的时候会隐式 将本身赋值给layer.Delegate ,所以在重绘的时候,会优先判断是否实现了代理方法,实现则利用代理方法绘制,而在代理方法 drawlayer: inCentext: 中调用 [super drawlayer: inCentext:] 后,会再次 掉用 drawRect: 否则, layerDelegate 绘制的结果,便是最终显示的结果
    • setNeedDisplay: 不管调用用多少次,只会在下一帧同步到显示屏
    • 假如不重写drawRect: 可以手动调用[self.layer setNeedDisplay] layer 边可以重绘,达到相同的目的
    • 当实现drawlayer: 方法将不会调用 drawlayer:inCentext: 在该方法内不能调用父类[super drawlayer:]方法 否则将crash

    <code>UIGraphicsBeginImageContextWithOptions(self.image.size, YES, 0);​</code>
    创建一个基于位图的上下文,并将其设置为当前上下文(CGContextRef)
    size:参数size为新创建的位图上下文的大小。它同时是由UIGraphicsGetImageFromCurrentImageContext函数返回的图形大小。
    opaque:不透明,如果图形完全不用透明,设置为YES以优化位图的存储。 这里需要颜色遮罩设置为YES 默认情况 NO;
    scale:缩放因子
    <pre>const CGFloat maskingColors[6] = { 255.0, 255.0, 255.0, 255.0, 255.0, 255.0 };
    //创建一个颜色遮的图片
    CGImageRef finalImage = CGImageCreateWithMaskingColors( image.CGImage, maskingColors );
    将白色作为颜色遮罩,最终将图片绘制layer</pre>

    看到 (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx 这个方法是否觉得疑问 ??
    这里已经有CGContextRef参数传入 能否使用 ctx 绘制位图。 答案是可以

    <pre>
    CGFloat lighting = ((CustomLayer *)layer).lighting;
    CGFloat red = 255 - self.cred;
    CGFloat greed = 255 - self.cgreed;
    CGFloat blue = 255 - self.cblue;
    CGFloat curRed = self.cred + red * (lighting / 255.0f);
    CGFloat curGreed = self.cgreed + greed * (lighting / 255.0f);
    CGFloat curBlue = self.cblue + blue * (lighting / 255.0f);
    UIGraphicsBeginImageContextWithOptions(self.image.size, YES, 1.0f);
    UIGraphicsPushContext(ctx);
    // self.currentContext = UIGraphicsGetCurrentContext();
    CGRect rect = CGContextGetClipBoundingBox(ctx);
    UIColor *color = [UIColor colorWithRed:curRed / 255.0f
    green:curGreed / 255.0f
    blue:curBlue / 255.0f
    alpha:1.0];
    [color set];
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:rect];
    [path fill];
    CGContextTranslateCTM(ctx, 0, self.bounds.size.height);
    CGContextScaleCTM(ctx, 1.0, -1.0);
    CGContextDrawImage(ctx, rect, _image.CGImage);
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsPopContext();
    UIGraphicsEndImageContext();
    const CGFloat maskingColors[6] = {255.0, 255.0, 255.0, 255.0, 248.0, 255.0};
    CGImageRef finalImage =
    CGImageCreateWithMaskingColors(image.CGImage, maskingColors);

    CGContextDrawImage(ctx, rect, finalImage);
    CGImageRelease(finalImage);
    </pre>

    利用UIGraphicsPushContext(ctx); 切换ctx 为当前上下文
    UIGraphicsPopContext(ctx); 切换为原来的上下文
    得到图片是倒立的 设置下
    <pre>
    CGContextTranslateCTM(ctx, 0, self.bounds.size.height);
    CGContextScaleCTM(ctx, 1.0, -1.0);
    </pre>
    至于原因可以去理解UIKit坐标系与Quartz(Core Graphics坐标系)
    将该方法中内容换成以上代码,可以得到我们想要的图片结果,但CGImageCreateWithMaskingColors
    颜色遮罩将没有效果,why??
    很简单,我们之前说了,颜色遮罩图片需要不透明,而传入CGCentextRef 绘制图片默认是透明的 所以CGImageCreateWithMaskingColors便会不起效果

    ViewController中使用

    <pre>

    • (void)viewDidLoad {
      [super viewDidLoad];
      self.view.backgroundColor = [UIColor yellowColor];
      _imageView = [CustomImageView new];
      UIButton *greedBtn = [UIButton buttonWithType:UIButtonTypeCustom];
      [greedBtn addTarget:self action:@selector(onClick:)
      forControlEvents:UIControlEventTouchUpInside];
      greedBtn.backgroundColor = [UIColor greenColor];
      greedBtn.tag = BTN_TAG;
      UIButton *blueBtn = [UIButton buttonWithType:UIButtonTypeCustom];
      [blueBtn addTarget:self
      action:@selector(onClick:)
      forControlEvents:UIControlEventTouchUpInside];
      blueBtn.backgroundColor = [UIColor blueColor];
      [self.view addSubview:_imageView];
      [self.view addSubview:greedBtn];
      [self.view addSubview:blueBtn];

    //---------------AutoLayout 布局可以跳过 ---------------
    _imageView.translatesAutoresizingMaskIntoConstraints = NO;
    NSLayoutConstraint *constraintW =[NSLayoutConstraint constraintWithItem:_imageView
    attribute:NSLayoutAttributeWidth
    relatedBy:NSLayoutRelationEqual
    toItem:nil
    attribute:NSLayoutAttributeNotAnAttribute
    multiplier:1.0
    constant:.25 * self.view.frame.size.width];
    NSLayoutConstraint *constraintH =
    [NSLayoutConstraint constraintWithItem:_imageView
    attribute:NSLayoutAttributeHeight
    relatedBy:NSLayoutRelationEqual
    toItem:nil
    attribute:NSLayoutAttributeNotAnAttribute
    multiplier:1.0
    constant:.20 * self.view.frame.size.height];
    NSLayoutConstraint *constraintX =
    [NSLayoutConstraint constraintWithItem:_imageView
    attribute:NSLayoutAttributeCenterX
    relatedBy:NSLayoutRelationEqual
    toItem:self.view
    attribute:NSLayoutAttributeCenterX
    multiplier:1.0
    constant:0];
    NSLayoutConstraint *constraintY =
    [NSLayoutConstraint constraintWithItem:_imageView
    attribute:NSLayoutAttributeCenterY
    relatedBy:NSLayoutRelationEqual
    toItem:self.view
    attribute:NSLayoutAttributeCenterY
    multiplier:1.0
    constant:0];

    constraintW.active = YES;//8.0后加入
    constraintH.active = YES;
    constraintX.active = YES;
    constraintY.active = YES;

    NSDictionary *views =
    NSDictionaryOfVariableBindings(greedBtn, blueBtn, _imageView);
    greedBtn.translatesAutoresizingMaskIntoConstraints = NO;
    blueBtn.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view
    addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:
    @"V:[_imageView]-1-[greedBtn(30)]"
    options:0
    metrics:nil
    views:views]];
    [self.view
    addConstraints:[NSLayoutConstraint
    constraintsWithVisualFormat:@"V:[blueBtn(greedBtn)]"
    options:0
    metrics:nil
    views:views]];
    [self.view
    addConstraints:[NSLayoutConstraint
    constraintsWithVisualFormat:
    @"H:[greedBtn(30)]-20-[blueBtn(greedBtn)]"
    options:NSLayoutFormatAlignAllTop
    metrics:nil
    views:views]];
    constraintX = [NSLayoutConstraint constraintWithItem:greedBtn
    attribute:NSLayoutAttributeCenterX
    relatedBy:NSLayoutRelationEqual
    toItem:self.view
    attribute:NSLayoutAttributeCenterX
    multiplier:1
    constant:-25];
    constraintX.active = YES;
    }
    </pre>
    动画执行方法
    <pre>- (void)onClick:(UIButton *)sender {
    if (sender.tag == BTN_TAG) {
    [_imageView setColor:[UIColor greenColor]];
    [_imageView setAnimation];
    } else {
    [_imageView setColor:[UIColor blueColor]];
    [_imageView setAnimation];
    }
    }</pre>

    结尾

    以上观点如有错误之处欢迎留言,博文是在工作间写的,细节方面可能不到位,欢迎指正

    相关文章

      网友评论

        本文标题:IOS 自定义Animation属性动画

        本文链接:https://www.haomeiwen.com/subject/ptsoettx.html