UIView 无限循环动画在每个重复循环后调用一个方法

发布于 2024-09-27 08:32:07 字数 1153 浏览 4 评论 0原文

我正束手无策,试图为这个范例提出一个设计模式。我在这个网站上没有太多运气,但在这个阶段我会尝试任何事情。

我正在尝试实现雷达类型的动画,因此我将视图旋转 360 度来表示绕圆旋转的半径。我已经在这个圆周围放置了点,并且能够使用标准三角计算与圆心的角度。

当半径绕圆扫过时,如果它与某个点相交(例如,该点的角度等于扫过半径的角度+公差),则该点会闪烁。

现在,我已经通过多种方式接近这个范​​例,但还没有实现理想的解决方案。这是我尝试过的。

第一:iOS4 动画块。我将半径每次旋转 10 度,持续时间为 0.1 秒,并在完成方法中检查与点的交点。这里的问题是,如果您将选项设置为重复,则不会调用完成方法。唯一一次调用完成方法是在整个动画终止时,而不是在每个重复周期之后,因此此解决方案不起作用。

第二:尝试使用 CABasicAnimation 进行显式动画。与上面的方法相同,每 0.1 秒旋转 10 度,并将委托设置为 self 并实现animationDidFinish 方法。在animationDidFinish方法中,我检查了与点的交集。我将动画设置为累积并将重复次数设置为巨大值。半径旋转,但再次,动画DidFinish不会被调用,除非我将repeatCount设置为一个小数字,然后它只在repeatCount结束时被调用,而不是在每个重复周期之后被调用。

第三:使用 NSTimer,这种方法确实有效,但动画可能会不稳定,具体取决于屏幕周围发生的情况。我使用 0.1 秒的计时器刻度并启动 10 度的单个动画以及每次刻度时的相交检查。这种方法的问题在于,由于处理器在后台执行其他动画,因此在启动每个动画时很容易被停顿。请注意,我使用其他两种方法时没有遇到此问题!

第四:我尝试结合使用两者。半径上的 CABasicAnimation 和点上的 NSTimer。这里的问题是,如果 iDevice 进入睡眠状态然后恢复,它们可能会不同步并且很容易发生。

第五:使用iOS3.0风格的动画块,旋转10度,持续时间0.1秒。使用animationDidStop方法设置委托和animationDidStopSelector,再次调用动画方法并检查与点的交集。这也有效,但很像第三种解决方案,当滚动和其他动画发生时它会很不稳定。这很可能是由动画的停止启动性质引起的。

基本上,有没有一种方法可以无限地动画视图,但让它在每个重复周期后调用一个方法?或者有没有办法让第三种解决方案工作得更顺利?

请帮忙,我已经用完了设计模式。

I am at my wits end trying to come up with a design pattern for this paradigm. I haven't had much luck on this site but at this stage I'll try anything.

I am trying to implement a radar type animation and hence I am rotating a view 360 degrees to represent the radius rotating around the circle. I have placed points around this circle and am able to calculate the angle from the center of the circle using standard trig.

As the radius sweeps around the circle, if it intersects with a point (eg angle of the point equals the angle of the sweeping radius + a tolerance) the point flashes on.

Now, I have approached this paradigm in a number of ways but have not achieved an ideal solution. Here is what I have tried.

FIRST: iOS4 animation blocks. I would rotated the radius 10 degrees at a time with a duration of .1sec and in the completion method, check for intersections with points. The problem here is that if you set the option to repeat, then the completion method doesn't get called. The only time the completion method gets called is when the entire animation terminates, not after every repeat cycle, so this solution doesn't work.

SECOND: Tried explicit animation using CABasicAnimation. Same approach as above, rotating 10 degrees at every .1 seconds and setting the delegate to self and implementing the animationDidFinish method. In the animationDidFinish method, I checked for intersections with point. I set the animation to cumulative and repeatCound to Huge Value. The raduis rotates but once again, the animationDidFinish doesn't get called unless I set the repeatCount to a small number and then it only gets called at the end of the repeatCount, not after every repeat cycle.

THIRD: Using NSTimer and this approach actually works but the animation can be jerky depending on what is going on around the screen. I use a timer tick of .1 sec and initiate a single animation of 10 degrees as well as a intersection check at every tick. The problem with this approach is that it is susceptible to being stalled when starting each animation due to the processor doing other animations in the background. Note that i didn't have this problem with the other two approaches!

FOURTH: I tried using a combination of the two. CABasicAnimation on the radius and NSTimer on the Points. This issue here is that they can get out of sync and happens quite easily if the iDevice goes to sleep and then resumes.

FIFTH: Using the iOS3.0 style animation block and rotating 10 degrees with a duration of .1sec. Setting the delegate and the animationDidStopSelector with the animationDidStop method calling the animation method again as well as checking of intersection with points. This works too but much like the third solution, it is jerky when scrolling and other animations are happening. This is most likely caused by the stop start nature of the animation.

Basically, is there a way to animation a view infinitely but make it call a method after every repeat cycle? Or is there a way to make the third solution work more smoothly?

PLEASE HELP, I HAVE RUN OUT OF DESIGN PATTERNS.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

第七度阳光i 2024-10-04 08:32:07

让它完美运行。

这是我所做的:

1/ 将 CABasicAnimation 旋转对象应用到图层,每 2 秒旋转 360 度。 (不需要以这种方式累积)

2/ 设置一个计时器,每 0.1 秒从层调用表示层。

3/ 根据表示层中存储的 3D 变换计算角度。 点击此处获取我关于如何执行此操作的答案。

4/ 检查相交角度。

完成:-)

需要注意的事项:
3D 变换基于 180 到 -180 度之间的角度,而不是 0 到 360 度
使用atan2 而不是atan,因为后者只会给出第一象限中的角度。

Got it working perfectly.

Here is what I did:

1/ Applied a CABasicAnimation rotation object to the layer to rotate 360 degrees every 2 sec. (don't need to make it cumulative that way)

2/ Set up a timer to call the presentation layer from the layer every .1 sec.

3/ Calculate the angle from the 3D transform stored in the presentation layer. Click here for my answer on how to do this.

4/ Check for intersecting angles.

Done :-)

Things to watch out for:
The 3D transform is based on angles between 180 and -180 degrees and not 0 to 360 degrees
Use atan2 and not atan as the later will only give angles in the first quadrant.

难得心□动 2024-10-04 08:32:07

好吧,自从我发布这个问题以来,我已经走了很长一段路。

公认的设计模式是实际生成一个时钟滴答声(通常使用 CADisplayLink),该时钟滴答声以每秒 30 帧或 60 帧的速度运行,具体取决于您希望动画的流畅度和速度。在每个帧回调中,您修改视图/图层上的变换并使用事务来显示它。

一些要点:
构建一个方法来测试相交的线和点:

-(BOOL)intersectWithAngle:(CGFloat)rad {        
    return (rad <= angle && rad >= angle - angleIncrement);
}

在尝试修改可设置动画的属性时,必须使用事务,以防止图层/视图对新值本身进行动画处理。 (您不想将其设置两次动画)

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

self.transform = rotationTransform;

[CATransaction commit];

无需在每一帧中不断创建新的变换,这只是浪费并消耗资源。最好创建一个名为rotationTransform的实例变量/属性,并修改每个帧并将其重新应用到图层/视图。

-(void)displayFrame {   
    rotationTransform.a = cos(angle);
    rotationTransform.b = sin(angle);
    rotationTransform.c = -sin(angle);
    rotationTransform.d = cos(angle);

    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

    self.transform = rotationTransform;

    [CATransaction commit];
}

接下来,创建一个方法来实际设置角度,使用实例变量/属性来存储角度并定义角度增量(我使用 3 度),还预定义 2-pi,这样您就不会总是重新计算它。

-(void)processFrame {
    angle += angleIncrement;
    if (angle >= M_2PI) {
        angle -= M_2PI;
    }

    if ([self intersectWithAngle:point.angle]) {
        [point animate];
    } 
}

最后,构建由 CADisplayLink 调用的方法,该方法设置帧并显示它,同时保持帧同步。

-(void)displayLinkDidTick {
    // get the time now
    NSTimeInterval timeNow = [NSDate timeIntervalSinceReferenceDate];

    if (timeOfLastDraw == 0) {
        numberOfTicks = 1;
    } else {
        NSTimeInterval timeSinceLastDraw = timeNow - timeOfLastDraw;
        NSTimeInterval desiredTimeInterval = kFrameDuration;

        numberOfTicks = (NSUInteger)(timeSinceLastDraw / desiredTimeInterval);      
    }

    if (numberOfTicks > 4) {
        numberOfTicks = 4;
        timeOfLastDraw = timeNow;
    } else {
        timeOfLastDraw += numberOfTicks * kFrameDuration;
    }


    while(numberOfTicks--) {

        [self processFrame];
        if (spriteState == SpriteStateIdle)
            break;
        }

    [self displayFrame];

}

此代码摘录已针对本文进行了大量修改,实际上,我在同一 CADisplayLink 实例中制作了扫描线的动画和点的闪烁。

OK, I have come a long way since I posted this question.

The accepted design pattern is to actually generate a clock tick (commonly using CADisplayLink) that runs at either 30 frames per second or 60 depending on how smooth and fast you want the animation to be. At every frame callback, you modify the transform on the view/layer and use a transaction to display it.

Some key notes:
Build a method to test for intersecting line and point:

-(BOOL)intersectWithAngle:(CGFloat)rad {        
    return (rad <= angle && rad >= angle - angleIncrement);
}

You must use transactions when attempting to modify a property that is animatable to prevent the layer/view from animating the new value itself. (You don't want to animate it twice)

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

self.transform = rotationTransform;

[CATransaction commit];

No need to keep creating new transforms every frame, this is just wasteful and consumes resources. Best create an instance variable / property called rotationTransform and modify that every frame and re-apply it to the layer/view.

-(void)displayFrame {   
    rotationTransform.a = cos(angle);
    rotationTransform.b = sin(angle);
    rotationTransform.c = -sin(angle);
    rotationTransform.d = cos(angle);

    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

    self.transform = rotationTransform;

    [CATransaction commit];
}

Next, create a method to actually set up the angle, use an instance variable / property to store the angle and define your angle incrementation (I use 3 degrees), also predefine 2-pi so that you are not always recalculating it.

-(void)processFrame {
    angle += angleIncrement;
    if (angle >= M_2PI) {
        angle -= M_2PI;
    }

    if ([self intersectWithAngle:point.angle]) {
        [point animate];
    } 
}

Finally, build your method that is called by CADisplayLink which sets up the frame and displays it while maintaining frame sync.

-(void)displayLinkDidTick {
    // get the time now
    NSTimeInterval timeNow = [NSDate timeIntervalSinceReferenceDate];

    if (timeOfLastDraw == 0) {
        numberOfTicks = 1;
    } else {
        NSTimeInterval timeSinceLastDraw = timeNow - timeOfLastDraw;
        NSTimeInterval desiredTimeInterval = kFrameDuration;

        numberOfTicks = (NSUInteger)(timeSinceLastDraw / desiredTimeInterval);      
    }

    if (numberOfTicks > 4) {
        numberOfTicks = 4;
        timeOfLastDraw = timeNow;
    } else {
        timeOfLastDraw += numberOfTicks * kFrameDuration;
    }


    while(numberOfTicks--) {

        [self processFrame];
        if (spriteState == SpriteStateIdle)
            break;
        }

    [self displayFrame];

}

This code extract has been heavily modified for this post, in actually fact I do the animation of the scan line and the blipping of the point in the same CADisplayLink instance.

小糖芽 2024-10-04 08:32:07

我想知道这是否可行:

UIView* yourView = ...;
// preparing the callbacks:
void(^)(void) animations = ...; // the animation would rotate 10 degrees
void(^)(BOOL) completion = ^(BOOL finished) {
  // do your radar stuff
  [UIView animateWithDuration:0.1 animations:animations completion:completion];
}

// the first call to start it all
[UIView animateWithDuration:0.1 animations:animations completion:completion];

这个想法是每次完成雷达活动后再次调用动画序列,假设雷达检查不是繁重的任务,它应该很顺利。

I wonder if this would work:

UIView* yourView = ...;
// preparing the callbacks:
void(^)(void) animations = ...; // the animation would rotate 10 degrees
void(^)(BOOL) completion = ^(BOOL finished) {
  // do your radar stuff
  [UIView animateWithDuration:0.1 animations:animations completion:completion];
}

// the first call to start it all
[UIView animateWithDuration:0.1 animations:animations completion:completion];

The idea is to call the animation sequence again each time after finishing your radar activity, assuming the radar check is not a heavy one it should be smooth.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文