自定义 Layer 属性的动画
默认情况下,CALayer 及其子类的绝大部分标准属性都可以执行动画,无论是添加一个 CAAnimation 到 Layer(显式动画),亦或是为属性指定一个动作然后修改它(隐式动画)。
- @interface ClockFace: CAShapeLayer
- @property (nonatomic, strong) NSDate *time;
- @end
- @interface ClockFace ()
- // 私有属性,译者注:这里申明的是 CALayer ,下面分配的却是 CAShapeLayer ,按照文字,应该都是 CAShapeLayer 才对
- @property (nonatomic, strong) CALayer *hourHand;
- @property (nonatomic, strong) CALayer *minuteHand;
- @end
- @implementation ClockFace
- - (id)init
- {
- if ((self = [super init]))
- {
- self.bounds = CGRectMake(0, 0, 200, 200);
- self.path = [UIBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
- self.fillColor = [UIColor whiteColor].CGColor;
- self.strokeColor = [UIColor blackColor].CGColor;
- self.lineWidth = 4;
- self.hourHand = [CAShapeLayer layer];
- self.hourHand.path = [UIBezierPath bezierPathWithRect:CGRectMake(-2, -70, 4, 70)].CGPath;
- self.hourHand.fillColor = [UIColor blackColor].CGColor;
- self.hourHand.position = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
- [self addSublayer:self.hourHand];
- self.minuteHand = [CAShapeLayer layer];
- self.minuteHand.path = [UIBezierPath bezierPathWithRect:CGRectMake(-1, -90, 2, 90)].CGPath;
- self.minuteHand.fillColor = [UIColor blackColor].CGColor;
- self.minuteHand.position = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
- [self addSublayer:self.minuteHand];
- }
- return self;
- }
- @end
- @interface ViewController ()
- @property (nonatomic, strong) IBOutlet UIDatePicker *datePicker;
- @property (nonatomic, strong) ClockFace *clockFace;
- @end
- @implementation ViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- // 添加时钟面板 Layer
- self.clockFace = [[ClockFace alloc] init];
- self.clockFace.position = CGPointMake(self.view.bounds.size.width / 2, 150);
- [self.view.layer addSublayer:self.clockFace];
- // 设置默认时间
- self.clockFace.time = [NSDate date];
- }
- - (IBAction)setTime
- {
- self.clockFace.time = self.datePicker.date;
- }
- @end
- - (void)setTime:(NSDate *)time
- {
- _time = time;
- NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
- NSDateComponents *components = [calendar components:NSHourCalendarUnit | NSMinuteCalendarUnit fromDate:time];
- self.hourHand.affineTransform = CGAffineTransformMakeRotation(components.hour / 12.0 * 2.0 * M_PI);
- self.minuteHand.affineTransform = CGAffineTransformMakeRotation(components.minute / 60.0 * 2.0 * M_PI);
- }

- @interface ClockFace ()
- @end
- @implementation ClockFace
- @dynamic time;
- - (id)init
- {
- if ((self = [super init]))
- {
- self.bounds = CGRectMake(0, 0, 200, 200);
- }
- return self;
- }
- @end
- @interface ViewController () <UITextFieldDelegate>
- @property (nonatomic, strong) IBOutlet UITextField *textField;
- @property (nonatomic, strong) ClockFace *clockFace;
- @end
- @implementation ViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- // 添加时钟面板 Layer
- self.clockFace = [[ClockFace alloc] init];
- self.clockFace.position = CGPointMake(self.view.bounds.size.width / 2, 150);
- [self.view.layer addSublayer:self.clockFace];
- }
- - (BOOL)textFieldShouldReturn:(UITextField *)textField
- {
- [textField resignFirstResponder];
- return YES;
- }
- - (void)textFieldDidEndEditing:(UITextField *)textField
- {
- self.clockFace.time = [textField.text floatValue];
- }
- @end
- + (BOOL)needsDisplayForKey:(NSString *)key
- {
- if ([@"time" isEqualToString:key])
- {
- return YES;
- }
- return [super needsDisplayForKey:key];
- }
- - (void)display
- {
- NSLog(@"time: %f", self.time);
- }
- 2014-04-28 22:37:04.253 ClockFace[49145:60b] time: 1.500000
- - (id<CAAction>)actionForKey:(NSString *)key
- {
- if ([key isEqualToString:@"time"])
- {
- CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
- animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
- animation.fromValue = @(self.time);
- return animation;
- }
- return [super actionForKey:key];
- }
- 2014-04-28 22:37:04.253 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.255 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.351 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.370 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.388 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.407 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.425 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.443 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.461 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.479 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.497 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.515 ClockFace[49145:60b] time: 1.500000
- 2014-04-28 22:37:04.755 ClockFace[49145:60b] time: 1.500000
- - (id<CAAction>)actionForKey:(NSString *)key
- {
- if ([key isEqualToString:@"time"])
- {
- CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
- animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
- animation.fromValue = @([[self presentationLayer] time]);
- return animation;
- }
- return [super actionForKey:key];
- }
- - (void)display
- {
- NSLog(@"time: %f", [[self presentationLayer] time]);
- }
- 2014-04-28 22:43:31.200 ClockFace[49176:60b] time: 0.000000
- 2014-04-28 22:43:31.203 ClockFace[49176:60b] time: 0.002894
- 2014-04-28 22:43:31.263 ClockFace[49176:60b] time: 0.363371
- 2014-04-28 22:43:31.300 ClockFace[49176:60b] time: 0.586421
- 2014-04-28 22:43:31.318 ClockFace[49176:60b] time: 0.695179
- 2014-04-28 22:43:31.336 ClockFace[49176:60b] time: 0.803713
- 2014-04-28 22:43:31.354 ClockFace[49176:60b] time: 0.912598
- 2014-04-28 22:43:31.372 ClockFace[49176:60b] time: 1.021573
- 2014-04-28 22:43:31.391 ClockFace[49176:60b] time: 1.134173
- 2014-04-28 22:43:31.409 ClockFace[49176:60b] time: 1.242892
- 2014-04-28 22:43:31.427 ClockFace[49176:60b] time: 1.352016
- 2014-04-28 22:43:31.446 ClockFace[49176:60b] time: 1.460729
- 2014-04-28 22:43:31.464 ClockFace[49176:60b] time: 1.500000
- 2014-04-28 22:43:31.636 ClockFace[49176:60b] time: 1.500000
- - (void)display
- {
- // 获取时间插值
- float time = [self.presentationLayer time];
- // 创建绘制上下文
- UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
- CGContextRef ctx = UIGraphicsGetCurrentContext();
- // 绘制时钟面板
- CGContextSetLineWidth(ctx, 4);
- CGContextStrokeEllipseInRect(ctx, CGRectInset(self.bounds, 2, 2));
- // 绘制时针
- CGFloat angle = time / 12.0 * 2.0 * M_PI;
- CGPoint center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
- CGContextSetLineWidth(ctx, 4);
- CGContextMoveToPoint(ctx, center.x, center.y);
- CGContextAddLineToPoint(ctx, center.x + sin(angle) * 80, center.y - cos(angle) * 80);
- CGContextStrokePath(ctx);
- // 绘制分针
- angle = (time - floor(time)) * 2.0 * M_PI;
- CGContextSetLineWidth(ctx, 2);
- CGContextMoveToPoint(ctx, center.x, center.y);
- CGContextAddLineToPoint(ctx, center.x + sin(angle) * 90, center.y - cos(angle) * 90);
- CGContextStrokePath(ctx);
- //set backing image 设置 contents
- self.contents = (id)UIGraphicsGetImageFromCurrentImageContext().CGImage;
- UIGraphicsEndImageContext();
- }

- const NSInteger hoursOnAClockFace = 12;
- - (void)display
- {
- // 获取时间插值
- float time = [self.presentationLayer time] / hoursOnAClockFace;
- // 从之前定义好的图像数组里获取图像帧
- NSInteger numberOfFrames = [self.frames count];
- NSInteger index = round(time * numberOfFrames) % numberOfFrames;
- UIImage *frame = self.frames[index];
- self.contents = (id)frame.CGImage;
- }
- @interface AudioLayer : CALayer
- - (id)initWithAudioFileURL:(NSURL *)URL;
- @property (nonatomic, assign) float volume;
- - (void)play;
- - (void)stop;
- - (BOOL)isPlaying;
- @end
- @interface AudioLayer ()
- @property (nonatomic, strong) AVAudioPlayer *player;
- @end
- @implementation AudioLayer
- @dynamic volume;
- - (id)initWithAudioFileURL:(NSURL *)URL
- {
- if ((self = [self init]))
- {
- self.volume = 1.0;
- self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:URL error:NULL];
- }
- return self;
- }
- - (void)play
- {
- [self.player play];
- }
- - (void)stop
- {
- [self.player stop];
- }
- - (BOOL)isPlaying
- {
- return self.player.playing;
- }
- + (BOOL)needsDisplayForKey:(NSString *)key
- {
- if ([@"volume" isEqualToString:key])
- {
- return YES;
- }
- return [super needsDisplayForKey:key];
- }
- - (id<CAAction>)actionForKey:(NSString *)key
- {
- if ([key isEqualToString:@"volume"])
- {
- CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
- animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
- animation.fromValue = @([[self presentationLayer] volume]);
- return animation;
- }
- return [super actionForKey:key];
- }
- - (void)display
- {
- // 设置音量值为合适的音量插值
- self.player.volume = [self.presentationLayer volume];
- }
- @end
- @interface ViewController ()
- @property (nonatomic, strong) AudioLayer *audioLayer;
- @end
- @implementation ViewController
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- NSURL *musicURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"music" ofType:@"caf"]];
- self.audioLayer = [[AudioLayer alloc] initWithAudioFileURL:musicURL];
- [self.view.layer addSublayer:self.audioLayer];
- }
- - (IBAction)playPauseMusic:(UIButton *)sender
- {
- if ([self.audioLayer isPlaying])
- {
- [self.audioLayer stop];
- [sender setTitle:@"Play Music" forState:UIControlStateNormal];
- }
- else
- {
- [self.audioLayer play];
- [sender setTitle:@"Pause Music" forState:UIControlStateNormal];
- }
- }
- - (IBAction)fadeIn
- {
- self.audioLayer.volume = 1;
- }
- - (IBAction)fadeOut
- {
- self.audioLayer.volume = 0;
- }
- @end
自定义 Layer 属性的动画的更多相关文章
- 自定义Property属性动画
同步发表于 http://avenwu.net/customlayout/2015/04/06/custom_property_animation/ 代码获取 git clone https://gi ...
- Core Animation 文档翻译 (第七篇)——改变Layer的默认动画
前言 核心动画使用action对象实现它的可视化动画.一个action对象是指遵循CAAction协议并定义了Layer相关的动画行为的对象.所有的CAAnimation对象实现了这个协议,无论何时L ...
- ios开发图层layer与核心动画二:CATransform3D,CAlayear和UIView区别,layer的position和anchorpoint
一:CATransform3D #import "ViewController.h" @interface ViewController () @property (weak, n ...
- Android动画效果之自定义ViewGroup添加布局动画
前言: 前面几篇文章介绍了补间动画.逐帧动画.属性动画,大部分都是针对View来实现的动画,那么该如何为了一个ViewGroup添加动画呢?今天结合自定义ViewGroup来学习一下布局动画.本文将通 ...
- iOS开发UI篇—CAlayer(自定义layer)
iOS开发UI篇—CAlayer(自定义layer) 一.第一种方式 1.简单说明 以前想要在view中画东西,需要自定义view,创建一个类与之关联,让这个类继承自UIView,然后重写它的Draw ...
- 【UWP】对 Thickness 类型属性进行动画
好几个月没写 blog 了,一个是在忙新版的碧影壁纸,另一方面是等(观望)周年更新的 api(不过现在还是比较失望,仍然没法支持矩形以外的 Clip).闲话少说,进入主题. 在 UWP 中,出于性能考 ...
- iOS 自定义layer的两种方式
在iOS中,你能看得见摸得着的东西基本都是UIView,比如一个按钮,一个标签,一个文本输入框,这些都是UIView: 其实UIView之所以能显示在屏幕上,完全是因为它内部的一个图层 在创建UIVi ...
- iOS开发UI篇—自定义layer
一.第一种方式 1.简单说明 以前想要在view中画东西,需要自定义view,创建一个类与之关联,让这个类继承自UIView,然后重写它的DrawRect:方法,然后在该方法中画图. 绘制图形的步骤: ...
- iOS-自定义Model转场动画-仿酷我音乐播放器效果
周末,闲来无事,仿写了酷我音乐播放器效果: 效果图如下: 实现思路: 1.实现手势处理视图旋转 2.自定义Model动画: 1.手势是利用了一个UIPanGestureRecognizer手势: 注意 ...
随机推荐
- LoadRuner性能测试之内存分析方法及步骤(Windows)
1.首先观察Available Mbytes(可用内存),至少要>=1/2的内存空间 2.然后观察Pages/sec值是不是很大 3.再观察Page Faules/sec是不是很大,其值表示 ...
- Python进阶之路---1.2python版本差异
Python2.*与python3.*版本差异 作为一个初学者,我们应该如何选择python的版本进行学习呢,这两个版本有什么区别呢,接下来让我们简单了解一下,以便我们后续的学习. Python版本差 ...
- DotNet程序汉化过程--SnippetCompiler奇葩的字符串
开篇前言 汉化的过程总会遇到各种各样的问题,让人抓狂,这一篇我就来讲解一下一个特殊的单词的汉化以及我的“艰辛历程”. 起因介绍 在SnippetCompiler有这么一个奇葩的字符串“查找>&g ...
- CSS样式之背景、文本
一.背景 1.背景颜色用background-color属性,例如:body{background-color:red} 2.用图像做背景用background-image属性,例如b ...
- Java并发编程之ConcurrentHashMap
原文地址:http://www.iteye.com/topic/1103980 ConcurrentHashMap 是一个线程安全的Hash Table,它的主要功能是提供了一组和HashTable功 ...
- ios文件读取(二)
- (void)viewDidLoad { [super viewDidLoad]; /** * @brief 获取文件路径 * */ NSString * filePath = [self get ...
- Stata和Matlab联合处理金融数据
Stata是统计学专业软件,可以很方便的对数据处理,但几乎只能按照整行整列进行,而且每次只能加载一个矩阵(dta文件),如果要用到多个矩阵数据进行操作或进行复杂的循环控制,就力不从心了. 而Matla ...
- Remove掉Request.QueryString
好久上博客来了,最近有点忙,有点懒. 今天在解决一个Request.QueryString 传值的问题上遇到了,当不是第一次加载时需要把Request.QueryString的值赋值为null,刚开始 ...
- 转载: js jquery 获取当前页面的url,获取frameset中指定的页面的url(有修改)
转载网址:http://blog.csdn.net/bestlxm/article/details/6800077 js jquery 怎么获取当前页面的url,获取frameset中指定的页面的ur ...
- python3.4入门——核心数据类型
变量.对象和引用 变量的创建:一个变量(即变量名,如a),当代码第一次给它赋值就创建了它. 变量类型:变量没有任何和它关联的类型信息约束.类型的概念存在于对象中,而不是变量名中.变量原本是通用的,只是 ...