使用 UIBezierPath 动画饼图进度
Animating Pie Progress with UIBezierPath
问题:
如果你看一下我当前的代码,你会发现如果 targetSeconds
高于 ~2-3 秒,它工作正常。
但是,如果targetSeconds
是0.005秒,它就不会工作,因为它不可能在0.005秒内完成100个方法调用。因此,有人对我可以做些什么来改进它有什么建议吗?我宁愿不包含第三方 GitHub 存储库。
当前代码:
// Target seconds means the seconds that it'll take to become 100.0f.
- (void)startAnimatingWithTargetSeconds:(NSTimeInterval)targetSeconds{
// Try to set timeInterval to 0.005f and you'll see that it won't finish in 0.005f
[NSTimer scheduledTimerWithTimeInterval:targetSeconds / 100.0f target:self selector:@selector(animateWithTimer:) userInfo:nil repeats:YES];
}
- (void)animateWithTimer:(NSTimer *)timer {
BOOL isFinished = self.currentProgress >= 100;
if (isFinished) {
// Invalidate timer
if (timer.isValid) {
[timer invalidate];
}
// Reset currentProgress
self.currentProgress = 0.0f;
}else{
if (timer.isValid) {
self.currentProgress += 1;
}
}
}
// Overriden setter
- (void)setCurrentProgress:(CGFloat)currentProgress {
if (_currentProgress == currentProgress) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
_currentProgress = currentProgress;
[self setNeedsDisplay];
});
}
然后在 drawRect
中,我有一个 UIBezierPath
基本上根据 self.currentProgress
画圆。
像这样:CGFloat endAngle = (self.currentProgress / 100.0f) * 2 * M_PI + startAngle;
问题:
是否有任何公式或任何东西可以帮助我解决我的问题?因为如果我将 self.currentProgress
设置为 self.currentProgress += 5;
而不是 1
,它的动画会快得多,这正是我正在寻找的。
我更喜欢使用这样的东西,因为计时器(和 usleep)在小间隔内工作非常不准确:
-(void)startAnimatingWithTargetSeconds:(NSTimeInterval)targetSeconds
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
float fps = 60;
float currentState = 0;
float frameStateChange = fps/targetSeconds;
NSDate *nextFrameDate = [NSDate date];
while (currentState < 1) {
currentState += frameStateChange;
self.currentProgress = roundf(currentState * 100.);
nextFrameDate = [nextFrameDate dateByAddingTimeInterval:1./fps];
while ([nextFrameDate timeIntervalSinceNow] > 0) {
usleep((useconds_t)(100000/fps));
}
}
self.currentProgress = 0;
});
}
首先,你希望什么时候每 0.005 秒重绘一次?那是 200 FPS,远远超出您的需要。
不要重新发明轮子——利用 Core Animation! Core Animation 已经知道如何以适当的速率调用您的状态更改函数,以及如何在必要时重绘视图,假设您告诉它该做什么。该策略的要点如下:
- 向您的图层添加一个动态 属性,表示您的饼图切片的完整性。
- 告诉 Core Animation 这个 属性 可以通过覆盖
actionForKey:
, or setting the animation into the actions
dictionary (or even more options, detailed here) 来设置动画。
- 告诉 Core Animation 对此 属性 的更改需要使用
needsDisplayForKey:
. 重绘图层
- 实施
display
方法,根据动态 属性 的 表示层的 值重绘饼图。
完成!现在,您可以将动态 属性 从任何值设置为任何其他值,就像不透明度、位置等一样。Core Animation 负责计时和回调,您将获得 60 FPS 的平滑流畅。
有关示例,请参阅以下资源,按有用性递减的顺序列出(在我看来):
- Animating Pie Slices using a custom CALayer – 这基本上就是你想要做的
- Animating Custom Layer Properties – 写得更好但不太适用
- Apple's Core Animation Guide – 深奥,但如果你想掌握 Core Animation 这只奇怪的野兽,值得一读
祝你好运!
问题:
如果你看一下我当前的代码,你会发现如果 targetSeconds
高于 ~2-3 秒,它工作正常。
但是,如果targetSeconds
是0.005秒,它就不会工作,因为它不可能在0.005秒内完成100个方法调用。因此,有人对我可以做些什么来改进它有什么建议吗?我宁愿不包含第三方 GitHub 存储库。
当前代码:
// Target seconds means the seconds that it'll take to become 100.0f.
- (void)startAnimatingWithTargetSeconds:(NSTimeInterval)targetSeconds{
// Try to set timeInterval to 0.005f and you'll see that it won't finish in 0.005f
[NSTimer scheduledTimerWithTimeInterval:targetSeconds / 100.0f target:self selector:@selector(animateWithTimer:) userInfo:nil repeats:YES];
}
- (void)animateWithTimer:(NSTimer *)timer {
BOOL isFinished = self.currentProgress >= 100;
if (isFinished) {
// Invalidate timer
if (timer.isValid) {
[timer invalidate];
}
// Reset currentProgress
self.currentProgress = 0.0f;
}else{
if (timer.isValid) {
self.currentProgress += 1;
}
}
}
// Overriden setter
- (void)setCurrentProgress:(CGFloat)currentProgress {
if (_currentProgress == currentProgress) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
_currentProgress = currentProgress;
[self setNeedsDisplay];
});
}
然后在 drawRect
中,我有一个 UIBezierPath
基本上根据 self.currentProgress
画圆。
像这样:CGFloat endAngle = (self.currentProgress / 100.0f) * 2 * M_PI + startAngle;
问题:
是否有任何公式或任何东西可以帮助我解决我的问题?因为如果我将 self.currentProgress
设置为 self.currentProgress += 5;
而不是 1
,它的动画会快得多,这正是我正在寻找的。
我更喜欢使用这样的东西,因为计时器(和 usleep)在小间隔内工作非常不准确:
-(void)startAnimatingWithTargetSeconds:(NSTimeInterval)targetSeconds
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
float fps = 60;
float currentState = 0;
float frameStateChange = fps/targetSeconds;
NSDate *nextFrameDate = [NSDate date];
while (currentState < 1) {
currentState += frameStateChange;
self.currentProgress = roundf(currentState * 100.);
nextFrameDate = [nextFrameDate dateByAddingTimeInterval:1./fps];
while ([nextFrameDate timeIntervalSinceNow] > 0) {
usleep((useconds_t)(100000/fps));
}
}
self.currentProgress = 0;
});
}
首先,你希望什么时候每 0.005 秒重绘一次?那是 200 FPS,远远超出您的需要。
不要重新发明轮子——利用 Core Animation! Core Animation 已经知道如何以适当的速率调用您的状态更改函数,以及如何在必要时重绘视图,假设您告诉它该做什么。该策略的要点如下:
- 向您的图层添加一个动态 属性,表示您的饼图切片的完整性。
- 告诉 Core Animation 这个 属性 可以通过覆盖
actionForKey:
, or setting the animation into theactions
dictionary (or even more options, detailed here) 来设置动画。 - 告诉 Core Animation 对此 属性 的更改需要使用
needsDisplayForKey:
. 重绘图层
- 实施
display
方法,根据动态 属性 的 表示层的 值重绘饼图。
完成!现在,您可以将动态 属性 从任何值设置为任何其他值,就像不透明度、位置等一样。Core Animation 负责计时和回调,您将获得 60 FPS 的平滑流畅。
有关示例,请参阅以下资源,按有用性递减的顺序列出(在我看来):
- Animating Pie Slices using a custom CALayer – 这基本上就是你想要做的
- Animating Custom Layer Properties – 写得更好但不太适用
- Apple's Core Animation Guide – 深奥,但如果你想掌握 Core Animation 这只奇怪的野兽,值得一读
祝你好运!