IOS 自定义Animation属性动画

前言

在前端开发中少不了动画的元素,官方提供了我们很多不错的属性动画,但有时满足不了策划的需求,这时就需要我们自定义动画,自定义属性动画。既然要自定义动画,就少不了要对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>

结尾

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

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

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,488评论 6 30
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,110评论 5 13
  • 书写的很好,翻译的也棒!感谢译者,感谢感谢! iOS-Core-Animation-Advanced-Techni...
    钱嘘嘘阅读 2,297评论 0 6
  • 核心动画(Core Animation)一、Core animation简单介绍 1.Core Animatio...
    北辰青阅读 1,032评论 0 1
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,096评论 1 23