前言
在前端開發(fā)中少不了動畫的元素,官方提供了我們很多不錯的屬性動畫,但有時滿足不了策劃的需求,這時就需要我們自定義動畫,自定義屬性動畫。既然要自定義動畫,就少不了要對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是一個抽象動畫類,遵行協(xié)議CAMediaTiming 和CAAction,實現(xiàn)的子類CABasicAnimation, CAKeyframeAnimation, CAAnimationGroup, CATransition.
由于本文只用到關(guān)鍵幀動畫,所以適當(dāng)?shù)膶AKeyframeAnimation進行下說明,如果要詳細同學(xué)可以自行學(xué)習(xí)下
CAKeyframeAnimation 比較關(guān)鍵兩個屬性 valuses 和path ,其它常用的屬性可以查看CAMediaTiming 協(xié)議
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屬性官方描述 關(guān)鍵是看最后一句 valuses 僅僅只是用于屬性動畫 也就是通過屬性可以拿到valuses 的值path 則不能
準(zhǔn)備工作
自定義Animation最終實現(xiàn)要實現(xiàn)的效果圖

在編寫代碼之前我們還需要準(zhǔn)備一張圖片

CustomLayer IMP的實現(xiàn)
<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首次加載時會調(diào)用 needsDisplayForKey:(NSString *)key方法來判斷當(dāng)前指定的屬性key改變時 是否需要重新繪制。
// 返回YES便會自動調(diào)用setNeedsDisplay方法,觸發(fā)重繪
+(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;
}
//設(shè)置燈泡顏色 - (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;
//基礎(chǔ)動畫
//[self setAnimationFrom:0.0 To:255];
}
//BasicAnimation 實現(xiàn) - (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"];
}
// 關(guān)鍵幀動畫
-(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"];
}
// 細心碼猿也許會發(fā)現(xiàn),這個明顯為空,為毛還要寫在這里。待會,后面進行說明 - (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);
//創(chuàng)建一個基于位圖的上下文
UIGraphicsBeginImageContextWithOptions(self.image.size, YES, 0);
//獲取當(dāng)前位圖上下文
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);
//從當(dāng)前位圖上下文,獲取繪制的圖片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//結(jié)束圖片繪制
UIGraphicsEndImageContext();
const CGFloat maskingColors[6] = { 255.0, 255.0, 255.0, 255.0, 255.0, 255.0 };
//創(chuàng)建一個顏色遮的圖片
CGImageRef finalImage = CGImageCreateWithMaskingColors( image.CGImage, maskingColors );CGContextDrawImage(ctx, self.bounds, finalImage);
//回收CGImage
CGImageRelease(finalImage);
}
</pre>
CustomImageView.m IMP文件中,重寫了drawRect 方法,但又什么代碼都沒有,能否去掉??
先看下正常情況下UIView 自繪流程圖

- 通過流程UIView 只要重寫drawRect: 才會自動執(zhí)行l(wèi)ayer 重繪工作,UIView 可以看作操作的畫筆,CALayer是UIView的畫布
- 由于UIView 在創(chuàng)建CALayer 的時候會隱式 將本身賦值給layer.Delegate ,所以在重繪的時候,會優(yōu)先判斷是否實現(xiàn)了代理方法,實現(xiàn)則利用代理方法繪制,而在代理方法 drawlayer: inCentext: 中調(diào)用 [super drawlayer: inCentext:] 后,會再次 掉用 drawRect: 否則, layerDelegate 繪制的結(jié)果,便是最終顯示的結(jié)果
- setNeedDisplay: 不管調(diào)用用多少次,只會在下一幀同步到顯示屏
- 假如不重寫drawRect: 可以手動調(diào)用[self.layer setNeedDisplay] layer 邊可以重繪,達到相同的目的
- 當(dāng)實現(xiàn)drawlayer: 方法將不會調(diào)用 drawlayer:inCentext: 在該方法內(nèi)不能調(diào)用父類[super drawlayer:]方法 否則將crash
<code>UIGraphicsBeginImageContextWithOptions(self.image.size, YES, 0);?</code>
創(chuàng)建一個基于位圖的上下文,并將其設(shè)置為當(dāng)前上下文(CGContextRef)
size:參數(shù)size為新創(chuàng)建的位圖上下文的大小。它同時是由UIGraphicsGetImageFromCurrentImageContext函數(shù)返回的圖形大小。
opaque:不透明,如果圖形完全不用透明,設(shè)置為YES以優(yōu)化位圖的存儲。 這里需要顏色遮罩設(shè)置為YES 默認(rèn)情況 NO;
scale:縮放因子
<pre>const CGFloat maskingColors[6] = { 255.0, 255.0, 255.0, 255.0, 255.0, 255.0 };
//創(chuàng)建一個顏色遮的圖片
CGImageRef finalImage = CGImageCreateWithMaskingColors( image.CGImage, maskingColors );
將白色作為顏色遮罩,最終將圖片繪制layer</pre>
看到 (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx 這個方法是否覺得疑問 ??
這里已經(jīng)有CGContextRef參數(shù)傳入 能否使用 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 為當(dāng)前上下文
UIGraphicsPopContext(ctx); 切換為原來的上下文
得到圖片是倒立的 設(shè)置下
<pre>
CGContextTranslateCTM(ctx, 0, self.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
</pre>
至于原因可以去理解UIKit坐標(biāo)系與Quartz(Core Graphics坐標(biāo)系)
將該方法中內(nèi)容換成以上代碼,可以得到我們想要的圖片結(jié)果,但CGImageCreateWithMaskingColors
顏色遮罩將沒有效果,why??
很簡單,我們之前說了,顏色遮罩圖片需要不透明,而傳入CGCentextRef 繪制圖片默認(rèn)是透明的 所以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>
動畫執(zhí)行方法
<pre>- (void)onClick:(UIButton *)sender {
if (sender.tag == BTN_TAG) {
[_imageView setColor:[UIColor greenColor]];
[_imageView setAnimation];
} else {
[_imageView setColor:[UIColor blueColor]];
[_imageView setAnimation];
}
}</pre>
結(jié)尾
以上觀點如有錯誤之處歡迎留言,博文是在工作間寫的,細節(jié)方面可能不到位,歡迎指正