产品中需要使用到一个带旋转动画的倒计时按钮。但在开发过程中产生了两个新的需求。一、APP进入后台时倒计时暂停,返回前台继续倒计时。二、在倒计时未结束的时候,移除该页面,此Button自动dealloc掉。(如果不dealloc,等到动画完成后由系统去dealloc,会触发此动画结束的回调,执行回调中的方法,而此时该页面已经移除了,会引起各种问题。)
那么先摆一下我踩的坑,最后给上我自己的解决方案。(如果有直接想看最终方案的童鞋,可到文尾,有传送门。)
第一种思路,使用Animation解决。
这是最先想到的方法,使用贝塞尔曲线画一个圆,再用CABaseAnimation做倒计时动画。实现细节我这边就不贴代码了,网上有很多这样的代码。
说一下这样解决的一个问题。使用CABaseAnimation做动画,我们很自然的使用
- (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag {
//执行动画结束回调
}
来截获动画结束时的回调,再传递到外层给做各种结束处理。但这样处理就会引发我一开始说的第一个问题,在APP进入后台时,系统会自动remove掉所有的Animation,就会触发 animationDidStop 这个函数,而此时我们并不需要触发它。再而返回前台后,这个动画就结束了。
我尝试了一些解决方法,在回到前台是重新开始动画等。但都没有很好的解决这个问题。
第二种思路,重绘圆环。
不使用Animation,就只能自己定义NSTimer来实现该功能。核心代码如下
self.progressValue = 0;
timer = [NSTimer at_scheduledTimerWithTimeInterval:0.03 block:^{
if (floor(self.progressValue) > 1.0f) {
if (timer) {
[timer invalidate];
timer = nil;
}
return;
} else {
frontFillBezierPath = [UIBezierPath bezierPathWithArcCenter:center radius:(CGRectGetWidth(wSelf.bounds)-progressStrokeWidth)/2.f startAngle:-M_PI_2 endAngle:(2*M_PI)*self.progressValue-M_PI_2 clockwise:YES];
frontFillLayer.path = frontFillBezierPath.CGPath;
}
self.progressValue+=(0.03/2);
} repeats:YES];
该方案的核心就是每0.03秒重绘一次贝塞尔曲线的圆弧,由于人的视觉残留现象,形成一个连续的动画。虽然这样对圆环的暂停,重启更加方便。也解决了文章开始第一条说的问题,但由于每0.03秒需要重绘一次,比较消耗CPU。如果每0.1s执行一次,就无法形成连续的动画。一般每秒要24-25帧,人眼才能觉得不卡顿。
第三种思路,利用strokeEnd自带的动画属性。
CALayer有两个属性strokeStart和strokeEnd,分别表示起始位置和结束为止,默认支持动画,这两个属性的值在0~1之间。strokeStart=0.1f; strokeEnd=0.7f则显示如下图所示。
图片来自网络(侵删)通过改变strokeEnd属性的值来实现倒计时的动画效果,核心代码如下:
CGFloat timeInterval = 0.1f;
CGFloat percent = 1.0f/(duration/timeInterval);
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer at_scheduledTimerWithTimeInterval:timeInterval repeats:YES block:^(NSTimer * _Nonnull timer) {
if (weakSelf.frontFillLayer.strokeEnd >= 1) {
[weakSelf stop];
} else {
weakSelf.frontFillLayer.strokeEnd += percent;
if (block) {
block(weakSelf.frontFillLayer.strokeEnd * duration);
}
}
}];
有一点需要注意,strokeStart默认的起始位置为3点钟方向。如果想改变起始位置为12点钟方向,把strokeStart设为-0.25是没有效果的。(它的取值范围是0-1)只需要一开始画贝塞尔曲线时,将起始位置设为-M_PI_2。
self.frontFillBezierPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:-M_PI_2 endAngle:1.5*M_PI clockwise:YES];
另外在dealloc时,手动销毁定时器,就解决了文章开头说的第二个问题了。
最后给上Demo地址,传送门。
网友评论