时钟动画
Posted 西贝了爷
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了时钟动画相关的知识,希望对你有一定的参考价值。
序言
笔者对动画是很钟情的,今天我们一起来学习学习如何通过Core Animation实现钟的秒针、分针和时针无限动画移动,与苹果手机上的世界闹钟中的秒针、分针和时针类似。通过观察,笔者感觉是动画来实现的,而不是定时针。
不过,这里提供了两种方式来实现:
- 通过定时器实现刷新,与挂钟一样,移动没有动画效果
- 通过Core Animation实现,与苹果的世界时钟一样,动画均匀地移动
效果图
关于时针、分针和秒针
这里我们为了更轻量一些,直接继承于UIView,而不是UIImageView。将图片直接给layer.contents就可以了,也就没有那么重了。
对于时针、分针和秒针,我们也是直接通过添加layer来实现的。下面的方法是用于生成这三种针的layer的,其中最关键的是设置锚点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
- (CALayer *)layerWithBackgroundColor:(UIColor *)color size:(CGSize)size {
CALayer *layer = [CALayer layer];
layer.backgroundColor = color.CGColor;
layer.anchorPoint = CGPointMake(0.5, 1);
// 设置为中心
layer.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
// 时针、分针、秒针长度是不一样的
layer.bounds = CGRectMake(0, 0, size.width, size.height);
// 加个小圆角
layer.cornerRadius = 4;
[self.layer addSublayer:layer];
return layer;
}
|
首先,position属性是决定子layer在父layer上的位置,默认为(0,0)。其次,anchorPoint属性是决定子layer上的哪个点会在position所指定的位置。好像很抽象啊,确实也很难理解。当年自觉coco2dx的时候,也是学习了好久才弄明白锚点。
现在,对于我们这里,我们要让position所指定的位置,也就是在时钟的正中间,那么锚点需要设置为(0.5,1)。这样所计算出来的子layer的初始位置(0.5 * layer.bounds.size.width, 1 * layer.bounds.size.height),这也就是我们所设置的正中间了。
如果这段话看不懂,请先阅读上面所推荐的文章,因此这个锚点确实不好理解。但是,要想做好动画,必须要先掌握好锚点与position的关系。
定时器实现
为了更好地说明,直接先上核心代码。下面先看看如何创建时针、分针、秒针:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
- (instancetype)initWithFrame:(CGRect)frame imageName:(NSString *)imageName {
if (self = [super initWithFrame:frame]) {
UIImage *image = [UIImage imageNamed:imageName];
self.layer.contents = (__bridge id _Nullable)(image.CGImage);
// hour layer set up
self.hourLayer = [self layerWithBackgroundColor:[UIColor blackColor]
size:CGSizeMake(3, self.frame.size.width / 2 - 40)];
// 秒针与分针一样长
self.minuteLayer = [self layerWithBackgroundColor:[UIColor blackColor]
size:CGSizeMake(3, self.frame.size.width / 2 - 20)];
self.secondLayer = [self layerWithBackgroundColor:[UIColor redColor]
size:CGSizeMake(1, self.frame.size.width / 2 - 20)];
self.secondLayer.cornerRadius = 0;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(onTimerUpdate:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
_timer = timer;
[self updateUI];
}
return self;
}
|
这里创建了定时器,通过定时器定时地去刷新UI,所以没有动画的过程。下面是更新UI显示的方法,主要是计算这个角度的问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
- (void)updateUI {
NSCalendar *calender = [NSCalendar currentCalendar];
NSDateComponents *date = [calender components:NSCalendarUnitSecond
| NSCalendarUnitMinute
| NSCalendarUnitHour
fromDate:[NSDate date]];
NSInteger second = date.second;
NSInteger minute = date.minute;
NSInteger hour = date.hour;
CGFloat perHourMove = 1.0 / 12. * 360.0;
CGFloat hourAngle = hour * perHourMove + minute * (1.0 / 60.0) * perHourMove;
self.hourLayer.transform = CATransform3DMakeRotation(kAngleToRadion(hourAngle), 0, 0, 1);
// 一分钟就是一圈,也就是每秒走度
CGFloat minuteAngle = minute * 360.0 / 60.0;
self.minuteLayer.transform = CATransform3DMakeRotation(kAngleToRadion(minuteAngle), 0, 0, 1);
CGFloat secondAngle = second * 360.0 / 60.0;
self.secondLayer.transform = CATransform3DMakeRotation(kAngleToRadion(secondAngle), 0, 0, 1);
}
|
- 每秒钟时针转的度数:1.0 / 12.0 * 360.0,因为一圈有12个小时,共360度,所以一小时就是这么多度
- 每秒钟分针转的度数:一分钟转一圈,正在是360度,所以每秒分针转360/60度
- 每秒钟秒针转的度数:60秒转一圈,共360度,所以每秒转360/60度
最后,别忘了在需要的地方调用释放定时器:
1
2
3
|
[self.clockView releaseTimer];
|
Core Animation实现时钟
其它代码与上面的采用定时器是差不多的,只是将定时器的部分改成了Core Animation部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
// 添加秒针动画
- (void)addSecondAnimationWithAngle:(CGFloat)angle {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.repeatCount = HUGE_VALF;
animation.duration = 60;
animation.removedOnCompletion = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.fromValue = @(angle * M_PI / 180);
animation.byValue = @(2 * M_PI);
[self.secondLayer addAnimation:animation forKey:@"SecondAnimationKey"];
}
// 添加分针动画
- (void)addMinuteAnimationWithWithAngle:(CGFloat)angle {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.repeatCount = HUGE_VALF;
animation.duration = 60 * 60;
animation.removedOnCompletion = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.fromValue = @(angle * M_PI / 180);
animation.byValue = @(2 * M_PI);
[self.minuteLayer addAnimation:animation forKey:@"MinuteAnimationKey"];
}
// 添加时针动画
- (void)addHourAnimationWithAngle:(CGFloat)angle {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
animation.repeatCount = HUGE_VALF;
animation.duration = 60 * 60 * 60 * 12;
animation.removedOnCompletion = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.fromValue = @(angle * M_PI / 180);
animation.byValue = @(2 * M_PI);
[self.hourLayer addAnimation:animation forKey:@"HourAnimationKey"];
}
|
这里要注意,我们刚开始就可能不是0角度,因此fromValue要从angle开始。另外,我们要保证一圈一圈地转,因此要使用byValue而不是toValue。
如果不太了解Core Animation,推荐大家阅读笔者曾经在公司所进行的一次分享的内容:说说Core Animation,这里会有很多基本的概念及属性说明,也许能帮你快速了解动画。
- 对于秒针动画:由于我们设置要转一圈,所以需要duration为60秒。
- 对于分针动画:由于转一圈就是一小时,所以需要duration为60*60秒
- 对于时针动画:由于转一圈就是12小时,所以需要duration为60*60*60*12秒
以上是关于时钟动画的主要内容,如果未能解决你的问题,请参考以下文章