// // LuckyNumController.m // 40_核心动画 // // Created by beyond on 14-9-19. // Copyright (c) 2014年 com.beyond. All rights reserved. // 幸运选号 #import "LuckyNumController.h" #import "Circle.h" @interface LuckyNumController () // 开始旋转 - (IBAction)startRotate; // 停止旋转 - (IBAction)stopRotate; - (IBAction)dismiss; // 要控制它的开始和停止 @property (nonatomic, weak) Circle *circle; @end @implementation LuckyNumController - (void)viewDidLoad { [super viewDidLoad]; // 创建一个封装好的Circle,并添加到self.view Circle *circle = [Circle circle]; circle.center = CGPointMake(self.view.frame.size.width * 0.5, self.view.frame.size.height * 0.5); [self.view addSubview:circle]; self.circle = circle; } // 开始旋转 - (IBAction)startRotate { // 调用其内部的方法 开始旋转 [self.circle startRotating]; } // 停止旋转 - (IBAction)stopRotate { // 调用其内部的方法 停止旋转 [self.circle stopRotating]; } - (IBAction)dismiss { [self dismissViewControllerAnimated:YES completion:nil]; } @end
// // Circle.h // 40_核心动画 // // Created by beyond on 14-9-19. // Copyright (c) 2014年 com.beyond. All rights reserved. // 大圆盘 #import <UIKit/UIKit.h> @interface Circle : UIView // 构造方法,快速创建实例对象 + (instancetype)circle; // 供外界调用,开始旋转 - (void)startRotating; // 供外界调用,停止旋转 - (void)stopRotating; @end
// // Circle.m // 40_核心动画 // // Created by beyond on 14-9-19. // Copyright (c) 2014年 com.beyond. All rights reserved. // #import "Circle.h" #import "CircleBtn.h" @interface Circle() @property (weak, nonatomic) IBOutlet UIImageView *centerCircle; // 三步曲,切换按钮的选中状态 @property (nonatomic, weak) CircleBtn *selectedBtn; // 时钟,控制centerCircle慢悠悠地转 @property (nonatomic, strong) CADisplayLink *link; // 开始选号 - (IBAction)startChoose; @end @implementation Circle #pragma mark - 生命周期方法 // 构造方法,快速创建实例对象 + (instancetype)circle { return [[[NSBundle mainBundle] loadNibNamed:@"Circle" owner:nil options:nil] lastObject]; } // 从xib加载之后,一次性添加12个按钮 // 在这个方法中,取得属性才是有值的 - (void)awakeFromNib { // 让imageView能跟用户进行交互 self.centerCircle.userInteractionEnabled = YES; // 一次性添加12个按钮 [self add12Btns]; } #define kAngleToRadius(angle) (angle)/180.0*M_PI // 一次性添加12个按钮 - (void)add12Btns { // 一次性 添加12个按钮 for (int i = 0; i < 12; i++) { // 1.创建一个按钮 CircleBtn *btn = [CircleBtn buttonWithType:UIButtonTypeCustom]; // 2.设置按钮的基本属性,并且根据i的不同,设置小按钮的旋转角度 [self setBtnBasicAttribute:btn index:i]; // 3.裁剪大图,并根据i的不同,从大图中不同位置rect,截取一个小图片 [self setBtnBg:btn bigImg:@"LuckyAstrology" selectedBigImg:@"LuckyAstrologyPressed" index:i]; // 4.监听按钮点击 [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchDown]; // 5.添加到self.view [self.centerCircle addSubview:btn]; // 6.默认选中第0个按钮 if (i == 0) { [self btnClick:btn]; } } } // 抽取的方法,基本属性,并且根据i的不同,设置小按钮的旋转角度 - (void)setBtnBasicAttribute:(UIButton *)btn index:(int)i { btn.bounds = CGRectMake(0, 0, 68, 143); // 设置按钮选中时的背景图片(红红的) [btn setBackgroundImage:[UIImage imageNamed:@"LuckyRototeSelected"] forState:UIControlStateSelected]; // 为了所有的12个小按钮,能够排成一个圆形,位置相同,锚点为底边的中点,根据i不同,绕着锚点进行旋转 // 位置全部相同,居中心 btn.layer.position = CGPointMake(self.centerCircle.frame.size.width * 0.5, self.centerCircle.frame.size.height * 0.5); // 设置按钮图层的锚点(底边的中点) btn.layer.anchorPoint = CGPointMake(0.5, 1); // 设置按钮的旋转角度(根据i不同,绕着锚点进行旋转) // 角度转弧度 kAngleToRadius(angle) angle/180.0*M_PI // CGFloat angle = (30 * i) / 180.0 * M_PI; CGFloat angle = kAngleToRadius(30*i); // 设置按钮的旋转角度 btn.transform = CGAffineTransformMakeRotation(angle); } // 抽取的方法,根据i的不同,从大图中不同位置rect,截取一个小图片 - (void)setBtnBg:(UIButton *)btn bigImg:(NSString *)bigImgName selectedBigImg:(NSString *)selectedBigImgName index:(int)i { // 1.加载大图片 UIImage *bigImg = [UIImage imageNamed:bigImgName]; UIImage *bigImgSelected = [UIImage imageNamed:selectedBigImgName]; // 从大图片中裁剪中对应星座的图片(点坐标系 转成像素坐标系) CGFloat smallW = bigImg.size.width / 12 * [UIScreen mainScreen].scale; CGFloat smallH = bigImg.size.height * [UIScreen mainScreen].scale; // 2.根据i的不同,从大图中不同位置rect,截取一个小图片 CGRect smallRect = CGRectMake(i * smallW, 0, smallW, smallH); // CGImageCreateWithImageInRect只认像素 // 截取出来的小图片 (默认状态下) CGImageRef smallImg = CGImageCreateWithImageInRect(bigImg.CGImage, smallRect); // 截取出来的小图片 (选中状态下) CGImageRef smallImgSelected = CGImageCreateWithImageInRect(bigImgSelected.CGImage, smallRect); // 3.设置按钮的默认和选中时的小图片 [btn setImage:[UIImage imageWithCGImage:smallImg] forState:UIControlStateNormal]; [btn setImage:[UIImage imageWithCGImage:smallImgSelected] forState:UIControlStateSelected]; } // 监听按钮点击,三步曲,切换按钮的选中状态 - (void)btnClick:(CircleBtn *)btn { self.selectedBtn.selected = NO; btn.selected = YES; self.selectedBtn = btn; } #pragma mark - 连线方法 // 开始选号,即开始快速疯狂地旋转, - (IBAction)startChoose { // 1.先停止 慢悠悠地转 [self stopRotating]; // 2.期间,禁止与用户交互 self.userInteractionEnabled = NO; // 3.开始疯狂地旋转 CABasicAnimation *anim = [CABasicAnimation animation]; anim.keyPath = @"transform.rotation"; // by是相对值 anim.byValue = @(2 * M_PI * 4); anim.duration = 1.5; // 时间函数,即 节奏, 开头和结尾比较慢,中间快 anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; // 设置代理,监听动画完成状态 anim.delegate = self; // 4.添加动画到图层 [self.centerCircle.layer addAnimation:anim forKey:nil]; } #pragma mark - 动画的代理方法 // 快速旋转的动画停止时调用,2秒后,开始慢悠悠地转 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { // 恢复与用户交互 self.userInteractionEnabled = YES; // 仅执行一次 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"只执行一次的代码"); }); // 延时1.2秒后,执行代码 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self startRotating]; }); } #pragma mark - 供外界调用 // 供外界调用,开始不停地旋转 - (void)startRotating { // 如果时钟有值,说明正在旋转,直接返回 if (self.link) return; // 创建时钟,添加到主循环(1秒内刷新60次) CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)]; [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; self.link = link; } // 供外界调用,停止旋转,清空时钟 - (void)stopRotating { [self.link invalidate]; self.link = nil; } #pragma mark - 时钟方法 // 慢慢旋转 - (void)update { self.centerCircle.transform = CGAffineTransformRotate(self.centerCircle.transform, M_PI / 500); } @end
// // CircleBtn.m // 40_核心动画 // // Created by beyond on 14-9-19. // Copyright (c) 2014年 com.beyond. All rights reserved. // #import "CircleBtn.h" @implementation CircleBtn // 调整按钮中的图片的位置 - (CGRect)imageRectForContentRect:(CGRect)contentRect { CGFloat imageW = 40; CGFloat imageH = 47; CGFloat imageX = (contentRect.size.width - imageW) * 0.5; CGFloat imageY = 20; return CGRectMake(imageX, imageY, imageW, imageH); } // 取消默认的高亮状态 - (void)setHighlighted:(BOOL)highlighted{} @end
// // ViewAnimationController.m // 40_核心动画 // // Created by beyond on 14-9-19. // Copyright (c) 2014年 com.beyond. All rights reserved. // UIView封装的动画们 #import "ViewAnimationController.h" @interface ViewAnimationController () @property (weak, nonatomic) IBOutlet UIImageView *nanaImgView; @property (weak, nonatomic) IBOutlet UIView *singleView; // UIView封装的转场的动画 - (IBAction)uiviewTransitionAnimation; // UIView封装的单纯的动画 - (IBAction)uiviewAnimation; // 图层的动画 - (IBAction)calayerAnimation; - (IBAction)dismiss; @property (nonatomic, assign) int index; @end @implementation ViewAnimationController // UIView封装的转场的动画 - (IBAction)uiviewTransitionAnimation { self.index++; if (self.index == 7) { self.index = 0; } NSString *filename = [NSString stringWithFormat:@"%d.png", self.index ]; self.nanaImgView.image = [UIImage imageNamed:filename]; UIViewAnimationOptions type = [self typeFromIndex:self.index]; [UIView transitionWithView:self.view duration:1.0 options:type animations:nil completion:nil]; } // UIView封装的单纯的动画 - (IBAction)uiviewAnimation { // 方式1:使用一前一后夹住 [UIView beginAnimations:nil context:nil]; // 动画执行完毕后, 可以手动监听,也可以用NSObject默认的方法监听 // [UIView setAnimationDelegate:self]; // [UIView setAnimationDidStopSelector:@selector(animateStop)]; self.singleView.center = CGPointMake(200, 300); [UIView commitAnimations]; // 方式2:使用block [UIView animateWithDuration:1.0 animations:^{ self.singleView.center = CGPointMake(200, 300); } completion:^(BOOL finished) { }]; } // 图层的动画 - (IBAction)calayerAnimation { // 1.创建基本动画 CABasicAnimation *anim = [CABasicAnimation animation]; // 2.设置动画属性 anim.keyPath = @"position"; anim.toValue = [NSValue valueWithCGPoint:CGPointMake(50, 50)]; anim.duration = 2.0; /**让图层保持动画执行完毕后的状态**/ // 动画执行完毕后不要删除动画 anim.removedOnCompletion = NO; anim.fillMode = kCAFillModeForwards; // 设置动画的代理,可以监听动画的执行阶段 anim.delegate = self; // 3.添加动画到图层,图层动画都是假象, 在动画执行过程中, 图层的position属性一直都没有变过 [self.singleView.layer addAnimation:anim forKey:nil]; // self.singleView.layer.position == CGPointMake(0, 0) } - (IBAction)dismiss { [self dismissViewControllerAnimated:YES completion:nil]; } #pragma mark - NSObject自动实现了动画的delegate - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { NSLog(@"动画已停止....%@", NSStringFromCGPoint(self.singleView.layer.position)); self.singleView.hidden = YES; } /* UIViewAnimationOptionCurveEaseInOut = 0 << 16, // default UIViewAnimationOptionCurveEaseIn = 1 << 16, UIViewAnimationOptionCurveEaseOut = 2 << 16, UIViewAnimationOptionCurveLinear = 3 << 16, UIViewAnimationOptionTransitionNone = 0 << 20, // default UIViewAnimationOptionTransitionFlipFromLeft = 1 << 20, UIViewAnimationOptionTransitionFlipFromRight = 2 << 20, UIViewAnimationOptionTransitionCurlUp = 3 << 20, UIViewAnimationOptionTransitionCurlDown = 4 << 20, UIViewAnimationOptionTransitionCrossDissolve = 5 << 20, UIViewAnimationOptionTransitionFlipFromTop = 6 << 20, UIViewAnimationOptionTransitionFlipFromBottom = 7 << 20, */ - (UIViewAnimationOptions)typeFromIndex:(int)index { switch (index%7) { case 0: return UIViewAnimationOptionTransitionFlipFromLeft; break; case 1: return UIViewAnimationOptionTransitionFlipFromRight; break; case 2: return UIViewAnimationOptionTransitionCurlUp; break; case 3: return UIViewAnimationOptionTransitionCurlDown; break; case 4: return UIViewAnimationOptionTransitionCrossDissolve; break; case 5: return UIViewAnimationOptionTransitionFlipFromTop; break; case 6: return UIViewAnimationOptionTransitionFlipFromBottom; break; default: break; } return 0; } @end
// // BasicAnimationController.m // 40_核心动画 // // Created by beyond on 14-9-18. // Copyright (c) 2014年 com.beyond. All rights reserved. // 核心动画之基本动画,只有fromValue和toValue #import "BasicAnimationController.h" // 图层相关处理 #import <QuartzCore/QuartzCore.h> @interface BasicAnimationController () // 向主控制器的view的主图层上 添加一个子图层(含图片的层) @property (nonatomic, strong) CALayer *nanaLayer; // 基本动画之 平移 - (IBAction)translateAnimation; // 基本动画之 旋转 - (IBAction)rotationAnimation; // 基本动画之 缩放 - (IBAction)scaleAnimation; // 基本动画之 形变 - (IBAction)transformAnimation; - (IBAction)dismiss; @end @implementation BasicAnimationController #pragma mark - 生命周期 - (void)viewDidLoad { [super viewDidLoad]; // 向主控制器的view的主图层上 添加一个子图层(含图片的层) [self addSubLayer]; } // 向主控制器的view的主图层上 添加一个子图层(含图片的层) - (void)addSubLayer { CALayer *nanaLayer = [CALayer layer]; nanaLayer.position = CGPointMake(160, 133); nanaLayer.bounds = CGRectMake(0, 0, 150, 150); nanaLayer.backgroundColor = [UIColor redColor].CGColor; // 重要~~~一个含图片的子图层 nanaLayer.contents = (id)[UIImage imageNamed:@"nanaLogo.jpg"].CGImage; // 圆角 nanaLayer.cornerRadius = 10; nanaLayer.masksToBounds = YES; // 控制器的view的主图层 中添加子图层 [self.view.layer addSublayer:nanaLayer]; self.nanaLayer = nanaLayer; } #pragma mark - 连线方法 // 基本动画之 平移 - (IBAction)translateAnimation { // 1.创建动画对象 CABasicAnimation *anim = [CABasicAnimation animation]; // 2.设置动画对象的属性 // keyPath决定了执行怎样的动画, 调整哪个属性来执行动画 anim.keyPath = @"position"; // anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)]; // toValue : 最终变成什么值 // byValue : 增加多少值 anim.byValue = [NSValue valueWithCGPoint:CGPointMake(30, 40)]; anim.duration = 2.0; /**让图层保持动画执行完毕后的状态**/ // 动画执行完毕后不要删除动画 anim.removedOnCompletion = NO; // 保持最新的状态 anim.fillMode = kCAFillModeForwards; // 3.最后一步,添加动画到图层 [self.nanaLayer addAnimation:anim forKey:nil]; } // 基本动画之 旋转 - (IBAction)rotationAnimation { // 1.创建动画对象 CABasicAnimation *anim = [CABasicAnimation animation]; // 2.设置动画对象的属性 // keyPath决定了执行怎样的动画, 调整哪个属性来执行动画 anim.keyPath = @"transform"; // anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)]; // 重要~~~参数:角度,x,y,z anim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 1, -1, 0)]; anim.duration = 2.0; /**让图层保持动画执行完毕后的状态**/ // 动画执行完毕后不要删除动画 anim.removedOnCompletion = NO; anim.fillMode = kCAFillModeForwards; // 3.最后一步,添加动画到图层 [self.nanaLayer addAnimation:anim forKey:nil]; } // 基本动画之 缩放 - (IBAction)scaleAnimation { // 1.创建动画对象 CABasicAnimation *anim = [CABasicAnimation animation]; // 2.设置动画对象的属性 // keyPath决定了执行怎样的动画, 调整哪个属性来执行动画 anim.keyPath = @"bounds"; // anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)]; anim.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 250, 250)]; anim.duration = 2.0; /**让图层保持动画执行完毕后的状态**/ // 动画执行完毕后不要删除动画 anim.removedOnCompletion = NO; // 保持最新的状态 anim.fillMode = kCAFillModeForwards; // 3.最后一步,添加动画到图层 [self.nanaLayer addAnimation:anim forKey:nil]; } // 基本动画之 形变 - (IBAction)transformAnimation { // 1.创建动画对象 CABasicAnimation *anim = [CABasicAnimation animation]; // 2.设置动画对象的属性 // keyPath决定了执行怎样的动画, 调整哪个属性来执行动画 // anim.keyPath = @"transform.rotation"; // anim.keyPath = @"transform.scale.x"; anim.keyPath = @"transform.translation.y"; anim.toValue = @(100); // anim.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)]; anim.duration = 2.0; /**让图层保持动画执行完毕后的状态**/ // 动画执行完毕后不要删除动画 anim.removedOnCompletion = NO; anim.fillMode = kCAFillModeForwards; // 3.最后一步,添加动画到图层 [self.nanaLayer addAnimation:anim forKey:nil]; } - (IBAction)dismiss { [self dismissViewControllerAnimated:YES completion:nil ]; } @end
#pragma mark - 两个关键帧动画演示 // 关键帧动画演示_1 沿着圆形路径移动 - (IBAction)keyFrameAnimation_1 { // 1.创建 核心动画之关键帧动画:KeyframeAnimation CAKeyframeAnimation *anim = [CAKeyframeAnimation animation]; // 2.设置动画属性 anim.keyPath = @"position"; anim.duration = 2.0; // 锚点为左上角 self.nanaImgView.layer.anchorPoint = CGPointMake(0.5, 0.5); // 关键两句代码~~~保持动画执行完毕后的状态 anim.removedOnCompletion = NO; anim.fillMode = kCAFillModeForwards; // 指定义动画的路径:沿着圆形路径移动 CGMutablePathRef path = CGPathCreateMutable(); CGPathAddEllipseInRect(path, NULL, CGRectMake(50, 50, 200, 200)); anim.path = path; // C语言中,创建就要释放 CGPathRelease(path); // 动画的时间函数:就是设置动画的执行节奏 // kCAMediaTimingFunctionEaseInEaseOut : 一开始比较慢, 中间会加速, 临近结束的时候, 会变慢 anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; // 设置动画的代理,目的是监听动画的开始、结束等状态 // 默认NSObject已经遵守了协议CAAnimationDelegate anim.delegate = self; // 3.最后一步,添加动画到图层 [self.nanaImgView.layer addAnimation:anim forKey:nil]; } // 关键帧动画演示_2 - (IBAction)keyFrameAnimation_2 { // CABasicAnimation 只支持两个值:fromValue --> toValue // 1.创建 核心动画之关键帧动画:KeyframeAnimation CAKeyframeAnimation *anim = [CAKeyframeAnimation animation]; // 2.设置动画属性 anim.keyPath = @"position"; anim.duration = 2.0; // 锚点为左上角 self.nanaImgView.layer.anchorPoint = CGPointMake(0, 0); NSValue *v1 = [NSValue valueWithCGPoint:CGPointZero]; NSValue *v2 = [NSValue valueWithCGPoint:CGPointMake(170, 0)]; NSValue *v3 = [NSValue valueWithCGPoint:CGPointMake(170, 330)]; NSValue *v4 = [NSValue valueWithCGPoint:CGPointMake(0, 330)]; NSValue *v5 = [NSValue valueWithCGPoint:CGPointZero]; anim.values = @[v1, v2, v3, v4,v5]; // anim.keyTimes = @[@(0.5), @(0.25), @(0.25)]; // 关键两句代码~~~保持动画执行完毕后的状态 anim.removedOnCompletion = NO; anim.fillMode = kCAFillModeForwards; // 3.最后一步,添加动画到图层 [self.nanaImgView.layer addAnimation:anim forKey:nil]; } #pragma mark - 某个动画的代理方法 // 动画开始的时候调用 - (void)animationDidStart:(CAAnimation *)anim { NSLog(@"animationDidStart"); } // 动画结束的时候调用 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { NSLog(@"animationDidStop"); }
// 角度转弧度公式,角*180/PI #define kAngle2Radius(angle) ((angle) / 180.0 * M_PI) // 开始同时播放多个动画(动画组) - (IBAction)animationGroup { // 1.创建旋转动画对象 CABasicAnimation *rotate = [CABasicAnimation animation]; // 动画作用于 旋转角度 rotate.keyPath = @"transform.rotation"; rotate.toValue = @(M_PI*4); // 2.创建缩放动画对象 CABasicAnimation *scale = [CABasicAnimation animation]; // 动画作用于 缩放 scale.keyPath = @"transform.scale"; scale.toValue = @(0.0); // 3.平移动画 CABasicAnimation *move = [CABasicAnimation animation]; // 动画作用于 平移位置 move.keyPath = @"transform.translation"; move.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)]; // 4.将所有的动画添加到动画组中 CAAnimationGroup *group = [CAAnimationGroup animation]; group.animations = @[rotate, scale, move]; group.duration = 2.0; // 关键两句代码~~~保持动画执行完毕后的状态 group.removedOnCompletion = NO; group.fillMode = kCAFillModeForwards; // 3.添加动画到图层,key只是为了将来控制它停止时才用到 [self.nanaImgView.layer addAnimation:group forKey:nil]; }
// // IconShakeController.m // 40_核心动画 // // Created by beyond on 14-9-18. // Copyright (c) 2014年 com.beyond. All rights reserved. // 核心动画之图标抖动 #import "IconShakeController.h" // 角度转弧度公式,角*180/PI #define kAngle2Radius(angle) ((angle) / 180.0 * M_PI) @interface IconShakeController () @property (weak, nonatomic) IBOutlet UIImageView *nanaImgView; // 开始抖动 - (IBAction)startShake; // 停止抖动 - (IBAction)stopShake; - (IBAction)dismiss; @end @implementation IconShakeController // 开始抖动 - (IBAction)startShake { // 1.创建 核心动画之关键帧动画:KeyframeAnimation CAKeyframeAnimation *anim = [CAKeyframeAnimation animation]; // 2.设置动画属性 // 左旋转,右旋转 anim.keyPath = @"transform.rotation"; // 数组 anim.values = @[@(kAngle2Radius(-3)), @(kAngle2Radius(3)), @(kAngle2Radius(-3))]; // 总计用时 anim.duration = 0.15; // 动画的重复执行次数:一直抖动 anim.repeatCount = MAXFLOAT; // 关键两句代码~~~保持动画执行完毕后的状态 anim.removedOnCompletion = NO; anim.fillMode = kCAFillModeForwards; // 3.添加动画到图层,key是为了将来控制它停止时用到 [self.nanaImgView.layer addAnimation:anim forKey:@"iconShake"]; } // 停止抖动 - (void)stopShake { // 为图层添加动画时,传入参数key,是为了控制它停止时用到 [self.nanaImgView.layer removeAnimationForKey:@"iconShake"]; } - (IBAction)dismiss { [self dismissViewControllerAnimated:YES completion:nil]; } @end
// // TransitionController.m // 40_核心动画_转场动画 // // Created by beyond on 14-9-18. // Copyright (c) 2014年 com.beyond. All rights reserved. // 核心动画之 转场动画 #import "TransitionController.h" @interface TransitionController () // 正在显示的图片的索引 @property (nonatomic, assign) int index; // 界面上的imageView @property (weak, nonatomic) IBOutlet UIImageView *nanaImgView; // 显示上一张图片 - (IBAction)previous; // 显示下一张图片 - (IBAction)next; - (IBAction)dismiss; @end @implementation TransitionController // 显示上一张图片 - (IBAction)previous { self.index--; if (self.index == -1) { self.index = 9; } NSString *filename = [NSString stringWithFormat:@"%d.png", self.index]; self.nanaImgView.image = [UIImage imageNamed:filename]; CATransition *anim = [CATransition animation]; anim.type = [self typeFromIndex:self.index]; // anim.type = @"cube"; // anim.subtype = kCATransitionFromLeft; // anim.type = kCATransitionFade; anim.duration = 2; [self.view.layer addAnimation:anim forKey:nil]; } // 显示下一张图片 - (IBAction)next { self.index++; if (self.index == 10) { self.index = 0; } NSString *filename = [NSString stringWithFormat:@"%d.png", self.index]; self.nanaImgView.image = [UIImage imageNamed:filename]; // 转场动画 CATransition *anim = [CATransition animation]; anim.type = [self typeFromIndex:self.index]; // anim.type = @"pageCurl"; // anim.subtype = kCATransitionFromRight; anim.duration = 2; // anim.startProgress = 0.0; // // anim.endProgress = 0.5; [self.view.layer addAnimation:anim forKey:nil]; } // 自定义方法,随机生成 不同的转场效果 /* 1.#define定义的常量 kCATransitionFade 交叉淡化过渡 kCATransitionMoveIn 新视图移到旧视图上面 kCATransitionPush 新视图把旧视图推出去 kCATransitionReveal 将旧视图移开,显示下面的新视图 2.用字符串表示 pageCurl 向上翻一页 pageUnCurl 向下翻一页 rippleEffect 滴水效果 suckEffect 收缩效果,如一块布被抽走 cube 立方体效果 oglFlip 上下翻转效果 */ - (NSString *)typeFromIndex:(int)index { switch (index%10) { case 0: return kCATransitionFade; break; case 1: return kCATransitionMoveIn; break; case 2: return kCATransitionPush; break; case 3: return kCATransitionReveal; break; case 4: return @"pageCurl"; break; case 5: return @"pageUnCurl"; break; case 6: return @"rippleEffect"; break; case 7: return @"suckEffect"; break; case 8: return @"cube"; break; case 9: return @"oglFlip"; break; default: break; } return nil; } - (IBAction)dismiss { [self dismissViewControllerAnimated:YES completion:nil]; } @end
UIView内部默认有个CALayer对象(根层),通过layer属性可以访问这个根层。
要注意的是,这个默认的层不允许重新创建,但可以往层里面添加子层
* UIView可以通过addSubview:方法添加子视图,
类似地,CALayer可以通过addSublayer:方法添加子层
接下来演示一下如何添加子层:
一、添加一个简单的图层
1 CALayer *myLayer = [CALayerlayer];
2// 设置层的宽度和高度(100x100)
3 myLayer.bounds = CGRectMake(0,0, 100, 100);
4// 设置层的位置
5 myLayer.position = CGPointMake(100,100);
6// 设置层的背景颜色:红色
7 myLayer.backgroundColor = [UIColor redColor].CGColor;
8// 设置层的圆角半径为10
9 myLayer.cornerRadius =10;
10
11// 添加myLayer到控制器的view的根层layer中
12[self.view.layer addSublayer:myLayer];
* 第1行创建了一个自动释放的CALayer对象,你也可以使用经典的alloc和init方法来创建
* 第12行将创建好的层添加到控制器的view的根层中
二、添加一个显示图片的图层
1 CALayer *myLayer = [CALayerlayer];
2// 设置层的宽度和高度(100x100)
3 myLayer.bounds = CGRectMake(0,0, 100, 100);
4// 设置层的位置
5 myLayer.position = CGPointMake(100,100);
6// 设置需要显示的图片
7 myLayer.contents = (id)[UIImage imageNamed:@"nana.png"].CGImage;
8// 设置层的圆角半径为10
9 myLayer.cornerRadius =10;
10// 如果设置了图片,需要设置这个属性为YES才有圆角效果
11 myLayer.masksToBounds = YES;
12
13// 添加myLayer到控制器的view的layer中
14[self.view.layer addSublayer:myLayer];
* 在第7行设置需要显示的图片,
注意,这里用的是UIImage的CGImage属性,是一种CGImageRef类型的数据
三、为什么CALayer中使用CGColorRef
和CGImageRef这2种数据类型,
而不用UIColor和UIImage?
* 首先要知道:CALayer是定义在QuartzCore框架中的;CGImageRef、CGColorRef两种数据类型是定义在CoreGraphics框架中的;UIColor、UIImage是定义在UIKit框架中的
* 其次,QuartzCore框架和CoreGraphics框架是可以跨平台使用的,在iOS和Mac OS X上都能使用,但是UIKit只能在iOS中使用
* 因此,为了保证可移植性,QuartzCore不能使用UIImage、UIColor,只能使用CGImageRef、CGColorRef
* 不过很多情况下,可以通过UIKit对象的特定方法,得到CoreGraphics对象,比如UIImage的CGImage方法可以返回一个CGImageRef
四、UIView和CALayer的选择
前面的2个效果不仅可以通过添加层来实现,还可以通过添加UIView来实现。
比如,第1个红色的层可以用一个UIView来实现,
第2个显示图片的层可以用一个UIImageView来实现。
既然CALayer和UIView都能实现相同的显示效果,那究竟该选择谁好呢?
* 其实,对比CALayer,UIView多了一个事件处理的功能。
也就是说,CALayer不能处理用户的触摸事件,而UIView可以
* 所以,如果显示出来的东西需要跟用户进行交互的话,用UIView;
如果不需要跟用户进行交互,用UIView或者CALayer都可以
* 当然,CALayer的性能会高一些,因为它少了事件处理的功能,更加轻量级
五、UIView和CALayer的其他关系
* UIView可以通过subviews属性访问所有的子视图,
类似地,CALayer也可以通过sublayers属性访问所有的子层
* UIView可以通过superview属性访问父视图,
类似地,CALayer也可以通过superlayer属性访问父层
* 下面再看一张UIView和CALayer的关系图:
如果两个UIView是父子关系,那么它们内部的CALayer也是父子关系。
即:父子UIView内部的根层也是父子层的关系
自定义图层,其实就是在层上绘图,一共有2种方法:
一、自定义层的方法1
方法描述:创建一个CALayer的子类,然后覆盖drawInContext:方法,使用Quartz2D API进行绘图
1.创建一个CALayer的子类,继承自CALayer
2.在.m文件中覆盖drawInContext:方法,在里面绘图
1@implementation MyLayer
2
3#pragma mark 绘制一个实心三角形
4 - (void)drawInContext:(CGContextRef)ctx {
5 // 设置为蓝色(后四个参数是:RGBA)
6 CGContextSetRGBFillColor(ctx,0, 0, 1, 1);
7
8
9 // 设置三角形的起点
10 CGContextMoveToPoint(ctx,50, 0);
11 // 从(50, 0)连线到(0, 100)
12 CGContextAddLineToPoint(ctx,0, 100);
13 // 从(0, 100)连线到(100, 100)
14 CGContextAddLineToPoint(ctx,100, 100);
15 // 合并路径,连接起点和终点
16 CGContextClosePath(ctx);
17
18 // 绘制路径
19 CGContextFillPath(ctx);
20 }
21
22 @end
3.在控制器中添加图层到屏幕上
1 MyLayer *layer = [MyLayer layer];
2// 设置层的宽高
3 layer.bounds = CGRectMake(0,0, 100, 100);
4// 设置层的位置
5 layer.position = CGPointMake(100,100);
6// 开始绘制图层(内部调用drawRect方法)
7[layer setNeedsDisplay];
8 [self.view.layer addSublayer:layer];
注意第7行,必需要调用setNeedsDisplay这个方法,才会触发drawInContext:方法的调用,然后进行绘图
二、自定义层的方法2
方法描述:
设置CALayer的delegate,然后让delegate实现drawLayer:inContext:方法,
当CALayer需要绘图时,会调用delegate的drawLayer:inContext:方法进行绘图。
* 这里要注意的是:
不能再将某个UIView设置为CALayer的delegate,
因为UIView对象已经是它内部根图层的delegate,
再次设置为其他层的delegate就会出问题。
UIView和它内部CALayer的默认关系图:
可见,UIView的根层的代理就是UIView自己
1.创建新的子图层,设置delegate为当前控制器,然后添加到控制器的view的layer中
1 CALayer *layer = [CALayer layer];
2// 设置delegate为当前控制器
3layer.delegate = self;
4// 设置层的宽高
5 layer.bounds = CGRectMake(0,0, 100, 100);
6// 设置层的位置
7 layer.position = CGPointMake(100,100);
8// 开始绘制图层(此时会调用代理的drawLayer方法)
9[layer setNeedsDisplay];
10[self.view.layer addSublayer:layer];
* 在第3行设置了CALayer的delegate,这里的self是指当前控制器
* 注意第9行,需要调用setNeedsDisplay这个方法,才会调用代理delegate的drawLayer:inContext:方法进行绘图
2.让CALayer的delegate(即当前控制器)实现drawLayer:inContext:方法
1#pragma mark 画一个矩形框
2 - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
3 // 设置颜色蓝色(参数是RGBA)
4 CGContextSetRGBStrokeColor(ctx,0, 0, 1, 1);
5 // 设置边框宽度
6 CGContextSetLineWidth(ctx,10);
7
8 // 添加一个跟层一样大的矩形到路径中
9 CGContextAddRect(ctx, layer.bounds);
10
11 // 绘制路径
12 CGContextStrokePath(ctx);
13 }
三、其他
1.总结
无论采取哪种方法来自定义层,继承或设置代理
都必须调用CALayer的setNeedsDisplay方法才能正常绘图。
2.UIView的详细显示过程
* 当UIView需要显示时,它内部的根层会准备好一个CGContextRef(图形上下文),然后调用根层的delegate(这里就是UIView)的drawLayer:inContext:方法,并且传入已经准备好的CGContextRef对象。而UIView在drawLayer:inContext:方法中又会调用自己的drawRect:方法
从而最终:只需在drawRect方法中写代码即可
* 平时在drawRect:中通过UIGraphicsGetCurrentContext()获取的就是由根层传入的CGContextRef对象,在drawRect:中完成的所有绘图都会填入到根层的CGContextRef中,最后才被拷贝至屏幕,进行显示
// // LayerController.m // 40_核心动画 // // Created by beyond on 14-9-19. // Copyright (c) 2014年 com.beyond. All rights reserved. /* 下面是大实话: UIView内部有一个根层 UIView是内部根层的delegate 1.根层会准备好一个 图层上下文 2.根层会调用代理(UIView)的drawLayer方法 并且将1.中准备好的图层上下文传进来 3.UIView的drawLayer方法,内部,会调用drawRect方法 因此,常用的drawRect方法中的所有东东, 全部被绘制到了 根层准备好的这个图层上下文中, 所以说,UIView中drawRect方法所画的所有东东,全画到了根层的上下文中 */ /* 自定义图层: 方式1: 子类,继承CALayer,实现drawLayer:inContext:方法 方式2: 在控制器中,直接创建一个子图层,设置其代理为当前控制器, 让代理实现的drawLayer方法中 画东东 注意:无论哪一种,都要手动调用子图层的setNeedsDisplay方法 */ // UIView *view; // UIView的根层的代理,已经是它自己了 // view.layer.delegate == view; // view的完整显示过程 // 1. view.layer会准备一个Layer Graphics Contex(图层类型的上下文) // 2. 调用view.layer.delegate(也就是view)的drawLayer:inContext:,并传入刚才准备好的图层上下文 // 3. view的drawLayer:inContext:方法内部又会调用view的drawRect:方法(就是我们经常写自定义绘图代码的方法) // 4. view就可以在drawRect:方法中实现绘图代码, 所有东西最终都绘制到view.layer上面 // 5. 系统再将view.layer的内容拷贝到屏幕, 于是完成了view的显示 #import "LayerController.h" #import "NanaLayer.h" @interface LayerController () - (IBAction)dismiss; - (IBAction)diyCALayer_1; - (IBAction)diyCALayer_2; @property (nonatomic, strong) CALayer *layer; // 隐式动画,即,改变CALayer的一些属性值,默认就会有动画效果 - (IBAction)layerAnimation; @property (weak, nonatomic) IBOutlet UIView *purpleView; @property (weak, nonatomic) IBOutlet UIImageView *nanaImgView; // 演示CALayer的基本属性 - (IBAction)layerBasicAttribute_1; - (IBAction)layerBasicAttribute_2; - (IBAction)layerBasicAttribute_3; @end @implementation LayerController #pragma mark - 连线方法 // 自定义图层方式1:使用子类 图层,覆盖父类CALayer的drawLayer,进行绘图 - (IBAction)diyCALayer_1 { NanaLayer *layer = [NanaLayer layer]; layer.bounds = CGRectMake(0, 0, 100, 100); layer.backgroundColor = [UIColor blueColor].CGColor; layer.anchorPoint = CGPointZero; // 无论哪种自定义图层,都必须 调用图层的setNeedsDisplay方法,才可以显示 [layer setNeedsDisplay]; [self.view.layer addSublayer:layer]; } // 自定义图层方式2:通过设置代理,调用代理的drawLayer方法,进行绘图 - (IBAction)diyCALayer_2 { CALayer *layer = [CALayer layer]; layer.bounds = CGRectMake(0, 0, 100, 100); layer.backgroundColor = [UIColor blackColor].CGColor; layer.anchorPoint = CGPointZero; layer.position = CGPointMake(100, 100); // 设置新建的图层代理,让代理实现drawLayer方法 // layer.delegate = self;(不知道为啥会崩溃???) // 无论哪种自定义图层,都必须 调用图层的setNeedsDisplay方法,才可以显示 [layer setNeedsDisplay]; [self.view.layer addSublayer:layer]; } #pragma mark - 图层的代理方法 // 自定义图层方式2:通过设置代理,调用代理的drawLayer方法,进行绘图 - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { // 设置RGB颜色 CGContextSetRGBFillColor(ctx, 1, 0, 0, 1); // 画个rect CGContextAddRect(ctx, CGRectMake(0, 0, 20, 20)); // 闭合路径 CGContextFillPath(ctx); } - (IBAction)dismiss { [self dismissViewControllerAnimated:NO completion:nil]; } #pragma mark - CALayer隐式动画 // 隐式动画,即,改变CALayer的一些属性值,默认就会有动画效果 - (IBAction)layerAnimation { // 创建一个layer并添加,2秒后,执行隐式动画 CALayer *layer = [CALayer layer]; layer.bounds = CGRectMake(0, 0, 100, 100); layer.backgroundColor = [UIColor redColor].CGColor; layer.position = CGPointZero; layer.anchorPoint = CGPointZero; [self.view.layer addSublayer:layer]; self.layer = layer; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 2秒后,执行隐式动画 // self.layer.backgroundColor = [UIColor blueColor].CGColor; // 如果不想要隐式动画,可通过开启事务 进行关闭动画 // [CATransaction begin]; // [CATransaction setDisableActions:YES]; // self.layer.position = CGPointMake(100, 100); self.layer.opacity = 0.5; // [CATransaction commit]; // 提交事务 }); } #pragma mark - 连线之CALayer的基本属性 // (边框加的是内边框) - (IBAction)layerBasicAttribute_1 { // 边框宽度(边框加的是内边框) self.purpleView.layer.borderWidth = 10; // 边框颜色 self.purpleView.layer.borderColor = [UIColor greenColor].CGColor; // 圆角 self.purpleView.layer.cornerRadius = 10; // self.purpleView.layer.masksToBounds = YES; // 阴影颜色 self.purpleView.layer.shadowColor = [UIColor blueColor].CGColor; // 阴影偏差 self.purpleView.layer.shadowOffset = CGSizeMake(20, 20); // 阴影不透明度 self.purpleView.layer.shadowOpacity = 0.5; } - (IBAction)layerBasicAttribute_2 { // 边框宽度 // self.nanaImgView.layer.borderWidth = 10; // // 边框颜色 // self.nanaImgView.layer.borderColor = [UIColor greenColor].CGColor; // 圆角 self.nanaImgView.layer.cornerRadius = 10; // 超出主层边框范围的内容都剪掉 self.nanaImgView.layer.masksToBounds = YES; // 阴影颜色 self.nanaImgView.layer.shadowColor = [UIColor blueColor].CGColor; // 阴影偏差 self.nanaImgView.layer.shadowOffset = CGSizeMake(20, 20); // 阴影不透明度 self.nanaImgView.layer.shadowOpacity = 0.5; } - (IBAction)layerBasicAttribute_3 { self.nanaImgView.layer.transform = CATransform3DMakeScale(1.5, 0.5, 0); self.nanaImgView.transform = CGAffineTransformMakeRotation(M_PI_4); self.nanaImgView.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1); NSValue *value = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI_4, 0, 0, 1)]; [self.nanaImgView.layer setValue:value forKeyPath:@"transform"]; [self.nanaImgView.layer setValue:@(M_PI_2) forKeyPath:@"transform.rotation"]; self.nanaImgView.layer.transform = CATransform3DMakeScale(0.5, 2, 0); [self.nanaImgView.layer setValue:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 2, 0)] forKeyPath:@"transform"]; // 可以传递哪些key path, 在官方文档搜索 "CATransform3D key paths" [self.nanaImgView.layer setValue:@(100) forKeyPath:@"transform.translation.x"]; } - (IBAction)layerBasicAttribute_4 { // 创建一个 用于展示图片的图层 // CALayer *layer = [[CALayer alloc] init]; CALayer *layer = [CALayer layer]; layer.backgroundColor = [UIColor redColor].CGColor; layer.bounds = CGRectMake(0, 0, 150, 150); layer.position = CGPointMake(200, 100); layer.cornerRadius = 10; layer.masksToBounds = YES; layer.contents = (id)[UIImage imageNamed:@"nanaLogo.png"].CGImage; [self.view.layer addSublayer:layer]; } @end
// // NanaView.m // 40_核心动画 // // Created by beyond on 14-9-19. // Copyright (c) 2014年 com.beyond. All rights reserved. /* 下面是大实话: UIView内部有一个根层 UIView是内部根层的delegate 1.根层会准备好一个 图层上下文 2.根层会调用代理(UIView)的drawLayer方法 并且将1.中准备好的图层上下文传进来 3.UIView的drawLayer方法,内部,会调用drawRect方法 因此,常用的drawRect方法中的所有东东, 全部被绘制到了 根层准备好的这个图层上下文中, 4.所以说,UIView中drawRect方法所画的所有东东,全画到了根层的上下文中 */ #import "NanaView.h" @implementation NanaView // 复习,UIView画圆 - (void)drawRect:(CGRect)rect { // 1.取得图形上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 2.设置红色 CGContextSetRGBFillColor(ctx, 1, 0, 0, 1); // 添加圆 CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 50, 50)); // 3.完成实心绘制 CGContextFillPath(ctx); } @end
// // NanaLayer.m // 40_核心动画 // // Created by beyond on 14-9-19. // Copyright (c) 2014年 com.beyond. All rights reserved. /* 自定义图层: 方式1: 子类,继承CALayer,实现drawLayer:inContext:方法 方式2: 在控制器中,直接创建一个子图层,设置其代理为当前控制器, 让代理实现的drawLayer方法中 画东东 注意:无论哪一种,都要手动调用子图层的setNeedsDisplay方法 */ #import "NanaLayer.h" @implementation NanaLayer /** * 只有明显地调用子图层的setNeedsDisplay方法, 才会调用drawInContext:方法进行绘制 */ - (void)drawInContext:(CGContextRef)ctx { // 红色 CGContextSetRGBFillColor(ctx, 1, 0, 0, 1); // 添加圆 CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 50, 50)); // 实心绘制 CGContextFillPath(ctx); } @end
速度控制函数(CAMediaTimingFunction) 1.kCAMediaTimingFunctionLinear(线性):匀速,给你一个相对静态的感觉 2.kCAMediaTimingFunctionEaseIn(渐进):动画缓慢进入,然后加速离开 3.kCAMediaTimingFunctionEaseOut(渐出):动画全速进入,然后减速的到达目的地 4.kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。 CAAnimation在分类中定义了代理方法 @interface NSObject (CAAnimationDelegate) - (void)animationDidStart:(CAAnimation *)anim; - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag; @end fillMode属性值(要想fillMode有效,最好设置removedOnCompletion=NO) kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态 kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态 kCAFillModeBackwards 在动画开始前,你只要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始.你可以这样设定测试代码,将一个动画加入一个layer的时候延迟5秒执行.然后就会发现在动画没有开始的时候,只要动画被加入了layer,layer便处于动画初始状态 kCAFillModeBoth 这个其实就是上面两个的合成.动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状 CALayer上动画的暂停和恢复 // t - active local time 图层的本地时间 // tp - parent layer time 父图层的时间 // 父图层和图层本地的时间换算公式 // t = (tp - beginTime) * speed + timeOffset // beginTime = tp - (t - timeOffset)/speed #pragma mark 暂停CALayer的动画 -(void)pauseLayer:(CALayer*)layer { CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.speed = 0.0; // 让CALayer的时间停止走动 layer.timeOffset = pausedTime; // 让CALayer的时间停留在pausedTime这个时刻 } #pragma mark 恢复CALayer的动画 -(void)resumeLayer:(CALayer*)layer { CFTimeInterval pausedTime = layer.timeOffset; layer.speed = 1.0; // 让CALayer的时间继续行走 layer.timeOffset = 0.0; // 取消上次记录的停留时刻 layer.beginTime = 0.0; // 取消上次设置的时间 // 计算暂停的时间(这里用CACurrentMediaTime()-pausedTime也是一样的) CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; // 设置相对于父坐标系的开始时间(往后退timeSincePause) layer.beginTime = timeSincePause; }
// 平移动画 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"]; anim.duration = 1; // 动画持续1秒 // 因为CGPoint是结构体,所以用NSValue包装成一个OC对象 anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(50, 50)]; anim.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)]; [layer addAnimation:anim forKey:@"MyAnim"]; // 通过MyAnim可以取回相应的动画对象,比如用来中途取消动画 // 缩放动画 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"]; // 没有设置fromValue说明当前状态作为初始值 // 宽度(width)变为原来的2倍,高度(height)变为原来的1.5倍 anim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2, 1.5, 1)]; anim.duration = 1; [layer addAnimation:anim forKey:nil]; // 旋转动画 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"]; // 这里是以向量(1, 1, 0)为轴,旋转π/2弧度(90°) // 如果只是在手机平面上旋转,就设置向量为(0, 0, 1),即Z轴 anim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI_2, 1, 1, 0)]; anim.duration = 1; [layer addAnimation:anim forKey:nil];
在关键帧动画中还有一个非常重要的参数,那便是calculationMode,计算模式.
其主要针对的是每一帧的内容为一个坐标点的情况,也就是对anchorPoint 和position 进行的动画.
当在平面座标系中有多个离散的点的时候,可以是离散的,也可以直线相连后进行插值计算,也可以使用圆滑的曲线将他们相连后进行插值计算.
calculationMode目前提供如下几种模式:
kCAAnimationLinear calculationMode的默认值,表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算;
kCAAnimationDiscrete 离散的,就是不进行插值计算,所有关键帧直接逐个进行显示;
kCAAnimationPaced 使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效;
kCAAnimationCubic 对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,这里的主要目的是使得运行的轨迹变得圆滑;
kCAAnimationCubicPaced 看这个名字就知道和kCAAnimationCubic有一定联系,
其实就是在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,
此时keyTimes以及timingFunctions也是无效的
<pre name="code" class="objc"><div class="cnblogs_code" style="margin:5px 0px; padding:5px; border:1px solid rgb(204,204,204); overflow:auto; line-height:26px; font-family:'Courier New'; background-color:rgb(245,245,245)"> <p> </p> <pre name="code" class="objc">/* 转场动画的过渡效果 fade //交叉淡化过渡(不支持过渡方向) kCATransitionFade push //新视图把旧视图推出去 kCATransitionPush moveIn //新视图移到旧视图上面 kCATransitionMoveIn reveal //将旧视图移开,显示下面的新视图 kCATransitionReveal cube //立方体翻滚效果 oglFlip //上下左右翻转效果 suckEffect //收缩效果,如一块布被抽走(不支持过渡方向) rippleEffect //滴水效果(不支持过渡方向) pageCurl //向上翻页效果 pageUnCurl //向下翻页效果 cameraIrisHollowOpen //相机镜头打开效果(不支持过渡方向) cameraIrisHollowClose //相机镜头关上效果(不支持过渡方向) */ /* 过渡方向 kCATransitionFromRight kCATransitionFromLeft kCATransitionFromBottom kCATransitionFromTop */ CATransition的使用 CATransition *anim = [CATransition animation]; anim.type = @“cube”; // 动画过渡类型 anim.subtype = kCATransitionFromTop; // 动画过渡方向 anim.duration = 1; // 动画持续1s // 代理,动画执行完毕后会调用delegate的animationDidStop:finished: anim.delegate = self; /*******中间穿插改变layer属性的代码**********/ [layer addAnimation:anim forKey:nil];
一、Core Animation简介
* Core Animation可以跨Mac OS X和iOS平台。
* Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。
* 要注意的是,Core Animation是直接作用在CALayer上的,并非UIView,称虚假动画。
二、Core Animation的使用步骤
1.初始化一个CAAnimation对象,并设置一些动画相关属性
2.通过调用CALayer的addAnimation:forKey:方法增加CAAnimation对象到CALayer中,这样就能开始执行动画了
3.通过调用CALayer的removeAnimationForKey:方法可以停止CALayer中的动画
三、CAAnimation
* 从前面的叙述可以看出,要想执行动画,就必须初始化一个CAAnimation对象。
* 其实,一般情况下,我们使用的比较多的是CAAnimation的子类,因此,先大致看看CAAnimation的继承结构:
黑线代表继承,黑色文字代表类名,
白色文字代表属性。
其中CAMediaTiming是一个协议(protocol)。
由下图看出:CAAnimation有三个儿子:动画组、属性动画、转场动画
其中属性动画也有二个儿子:基本动画、帧动画
基本动画只有fromValue和toValue,而帧动画是数组形式
1.CAAnimation的常用属性
* CAAnimation是所有动画类的父类,但是它不能直接使用,应该使用它的子类
* 常见属性有:
1> duration:动画的持续时间
2> repeatCount:动画的重复次数
3> timingFunction:控制动画运行的节奏
timingFunction可选的值有:
4> delegate:动画代理,用来监听动画的执行过程
代理对象需要实现的方法有:(这几个方法被定义在NSObject某个分类中)
1@interface NSObject (CAAnimationDelegate)
2// 动画开始执行的时候触发这个方法
3 - (void)animationDidStart:(CAAnimation *)anim;
4
5// 动画执行完毕的时候触发这个方法
6 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
7@end
* 上面介绍的所有属性都是属于CAAnimation的,因此,CAAnimation的所有子类都能使用它们。
2.其他
* CAPropertyAnimation也是不能直接使用的,也要使用它的子类
* 所以,能用的动画类只剩下4个:
孙子两个:CABasicAnimation、CAKeyframeAnimation、
儿子两个:CATransition、CAAnimationGroup
四、CAPropertyAnimation
* CAPropertyAnimation是CAAnimation的子类,但是不能直接使用,要想创建动画对象,应该使用它的两个子类:CABasicAnimation和CAKeyframeAnimation
* 它有个NSString类型的keyPath属性,你可以指定CALayer的某个属性名为keyPath,并且对CALayer的这个属性的值进行修改,达到相应的动画效果。比如,指定@"position"为keyPath,就会修改CALayer的position属性的值,以达到平移的动画效果
* 因此,初始化好CAPropertyAnimation的子类对象后,必须先设置keyPath,搞清楚要修改的是CALayer的哪个属性,执行的是怎样的动画
CABasicAnimation是CAPropertyAnimation的子类,
使用它可以实现一些基本的动画效果,它可以让CALayer的某个属性从某个值渐变到另一个值。
下面就用CABasicAnimation实现几个简单的动画。
* 先初始化一个UIView添加到控制器的view中,
然后在这个UIView的layer上执行动画,下面的self是指控制器
1 _myView = [[UIView alloc] init];
2 _myView.layer.position = CGPointMake(100,100);
3 _myView.layer.bounds = CGRectMake(0,0, 100, 100);
4 _myView.backgroundColor = [UIColor blueColor];
5[self.view addSubview:_myView];
6 [_myView release];
一、平移动画
1.方法1
1// 说明这个动画对象要对CALayer的position属性执行动画
2 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];
3// 动画持续1.5s
4 anim.duration =1.5;
5
6// position属性值从(50, 80)渐变到(300, 350)
7 anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(50,80)];
8 anim.toValue = [NSValue valueWithCGPoint:CGPointMake(300,350)];
9
10// 设置动画的代理
11 anim.delegate = self;
12
13// 由于核心动画是虚假动画,所以要显式指出 保持动画执行后的状态
14 anim.removedOnCompletion = NO;
15 anim.fillMode = kCAFillModeForwards;
16
17// 添加动画对象到图层上
18 [_myView.layer addAnimation:anim forKey:@"translate"];
* 第2行设置的keyPath是@"position",说明要修改的是CALayer的position属性,也就是会执行平移动画
* 注意第7、8行,这里并不是直接使用CGPoint这种结构体类型,而是要先包装成NSValue对象后再使用。这2行代码表示CALayer从位置(50, 80)移动到位置(300, 350)
* 如果将第8行的toValue换成byValue,代表CALayer从位置(50, 80)开始向右移动300、向下移动350,也就是移动到位置(350, 430)
* 默认情况下,动画执行完毕后,动画会自动从CALayer上移除,CALayer又会回到原来的状态。为了保持动画执行后的状态,可以加入第14、15行代码
* 第18行后面的@"translate"是给动画对象起个名称,以后可以调用CALayer的removeAnimationForKey:方法根据动画名称停止相应的动画
* 第11行是设置动画的代理,可以监听动画的执行过程,这里设置控制器为代理。代理需要实现的方法有:
1#pragma mark 动画开始
2 - (void)animationDidStart:(CAAnimation *)anim {
3 NSLog(@"动画开始了");
4 }
5
6#pragma mark 动画结束
7 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
8 // 查看一下动画执行完毕后的position值
9 NSString *string = NSStringFromCGPoint(_myView.layer.position);
10 NSLog(@"动画结束了,position:%@",string);
11 }
2.方法2
1 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"];
2 anim.duration =1;
3 // 参数是:X、Y、Z
4CATransform3D form =CATransform3DMakeTranslation(350,350,0);
5 anim.toValue = [NSValue valueWithCATransform3D:form];
6
7 [_myView.layer addAnimation:anim forKey:nil];
通过CALayer的transform属性实现平移动画,layer会从自己的初始位置平移到(350, 350)位置
二、缩放动画
1.方法1
1 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"bounds"];
2 anim.duration =2;
3
4 anim.toValue = [NSValue valueWithCGRect:CGRectMake(0,0, 30, 30)];
5
6 [_myView.layer addAnimation:anim forKey:nil];
layer会从原来的尺寸(100x100)变为30x30
2.方法2
1 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"];
2 anim.duration =1.5; // 动画持续1.5s
3 // 参数:X、Y、Z
4// CALayer的宽度从0.5倍变为2倍
5// CALayer的高度从0.5倍变为1.5倍
6 anim.fromValue = [NSValuevalueWithCATransform3D:CATransform3DMakeScale(0.5,0.5,1)];
7 anim.toValue = [NSValuevalueWithCATransform3D:CATransform3DMakeScale(2,1.5,1)];
8
9 [_myView.layer addAnimation:anim forKey:nil];
三、旋转动画
1 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"];
2 anim.duration =1.5;
3 // 参数:X、Y、Z
4// 绕着(0, 0, 1)这个向量轴顺时针旋转45°
5 anim.toValue = [NSValuevalueWithCATransform3D:CATransform3DMakeRotation(M_PI_4,0,0,1)];
6
7 [_myView.layer addAnimation:anim forKey:nil];
其实可以不用设置fromValue,这里只设置了toValue
四、其他
* 除开前面使用的position、transform属性,其实CALayer还有好多属性都可以形成动画,这些属性统称为"Animatable Properties"。
* CABasicAnimation虽然能够做很多基本的动画效果,
但是有个局限性,只能让CALayer的属性从某个值渐变到另一个值,仅仅是在2个值之间渐变,
因此:CAKeyFrameAnimation可以看成是扩展了的CABasicAnimation
// 说明需要执行动画 [UIView beginAnimations:nil context:nil]; // 设置动画持续事件 [UIView setAnimationDuration:1]; // 设置转场动画 [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:YES]; // 交换子视图的位置 [self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1]; // 提交动画 [UIView commitAnimations];
typedefNS_ENUM(NSInteger, UIViewAnimationCurve) {
UIViewAnimationCurveEaseInOut, // slow at beginning and end
UIViewAnimationCurveEaseIn, // slow at beginning
UIViewAnimationCurveEaseOut, // slow at end
UIViewAnimationCurveLinear
};
typedef NS_ENUM(NSInteger,UIViewAnimationTransition) {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
UIViewAnimationOptionCurveEaseInOut
UIViewAnimationOptionCurveEaseIn
UIViewAnimationOptionCurveEaseOut
UIViewAnimationOptionCurveLinear
UIViewAnimationOptionTransitionNone
UIViewAnimationOptionTransitionFlipFromLeft
UIViewAnimationOptionTransitionFlipFromRight
UIViewAnimationOptionTransitionCurlUp
UIViewAnimationOptionTransitionCurlDown
UIViewAnimationOptionTransitionCrossDissolve
UIViewAnimationOptionTransitionFlipFromTop
UIViewAnimationOptionTransitionFlipFromBottom