記錄iOS動畫的學習及實踐html
目錄git
• 顯示層(UIView)動畫 ◦ 初級動畫 ◦ 關鍵幀動畫 ◦ 逐幀動畫 ◦ Gif動畫的處理 • 內容層(CALayer)動畫 ◦ CoreAnimation結構 ◦ CABasicAnimation ◦ CAKeyFrameAnimation ◦ CAGroupAnimation ◦ CATransition ◦ CAEmitterLayer 粒子動畫 ◦ CAGradientLayer 梯度動畫 ◦ CAShapeLayer 繪製動畫 ◦ CAReplicatorLayer 複製動畫
根據動畫的實現「位置」,能夠分爲 顯示層(UIView)動畫 和 內容層(CALayer)動畫 github
初級動畫,是最爲常見的一種動畫。經過UIView屬性的修改,配合UIView的兩種動畫apiblock
、方法形式
實現web
api使用示範spring
// 動畫方法形式 [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.35f]; [UIView setAnimationRepeatAutoreverses:NO];// 自動翻轉 [UIView setAnimationDelegate:self];// 需設置代理 [UIView setAnimationDidStopSelector:@selector(testForAutoreverses)]; self.testButton.backgroundColor = [UIColor lightGrayColor]; [UIView commitAnimations]; // 動畫block形式 [UIView animateWithDuration:2.0f animations:^{ self.testButton.backgroundColor = [UIColor whiteColor]; }];
根據簡單動畫的效果,最多見的顯示層(UIView)屬性修改分爲如下幾類api
frame
屬性相關的位置形狀變化alpha
屬性相關transform
屬性相關若是但願使用彈跳效果,系統也一樣提供了api供開發者使用數組
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion
參數介紹:dampingRatio
:速度衰減比例。取值範圍0 ~ 1,值越低震動越強velocity
:初始速度,越大速度越快xcode
使咱們具有控制一段連貫時間內,精確到某個時間點(關鍵幀)上的連續變化的能力緩存
// 系統api + (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0); + (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations NS_AVAILABLE_IOS(7_0); // start time and duration are values between 0.0 and 1.0 specifying time and duration relative to the overall time of the keyframe animation
api中的options
參數解釋 官方文檔app
UIViewKeyframeAnimationOptionCalculationModeLinear = 0 << 10, // 線性運算模式.The option to use a simple linear calculation when interpolating between keyframe values. UIViewKeyframeAnimationOptionCalculationModeDiscrete = 1 << 10, // 離散運算模式,展現關鍵幀.The option to not interpolate between keyframe values, but rather to jump directly to each new keyframe value. UIViewKeyframeAnimationOptionCalculationModePaced = 2 << 10, // 均勻運算模式.The option to compute intermediate keyframe values using a simple pacing algorithm. This option results in an evenly paced animation. UIViewKeyframeAnimationOptionCalculationModeCubic = 3 << 10, // The option to compute intermediate frames using a default Catmull-Rom spline that passes through the keyframe values. You cannot adjust the parameters of this algorithm. UIViewKeyframeAnimationOptionCalculationModeCubicPaced = 4 << 10 // The option to compute intermediate frames using the cubic scheme while ignoring the timing properties of the animation. Instead, timing parameters are calculated implicitly to give the animation a constant velocity.
最後兩種Cubic
運算模式,附上實驗代碼及效果圖:(可達鴨眉頭一皺,發現這兩個參數並不簡單)
- (void)viewDidLoad { [super viewDidLoad]; self.redCircle = [[UIView alloc] init]; _redCircle.backgroundColor = [UIColor redColor]; _redCircle.frame = CGRectMake(0, 0, 50, 50); _redCircle.layer.cornerRadius = 5; [_redCircle.layer masksToBounds]; [self.view addSubview:_redCircle]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self keyframeAnimation]; } - (void)keyframeAnimation{ [UIView animateKeyframesWithDuration:10 delay:0 options:UIViewKeyframeAnimationOptionCalculationModeCubic animations:^{ [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:1/5.0 animations:^{ self.redCircle.frame = CGRectMake(50, 200, 50, 50); }]; [UIView addKeyframeWithRelativeStartTime:1/5.0 relativeDuration:1/5.0 animations:^{ self.redCircle.frame = CGRectMake(100, 250, 50, 50); }]; [UIView addKeyframeWithRelativeStartTime:2/5.0 relativeDuration:1/5.0 animations:^{ self.redCircle.frame = CGRectMake(150, 275, 50, 50); }]; [UIView addKeyframeWithRelativeStartTime:3/5.0 relativeDuration:1/5.0 animations:^{ self.redCircle.frame = CGRectMake(200, 350, 50, 50); }]; [UIView addKeyframeWithRelativeStartTime:4/5.0 relativeDuration:1/5.0 animations:^{ self.redCircle.frame = CGRectMake(250, 500, 50, 50); }]; } completion:^(BOOL finished) { }]; }
CalculationModeCubic
是一種特殊的貝塞爾曲線(關於貝塞爾曲線原理,文下文中有詳細解釋),就像官方文檔中指出的,經過default Catmull-Rom spline
在關鍵幀值中插入兩個運算點,讓貝塞爾曲線可以穿過全部的點而實現運算得出的動畫速度更加平滑,防止出現因動畫時間太短形成的動畫突變的狀況參考資料
CalculationModeCubicPaced
:暫且解釋爲均勻平滑模式,多是由於忽略了時間屬性?特殊狀況下仍會出現突轉的狀況經過實例對比,不論是下落仍是突轉,cubic
的效果都更加平滑
self.view.backgroundColor = [UIColor lightGrayColor]; self.animationView = [[UIImageView alloc] init]; self.animationView.image = [UIImage imageNamed:@"m2_10001"]; [self.animationView sizeToFit]; self.animationView.center = self.view.center; [self.view addSubview:self.animationView]; NSMutableArray *imgArr = [NSMutableArray new]; for (int i = 1; i < 67; i ++) { UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"m2_1%04d",i]]; [imgArr addObject:img]; } self.animationView.animationImages = [imgArr copy]; self.animationView.animationDuration = 67/30.0; [self.animationView startAnimating];
NSTimer
或者CADisplayLink
實現幀速率的控制,設置好相應的timeinterval
或者frameinterval
便可。( CADisplayLink
固定刷新率60Hz,frameInterval屬性的賦值n,實際間隔時間爲n x 1/60`,頻率固定,推薦使用 )Image I/O框架提供了不透明數據類型來讀取圖像數據和寫圖像數據到一個目的地(CGImageSourceRef和CGImageDestinationRef)。它支持不少圖像格式,包括標準web格式、搞動態範圍圖像,和原始相機數據。Image I/O還有許多其餘特性:
1.mac平臺上的最快的圖像解碼和編碼。
2.逐步加載圖片的能力。
3.支持圖像元數據。
4.緩存效果。
你能夠經過下面的對象來建立image source和image destination:
1.URL:即CFURLRef對象。
2.Core Foundation對象:CFDataRef和CFmutableDataRef。
3.Quartz data consumer(CGDataConsumerRef)和data provider(CGDataProviderRef)對象。
1.使用Image I/O框架: #import <ImageIO/ImageIO.h>
2.使用UTType:#import <MobileCoreServices/MobileCoreServices.h>
二、支持的圖像格式:例如JPEG、JPEG2000、RAW、TIFF、BMP、PNG。在不一樣的平臺不是全部的格式都支持。你能夠調用下列函數來得到支持的格式:
1)CGImageSourceCopyTypeIdentifiers:返回同一類型修飾符的數組,表示支持的圖像源
2)CGImageDestinationCopyTypeIdentifiers:返回支持的目的地的Uniform Type Identifiers(UTIs)
相關知識連接
image i/o
UTType官方文檔
UTIs
iOS原生是不支持gif格式圖片的,因此當咱們想要使用一張gif圖片時,須要將gif圖片經過image i/o
轉換成相應的序列幀圖片後,生成UIImage
使用
具體步驟以下圖
咱們要使用的GIF圖片
下面請看咱們轉換的具體代碼:
NSString *path = [[NSBundle mainBundle] pathForResource:@"gakki2" ofType:@"gif"]; UIImage *animateImg = [UIImage imageWithContentsOfFile:path]; UIImageView *gifImageView = [[UIImageView alloc] initWithImage:animateImg]; [gifImageView sizeToFit]; gifImageView.center = self.view.center; [self.view addSubview:gifImageView];
看一下效果
不出所料,果真直接使用是行不通的對嗎?可是Gakki仍是很好看有木有~
咱們按照正確的方式打開
/** * GIF -> NSData -> ImageIO -> UIImage */ - (void)testOfGif{ NSString *path = [[NSBundle mainBundle] pathForResource:@"gakki2" ofType:@"gif"]; // UIImage *animateImg = [UIImage imageWithContentsOfFile:path]; NSData *gifData = [NSData dataWithContentsOfFile:path]; CGImageSourceRef gifdataSource = CGImageSourceCreateWithData((__bridge CFDataRef)gifData, NULL); size_t count = CGImageSourceGetCount(gifdataSource); UIImage *animateImg = nil; if (count <= 1) { animateImg = [UIImage imageWithData:gifData]; } else { NSMutableArray *imgArr = [[NSMutableArray alloc] init]; CGFloat timeinterval = 0.0f; for (size_t i = 0; i < count; i ++) { CGImageRef imgRf = CGImageSourceCreateImageAtIndex(gifdataSource, i, NULL); if (!imgRf) { continue; } timeinterval += [self getKeyFrameDurationWithIndex:i andSoureceRef:gifdataSource]; UIImage *img = [UIImage imageWithCGImage:imgRf scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]; [imgArr addObject:img]; CGImageRelease(imgRf); } CFRelease(gifdataSource); animateImg = [UIImage animatedImageWithImages:imgArr duration:timeinterval]; } UIImageView *gifImageView = [[UIImageView alloc] initWithImage:animateImg]; [gifImageView sizeToFit]; gifImageView.center = self.view.center; [self.view addSubview:gifImageView]; } - (CGFloat)getKeyFrameDurationWithIndex:(size_t )index andSoureceRef:(CGImageSourceRef )soureRef{ CGFloat frameDuration = 0.1f; CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(soureRef, index, NULL); NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties; NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary]; NSNumber *duration = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime]; if (duration) { frameDuration = duration.floatValue; } else { NSNumber *duration = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime]; if (duration) frameDuration = duration.floatValue; } if (frameDuration < 0.011f) { frameDuration = 0.1f; } CFRelease(cfFrameProperties); return frameDuration; }
注意點:
image i/o
屬於Core Foundation
,因此出現了大量的__bridge
處理。書寫的時候有點不習慣,不過看一下xcode的錯誤信息便能知道緣由CGImageSourceRef
讀取單幀圖片的時間信息。這裏須要注意一下,由於咱們在用序列幀圖片數組合成gif
文件時,須要對每一張圖片的該信息賦值CFRelease
操做SDWebImage
中相關處理的備註作出解釋:if (frameDuration < 0.011f) { frameDuration = 0.1f; }
SDWebImage: UIImage + GIF.m
Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
We follow Firefox's behavior and use a duration of 100 ms for any frames that specify a duration of <= 10 ms. See <rdar://problem/7689300> and http://webkit.org/b/36082 for more information.
看一下咱們運行的效果:幀速率的效果跟原圖保持一致
它能夠實際應用於視頻轉Gif的操做,由於視頻咱們能夠按照時間去進行相應的截圖,若是控制好時間間隔,循環截圖,經過如下方法生成Gif應該能夠實現。這裏就暫時不實驗了,感興趣的同窗能夠試一下。
附視頻截圖方法
// 經過視頻url截取封面 + (UIImage*) thumbnailImageForVideo:(NSURL *)videoURL { AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil]; AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset]; gen.appliesPreferredTrackTransform = YES; CMTime time = CMTimeMakeWithSeconds(1.0, 600);// 設置時間 NSError *error = nil; CMTime actualTime; CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error]; UIImage *thumbImg = [[UIImage alloc] initWithCGImage:image]; if (image) { CFRelease(image); } return thumbImg; }
言歸正傳,還記得咱們前面講逐幀動畫時,用到的圖片數組嗎?咱們利用它們,來合成一張gif被進行沙盒存儲
代碼實現,此處再次提醒你們須要導入的頭文件
1.使用Image I/O框架: #import <ImageIO/ImageIO.h>
2.使用UTType:#import <MobileCoreServices/MobileCoreServices.h>
/** * 序列幀圖片的讀取 -> 建立Gif文件,獲取CGImageDestinationRef -> 爲單幀圖片CGImageRef屬性賦值 -> 爲Gif設置相關屬性 -> 經過CGImageDestinationRef合成gif */ - (void)createGifFile{ NSMutableArray *imgArr = [NSMutableArray new]; for (int i = 1; i < 67; i ++) { UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"m2_1%04d",i]]; [imgArr addObject:img]; } NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; NSString *magicPath = [path stringByAppendingString:@"/magic.gif"]; NSLog(@"magicPath : %@",magicPath); CFURLRef URLRef = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (__bridge CFStringRef)magicPath, kCFURLPOSIXPathStyle, NO); CGImageDestinationRef gifRef = CGImageDestinationCreateWithURL(URLRef, kUTTypeGIF,imgArr.count, NULL); NSDictionary *keyFrameDict = @{(NSString *)kCGImagePropertyGIFDelayTime:@(1/30.0)};// 30fps NSDictionary *gifKeyFrameDict = @{(NSString *)kCGImagePropertyGIFDictionary:keyFrameDict}; for (UIImage *image in imgArr) {// 每幀圖片進行設置 CGImageRef imageRef = [image CGImage]; CGImageDestinationAddImage(gifRef, imageRef, (__bridge CFDictionaryRef) gifKeyFrameDict); } NSDictionary *gifSettingDict = @{ // 色彩空間格式 (NSString *)kCGImagePropertyColorModel:(NSString *)kCGImagePropertyColorModelGray, // 色彩深度 (NSString *)kCGImagePropertyDepth:@(8), // gif執行次數 (NSString *)kCGImagePropertyGIFLoopCount:@(1) }; NSDictionary *gifDict = @{ (NSString *)kCGImagePropertyGIFDictionary : gifSettingDict }; CGImageDestinationSetProperties(gifRef, (__bridge CFDictionaryRef) gifDict); CGImageDestinationFinalize(gifRef); }
在設置gif圖片的屬性時,咱們用到了 色彩空間格式 、 色彩深度 兩個參數,色彩格式咱們採用最爲常見的RGB
格式,色彩深度咱們設置爲@(8)
。須要你們根據本身的實際須要去設置。
圖像類型 | 色階位數 |
---|---|
黑白圖像 | 1 |
灰度圖像 | 8 |
彩色圖像 | 16 或 32 |
咱們看一下沙盒中咱們生成的Gif :finder shift + command + g
內容層動畫,是指在必定時間內,對layer
的Animatable Property
進行修改所產生的動畫。因此,熟悉CALayer
的常見屬性,會對咱們後邊用CoreAnimation
實現 內容層動畫 大有幫助。
下邊咱們列舉一些常見的屬性並作了必要備註
這裏須要注意的是,若是一個layer
對象存在對應着的View,則稱這個layer
是一個Root Layer
, 非 Root Layer 通常都是經過CALayer
或其子類
直接建立的。
全部的非Root Layer在設置Animatable Properties的時候都存在着隱式動畫,默認的duration是0.25秒.
咱們看一個簡單CAShapeLayer
隱式動畫的例子
- (void)testForCALayer{ self.testLayer = [[CAShapeLayer alloc] init]; _testLayer.strokeEnd = 0; _testLayer.strokeColor = [UIColor redColor].CGColor; _testLayer.fillColor = [UIColor clearColor].CGColor; _testLayer.lineWidth = 3; _testLayer.path = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:100 startAngle:-M_PI_2 endAngle:3/2.0*M_PI clockwise:YES].CGPath; [self.view.layer addSublayer:_testLayer]; } - (void)animationForLayer{ self.testLayer.strokeEnd = 1; }
在touchesBegan
方法中調用animationForLayer
的方法,能夠發現,咱們並無設置任何動畫相關的語句 效果以下
CABasicAnimation
CABasicAnimation
必要屬性的設置:
keyPath
傳入layer的屬性字符串duration
設置基本動畫時長- (void)setRomovedOnCompletion
默認爲YES
,會在動畫結束時,將動畫從render tree
中一次性移除,恢復初始狀態。一般咱們設置爲NO
,保留動畫結束時的效果fillModel
屬性決定當前對象過了非active時間段的行爲,一般咱們選擇kCAFillModeForwards
kCAFillModeRemoved
這個是默認值,也就是說當動畫開始前和動畫結束後,動畫對layer都沒有影響,動畫結束後,layer會恢復到以前的狀態kCAFillModeForwards
當動畫結束後,layer會一直保持着動畫最後的狀態kCAFillModeBackwards
同 kCAFillModeForwards
是相對的,就是在動畫開始前,你只要將動畫加入了一個layer,layer便當即進入動畫的初始狀態並等待動畫開始.你能夠這樣設定測試代碼,將一個動畫加入一個layer的時候延遲5秒執行.而後就會發如今動畫沒有開始的時候,只要動畫被加入了layer,layer便處於動畫初始狀態kCAFillModeBoth
理解了上面兩個,這個就很好理解了,這個其實就是上面兩個的合成.動畫加入後開始以前,layer便處於動畫初始狀態,動畫結束後layer保持動畫最後的狀態.如下三個屬性,參照官方描述,很容易理解
fromValue
toValue
byValue
/* Father's Love * The objects defining the property values being interpolated between. * All are optional, and no more than two should be non-nil. The object * type should match the type of the property being animated (using the * standard rules described in CALayer.h). The supported modes of * animation are: * * - both `fromValue' and `toValue' non-nil. Interpolates between * `fromValue' and `toValue'. * * - `fromValue' and `byValue' non-nil. Interpolates between * `fromValue' and `fromValue' plus `byValue'. * * - `byValue' and `toValue' non-nil. Interpolates between `toValue' * minus `byValue' and `toValue'. * * - `fromValue' non-nil. Interpolates between `fromValue' and the * current presentation value of the property. * * - `toValue' non-nil. Interpolates between the layer's current value * of the property in the render tree and `toValue'. * * - `byValue' non-nil. Interpolates between the layer's current value * of the property in the render tree and that plus `byValue'. */
若是但願加入顯示層動畫中的spring
效果,Father Apple貼心的爲咱們準備了CABasicAnimation
的子類CASpringAnimation
,推薦使用。控制彈性動畫的屬性
damping
initialVelocity
mass
stiffness
簡單demo演示
- (void)testForCABasicAnimation{ UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; [button setTitle:@"Animation" forState:UIControlStateNormal]; [button setBackgroundColor:[UIColor lightGrayColor]]; [button setTitleColor:[UIColor purpleColor] forState:UIControlStateNormal]; [button sizeToFit]; button.center = self.view.center; button.layer.borderColor = [UIColor redColor].CGColor; [self.view addSubview:button]; self.testButton = button; self.testButton.layer.shadowColor = [UIColor blackColor].CGColor; self.testButton.layer.shadowOpacity = 0.5; NSLog(@"\nshadowOffset:%f,%f",self.testButton.layer.shadowOffset.width,self.testButton.layer.shadowOffset.height); NSLog(@"\nshadowRadius:%f",self.testButton.layer.shadowRadius); } - (void)callBasicAnimation{ CASpringAnimation *basic = [[CASpringAnimation alloc] init]; basic.keyPath = @"cornerRadius"; basic.toValue = @15; basic.duration = 2.0f; basic.fillMode = kCAFillModeForwards; [basic setRemovedOnCompletion:NO]; basic.damping = 6; basic.initialVelocity = 5; basic.mass = 0.5; [self.testButton.layer addAnimation:basic forKey:nil]; }
同顯示層動畫
中的關鍵幀動畫
相似,提供更加精確的動畫控制能力
經常使用屬性
values
數組類型,描述每一個關鍵幀的相關屬性keyTimes
NSNumber
泛型數組類型,描述關鍵幀時間信息,範圍addKeyframeWithRelativeStartTime
中的時間描述相似,再也不贅述path
屬性,更加精確的控制動畫的路徑簡單demo演示
- (void)callKeyframeAnimation{// CAKeyframeAnimation CAKeyframeAnimation *keyframe = [[CAKeyframeAnimation alloc] init]; keyframe.keyPath = @"position"; keyframe.duration = 5.0f; // keyframe.repeatCount = MAXFLOAT; keyframe.fillMode = kCAFillModeForwards; keyframe.calculationMode = kCAAnimationCubic; [keyframe setRemovedOnCompletion:NO]; // keyframe.values = @[ // (__bridge id _Nullable)[UIColor redColor].CGColor, // (__bridge id _Nullable)[UIColor yellowColor].CGColor, // (__bridge id _Nullable)[UIColor greenColor].CGColor, // (__bridge id _Nullable)[UIColor blueColor].CGColor, // (__bridge id _Nullable)[UIColor purpleColor].CGColor // ]; keyframe.path = [self keyframePath]; [self.testButton.layer addAnimation:keyframe forKey:nil]; } - (CGMutablePathRef )keyframePath{ CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, NULL, 100, 100); CGPathAddLineToPoint(path, NULL, 100, 200); CGPathAddLineToPoint(path, NULL, 200, 350); CGPathAddArc(path, NULL, 200, 350, 100, 0, M_PI, NO); return path; }
提供了animations
數組接口,提供多個動畫進行組合的能力
簡單demo演示
- (void)callAnimationGroup{ CAKeyframeAnimation *keyframe = [[CAKeyframeAnimation alloc] init]; keyframe.keyPath = @"position"; keyframe.path = [self keyframePath]; CAKeyframeAnimation *colors = [[CAKeyframeAnimation alloc] init]; colors.keyPath = @"backgroundColor"; colors.values = @[ (__bridge id _Nullable)[UIColor redColor].CGColor, (__bridge id _Nullable)[UIColor yellowColor].CGColor, (__bridge id _Nullable)[UIColor greenColor].CGColor, (__bridge id _Nullable)[UIColor blueColor].CGColor, (__bridge id _Nullable)[UIColor purpleColor].CGColor ]; CAAnimationGroup *group = [[CAAnimationGroup alloc] init]; group.animations = @[keyframe,colors]; group.duration = 5.0f; group.fillMode = kCAFillModeForwards; [group setRemovedOnCompletion:NO]; [self.testButton.layer addAnimation:group forKey:nil]; }
用於不一樣視圖場景之間的切換
使用過程相對簡單
CATransition
,設置相應的轉場動畫keyduration
,type,subtype
等layer
中系統提供的相關Api(已作必要註釋)
公有type Api
// 淡入淡出效果 CA_EXTERN NSString * const kCATransitionFade CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0); // 移動效果 CA_EXTERN NSString * const kCATransitionMoveIn CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0); // 推送效果 CA_EXTERN NSString * const kCATransitionPush CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0); // 揭開效果 CA_EXTERN NSString * const kCATransitionReveal CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
私有type Api(只能使用字符串表示 使用有風險,上傳需謹慎)
subtypes 轉場方向
CA_EXTERN NSString * const kCATransitionFromRight CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0); CA_EXTERN NSString * const kCATransitionFromLeft CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0); CA_EXTERN NSString * const kCATransitionFromTop CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0); CA_EXTERN NSString * const kCATransitionFromBottom CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
簡單demo演示
- (void)setImgViewForCATransiton{ UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)]; imageView.center = self.view.center; imageView.image = [UIImage imageNamed:@"pic_1"]; imageView.contentMode = UIViewContentModeScaleAspectFill; imageView.layer.cornerRadius = 5; imageView.layer.masksToBounds = YES; self.testImgView = imageView; [self.view addSubview:imageView]; } // CATransition - (void)testForCATransition{ self.testImgView.image = [UIImage imageNamed:@"pic_2"]; CATransition *transition = [[CATransition alloc] init]; transition.type = kCATransitionReveal; transition.subtype = kCATransitionFromTop; transition.duration = 2; [self.testImgView.layer addAnimation:transition forKey:nil]; }
題外話
CATransition
同NSTimer
結合使用,實現登陸界面fade效果的輪播,有興趣的同窗能夠看一下《iOS-幾行代碼實現登陸頁Fade效果的圖片輪播》 Transition
,那麼就不得不提一下iOS中的轉場動畫,以前筆者在這篇文章中,也有所介紹《iOS-自定義轉場》 粒子系統,由CAEmitterLayer
粒子發射器圖層及CAEmitterCell
粒子發射單元組成
@interface CAEmitterLayer : CALayer // 粒子單元數組 @property(nullable, copy) NSArray<CAEmitterCell *> *emitterCells; // 粒子的建立速率,默認值爲1s Animatable @property float birthRate; // 粒子的存活時間係數,默認值爲1s Animatable @property float lifetime; // 發射器在xy平面的位置 Animatable @property CGPoint emitterPosition; // 發射器在z平面的位置 Animatable @property CGFloat emitterZPosition; // 發射器的尺寸大小 @property CGSize emitterSize; // 發射器的深度 @property CGFloat emitterDepth; // 發射器形狀 包括'point' (the default)點, 'line'線, 'rectangle'矩形,'circle'圓形, 'cuboid'立方體 and 'sphere'球形 @property(copy) NSString *emitterShape; // 發射器的發射模式 包括'points'發射器內部發出, 'outline'發射器邊緣發出, 'surface'發射器表面發出 and 'volume' (the default)發射器中點發出 @property(copy) NSString *emitterMode; // 發射器渲染模式 'unordered' (the default)粒子無序, 'oldestFirst'越早聲明的粒子渲染層級越高,'oldestLast'越晚聲明的粒子渲染層級越高, 'backToFront' (i.e. sorted into Z order) 按照z軸的順序渲染and 'additive'粒子混合. The first four use source-over compositing, the last uses additive compositing. @property(copy) NSString *renderMode; // 開啓深度顯示(三維空間效果) @property BOOL preservesDepth; // 粒子速度係數 * cell速度 默認1 Animatable @property float velocity; // 粒子縮放係數 * cellscale 默認1 Animatable @property float scale; // 粒子旋轉位置係數 *cellspin 默認1 Animatable @property float spin; // 初始化隨機的粒子種子 默認0 @property unsigned int seed; @end
// 構建方法 + (instancetype)emitterCell; // 發射單元名稱,用來構建 keyPath @property(nullable, copy) NSString *name; // 是否容許被渲染 @property(getter=isEnabled) BOOL enabled; // 每秒建立的粒子數,默認值0 Animatable @property float birthRate; // 粒子平均存活時間及存活時間容差 @property float lifetime; @property float lifetimeRange; // 發射緯度:z軸方向的發射角度 發射經度:x-y平面中,相對x軸的發射角度 Animatable @property CGFloat emissionLatitude; @property CGFloat emissionLongitude; // 發射角度的容差 Animatable @property CGFloat emissionRange // 粒子平均速度及容差 默認值0 Animatable @property CGFloat velocity; @property CGFloat velocityRange; // 粒子在各個方向的加速度 Animatable @property CGFloat xAcceleration; @property CGFloat yAcceleration; @property CGFloat zAcceleration; // 粒子平均縮放相關屬性 Animatable @property CGFloat scale; @property CGFloat scaleRange; @property CGFloat scaleSpeed; // 粒子平均旋轉相關屬性 Animatable @property CGFloat spin; @property CGFloat spinRange; // 粒子的主顏色 及 R/G/B/Alpha 的容差 Animatable @property(nullable) CGColorRef color; @property float redRange;// 默認 0 @property float greenRange;// 默認 0 @property float blueRange;// 默認 0 @property float alphaRange;// 默認 0 // 粒子的主顏色基礎上 R/G/B/Alpha變化速度 Defaults to (0, 0, 0, 0) Animatable @property float redSpeed; @property float greenSpeed; @property float blueSpeed; @property float alphaSpeed; // 渲染粒子,能夠設置爲一個CGImage的對象 @property(nullable, strong) id contents; // 渲染範圍 @property CGRect contentsRect; // image的像素尺寸同視圖大小的比例,默認爲1.0 @property CGFloat contentsScale;
項目中,粒子動畫的應用場景,大可能是配合彈框提示出現的
簡單demo演示
實現綵帶噴撒動畫的效果
- (void)testForCAEmitterLayer{ // 初始化發射器圖層 CAEmitterLayer *emitterLayer = [CAEmitterLayer layer]; emitterLayer.birthRate = 1; emitterLayer.lifetime = 10; emitterLayer.emitterPosition = self.view.center; emitterLayer.emitterSize = self.waveButton.frame.size; emitterLayer.emitterShape = kCAEmitterLayerRectangle; emitterLayer.emitterMode = kCAEmitterLayerOutline; emitterLayer.renderMode = kCAEmitterLayerOldestFirst; // 初始化發射單元並標記name,方便經過keypath修改相關屬性 CGSize particleSize = CGSizeMake(15, 25); CAEmitterCell *blueCell = [self demo_CAEmitterCellWithImage:[self createImageWithColor:[UIColor blueColor] andSize:particleSize]]; blueCell.name = @"blue"; CAEmitterCell *yellowCell = [self demo_CAEmitterCellWithImage:[self createImageWithColor:[UIColor yellowColor] andSize:particleSize]]; yellowCell.name = @"yellow"; CAEmitterCell *redCell = [self demo_CAEmitterCellWithImage:[self createImageWithColor:[UIColor redColor] andSize:particleSize]]; redCell.name = @"red"; // 將發射單元添加至發射器 emitterLayer.emitterCells = @[blueCell,yellowCell,redCell]; // 在目標位置添加發射器 [self.view.layer addSublayer:emitterLayer]; } - (CAEmitterCell *)demo_CAEmitterCellWithImage:(UIImage *)image{ CAEmitterCell *emitterCell = [CAEmitterCell emitterCell]; // contents emitterCell.contents = (__bridge id _Nullable)(image.CGImage); // birthRate emitterCell.birthRate = 10; // lifetime emitterCell.lifetime = 40; emitterCell.lifetimeRange = 20; // emission // emitterCell.emissionLongitude // emitterCell.emissionLatitude emitterCell.emissionRange = M_PI_2; // volocity emitterCell.velocity = 200; emitterCell.velocityRange = 20; // Acceleration emitterCell.xAcceleration = 0; emitterCell.yAcceleration = 9.8; // scale emitterCell.scale = 0.6; emitterCell.scaleRange = 0.6; // spin emitterCell.spin = M_PI * 2; emitterCell.spinRange = M_PI * 2; // color // emitterCell.redRange = 0.1f; // emitterCell.greenRange = 0.1f; // emitterCell.blueRange = 0.1f; // emitterCell.alphaRange = 1; // color speed // emitterCell.alphaSpeed = 10; return emitterCell; } - (UIImage *)createImageWithColor:(UIColor *)color andSize:(CGSize)size{ UIGraphicsBeginImageContext(size); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, color.CGColor); CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height)); UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; }
效果以下
EmitterLayer
繼承自CALayer
,因此顯示層的CoreAnimation
對它一樣適用。同時,這裏就用到了咱們設置的EmitterCell
的name
屬性值)CAAnimationGroup *demo_ParticleAnimation(){ CABasicAnimation *blue = [CABasicAnimation animation]; blue.keyPath = @"emitterCells.blue.birthRate"; blue.fromValue = @30; blue.toValue = @0; CABasicAnimation *red = [CABasicAnimation animation]; red.keyPath = @"emitterCells.red.birthRate"; red.fromValue = @30; red.toValue = @0; CABasicAnimation *yellow = [CABasicAnimation animation]; yellow.keyPath = @"emitterCells.yellow.birthRate"; yellow.fromValue = @30; yellow.toValue = @0; CAAnimationGroup *group = [[CAAnimationGroup alloc] init]; group.animations = @[blue,red,yellow]; group.duration = 1.0f; group.fillMode = kCAFillModeForwards; [group setRemovedOnCompletion:NO]; return group; }
合適位置給發射器添加動畫
// 添加發射動畫 [emitterLayer addAnimation:demo_ParticleAnimation() forKey:nil];
效果以下
CAGradientLayer
是系統提供的顏色梯度變化能力的類,使用相對比較簡單
下面看一下頭文件中的幾個簡單接口
// CGColorRef顏色數組 @property(nullable, copy) NSArray *colors; // 控制顏色範圍的數組[0,1] 類比`KeyFrameAnimation`中的`keyTimes`理解 @property(nullable, copy) NSArray<NSNumber *> *locations; // 相似`anchorPoint`,是相對於視圖自己映射關係的相對位置,要來絕對梯度變化的方向 @property CGPoint startPoint; @property CGPoint endPoint;
項目中彷佛用到的地方很少,也許在音頻的應用中作些跑馬燈(此處手動滑稽)或者正常點的二維碼掃描動畫中用到。其餘的應用場景暫時想不出,若是這篇文章真的有讀者的話,歡迎補充交流
簡單demo演示
接下來,咱們像經典致敬,山寨一個活動解鎖效果
直接上代碼
// GradientLayer - (void)testForGradientLayer{ CAGradientLayer *gradientLayer = [[CAGradientLayer alloc] init]; gradientLayer.frame = self.label.bounds; gradientLayer.startPoint = CGPointMake(0, 0);// 相似anchor point的映射關係 gradientLayer.endPoint = CGPointMake(1, 0); gradientLayer.colors = @[(__bridge id _Nullable)[UIColor clearColor].CGColor,(__bridge id _Nullable)[UIColor whiteColor].CGColor,(__bridge id _Nullable)[UIColor clearColor].CGColor]; gradientLayer.locations = @[@0,@0,@0.3];// [0,1] CABasicAnimation *basic = [CABasicAnimation animation]; basic.keyPath = @"locations"; basic.toValue = @[@0.7,@1,@1]; basic.duration = 3.0; basic.fillMode = kCAFillModeForwards; [basic setRemovedOnCompletion:NO]; basic.repeatCount = MAXFLOAT; [gradientLayer addAnimation:basic forKey:nil]; // 設置蒙板 self.label.layer.mask = gradientLayer; } - (void)setupUI4GradientLayer{ self.label = [[UILabel alloc] init]; _label.text = @"Slide To Unlock"; _label.textColor = [UIColor whiteColor]; _label.textAlignment = NSTextAlignmentCenter; _label.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium]; _label.frame = CGRectMake(30, CGRectGetHeight(self.view.bounds) - 100, CGRectGetWidth(self.view.bounds) - 60, 60); [self.view addSubview:_label]; self.view.backgroundColor = [UIColor blackColor]; }
效果以下(彷佛還蠻不錯的-。-)
CAShapeLayer
配合CGPathRef
或者UIBezierPath
路徑,堪稱繪圖利器。上文中演示隱式動畫
時已經提到,下面再經過例子,演示幾種常見的用法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.testLayer.strokeEnd = 1; self.testLayer2.strokeEnd = 1; self.testLayer3.strokeEnd = 1; } // CAshapeLayer - (void)testForCAShapeLayer{ // bezierPath CGRect screenBounds = [UIScreen mainScreen].bounds; UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(20, 200)]; [path addCurveToPoint:CGPointMake(screenBounds.size.width - 20, 200) controlPoint1:CGPointMake(screenBounds.size.width/2, 50) controlPoint2:CGPointMake(screenBounds.size.width/2, 350)]; CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init]; shapeLayer.strokeEnd = 0; shapeLayer.strokeColor = [UIColor redColor].CGColor; shapeLayer.fillColor = [UIColor clearColor].CGColor; shapeLayer.lineWidth = 3; shapeLayer.path = path.CGPath; self.testLayer = shapeLayer; [self.view.layer addSublayer:shapeLayer]; // CGPath CGMutablePathRef mutablePath = CGPathCreateMutable(); CGPathMoveToPoint(mutablePath, NULL, 20, 200); CGPathAddLineToPoint(mutablePath, NULL, screenBounds.size.width/2, 50); CGPathAddLineToPoint(mutablePath, NULL, screenBounds.size.width/2, 350); CGPathAddLineToPoint(mutablePath, NULL, screenBounds.size.width - 20, 200); CAShapeLayer *shapeLayer2 = [[CAShapeLayer alloc] init]; shapeLayer2.strokeEnd = 0; shapeLayer2.strokeColor = [UIColor lightGrayColor].CGColor; // shapeLayer2.fillColor = [[UIColor blueColor] colorWithAlphaComponent:0.1].CGColor; shapeLayer2.fillColor = [UIColor clearColor].CGColor; shapeLayer2.lineWidth = 5; shapeLayer2.lineCap = kCALineCapRound; shapeLayer2.lineJoin = kCALineJoinBevel; shapeLayer2.path = mutablePath; self.testLayer2 = shapeLayer2; [self.view.layer addSublayer:shapeLayer2]; CGMutablePathRef mutablePath2 = CGPathCreateMutable(); CGPathMoveToPoint(mutablePath2, NULL, screenBounds.size.width/2, screenBounds.size.height - 50); CGPathAddLineToPoint(mutablePath2, NULL, screenBounds.size.width/2, 380); CAShapeLayer *shapeLayer3 = [CAShapeLayer layer]; shapeLayer3.strokeColor = [UIColor purpleColor].CGColor; shapeLayer3.lineWidth = 20; shapeLayer3.path = mutablePath2; shapeLayer3.strokeEnd = 0; self.testLayer3 = shapeLayer3; [self.view.layer addSublayer:shapeLayer3]; // slider UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(20, screenBounds.size.height - 50, screenBounds.size.width - 40, 50)]; slider.minimumValue = 0; slider.maximumValue = 1; slider.value = 1; [slider addTarget:self action:@selector(valueChanged:) forControlEvents:UIControlEventValueChanged]; [self.view addSubview:slider]; } - (void)valueChanged:(UISlider *)slider{ self.testLayer.strokeEnd = slider.value; self.testLayer2.strokeEnd = slider.value; self.testLayer3.strokeEnd = slider.value; }
顧名思義,這個Layer
是讓咱們具有了複製能力。經過複製一些帶動畫的layer
,實現有層次感的動畫,由於接口較少,且容易理解,特別注意下instanceTransform
屬性便可。有興趣的同窗直接進代碼
// sound wave CAShapeLayer *circle = [CAShapeLayer layer]; circle.bounds = CGRectMake(0, 0, 100, 100); circle.position = self.view.center; circle.opacity = 1; UIBezierPath *circlepath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50, 50) radius:15 startAngle:0 endAngle:2 * M_PI clockwise:YES]; circle.fillColor = ThemeColor.CGColor; circle.path = circlepath.CGPath; CABasicAnimation *opacity = [CABasicAnimation animation]; opacity.keyPath = @"opacity"; opacity.fromValue = @1; opacity.toValue = @0; CABasicAnimation *scale = [CABasicAnimation animation]; scale.keyPath = @"transform.scale"; scale.fromValue = @1; scale.toValue = @(0.3); CAAnimationGroup *group = [CAAnimationGroup animation]; group.duration = 0.8; group.fillMode = kCAFillModeForwards; [group setRemovedOnCompletion:YES]; group.animations = @[scale]; group.autoreverses = YES; group.repeatCount = MAXFLOAT; [circle addAnimation:group forKey:nil]; CAReplicatorLayer *gradientLayer = [CAReplicatorLayer layer]; [gradientLayer addSublayer:circle]; gradientLayer.instanceCount = 3; gradientLayer.instanceDelay = 0.3; // [self.view.layer addSublayer:gradientLayer]; // loading ball CAReplicatorLayer *instanceGradient = [CAReplicatorLayer layer]; [instanceGradient addSublayer:circle]; instanceGradient.instanceCount = 3; instanceGradient.instanceDelay = 0.2; instanceGradient.instanceTransform = CATransform3DMakeTranslation(50, 0, 0); [self.view.layer addSublayer:instanceGradient];
帶點擊水紋及加載提示的button動畫
關鍵點:
NSTimer
逐幀動畫frame
及center
AnimationGroup
組合cornerRadius
及opacity
的修改CADisplayLink
逐幀動畫CAShapeLayer
的strokeEnd
屬性實現的CABasicAnimation
動畫效果以下:
(demo地址在文章最後)
原理描述:給定n+1個數據點,p0(x0,y0)...pn(xn,yn),生成一條曲線,使得該曲線與這些點所鏈接的折線相近
相關資料
能夠看到,上圖中折線的中間點,即爲咱們所說的控制點,他的位置變化,直接影響了曲線的走向
手機QQ中最先出現(強迫症福音有木有),能夠根據手勢滑動的距離動態改變badge的形狀
咱們能夠大體根據下圖中繪製的樣例,理解貝塞爾曲線在這個例子中的做用
這個Badgeview由如下幾部分組成:
C/P/B
,肯定下半部分曲線的三個點爲D/O/A
,這幾個點的位置肯定,用到了最基礎的正弦函數
餘弦函數
以及三角形求變長的簡單計算)全部的複雜動畫,都是由基本顯示層動畫
、內容層動畫
組合而成。嘗試作過一些練習後,筆者這隻鶸,發現動畫真的是一件有意思的事情呢!
筆者簡書地址:iOS-動畫知識梳理
傳送門:Demo