16

I have animated a UIView so that it shrinks when the user touches a toggle button and it expands back to its original size when the user touches the button again. So far everything works just fine. The problem is that the animation takes some time - e.g. 3 seconds. During that time I still want the user to be able to interact with the interface. So when the user touches the button again while the animation is still in progress the animation is supposed to stop right where it is and reverse.

In the Apple Q&As I have found a way to pause all animations immediately:

https://developer.apple.com/library/ios/#qa/qa2009/qa1673.html

But I do not see a way to reverse the animation from here (and omit the rest of the initial animation). How do I accomplish this?

- (IBAction)toggleMeter:(id)sender {
    if (self.myView.hidden) {        
        self.myView.hidden = NO;
        [UIView animateWithDuration:3 animations:^{
            self.myView.transform = expandMatrix;
        } completion:nil];
    } else {
        [UIView animateWithDuration:3 animations:^{
            self.myView.transform = shrinkMatrix;
        } completion:^(BOOL finished) {
            self.myView.hidden = YES;
        }];
    }
}
4

3 回答 3

44

除了以下(我们从表示层获取当前状态,停止动画,从保存的表示层重置当前状态,并启动新动画)之外,还有一个更简单的解决方案。

如果制作基于块的动画,如果您想在 8.0 之前的 iOS 版本中停止动画并启动新动画,您可以简单地使用该UIViewAnimationOptionBeginFromCurrentState选项。(在 iOS 8 中生效,默认行为不仅是从当前状态开始,而且以反映当前位置和当前速度的方式这样做,因此在很大程度上完全不必担心这个问题. 请参阅 WWDC 2014 视频Building Interruptible and Responsive Interactions了解更多信息。)

[UIView animateWithDuration:3.0
                      delay:0.0
                    options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
                 animations:^{
                     // specify the new `frame`, `transform`, etc. here
                 }
                 completion:NULL];

您可以通过停止当前动画并从当前动画停止的位置开始新动画来实现这一点。你可以用Quartz 2D做到这一点:

  1. 如果您还没有,请将 QuartzCore.framework 添加到您的项目中。(在现代版本的 Xcode 中,通常不需要显式执行此操作,因为它会自动链接到项目。)

  2. 如果您还没有导入必要的头文件(同样,在当代版本的 Xcode 中不需要):

    #import <QuartzCore/QuartzCore.h>
    
  3. 让您的代码停止现有动画:

    [self.subview.layer removeAllAnimations];
    
  4. 获取对当前表示层的引用(即此时的视图状态):

    CALayer *currentLayer = self.subview.layer.presentationLayer;
    
  5. 根据 中的当前值重置transform(或或其他) :framepresentationLayer

    self.subview.layer.transform = currentLayer.transform;
    
  6. transform现在从那个(或其他)动画frame到新值:

    [UIView animateWithDuration:1.0
                          delay:0.0
                        options:UIViewAnimationOptionAllowUserInteraction
                     animations:^{
                         self.subview.layer.transform = newTransform;
                     }
                     completion:NULL];
    

综上所述,这是一个将我的变换比例从 2.0x 切换到识别和返回的例程:

- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
    CALayer *currentLayer = self.subview.layer.presentationLayer;

    [self.subview.layer removeAllAnimations];

    self.subview.layer.transform = currentLayer.transform;

    CATransform3D newTransform;

    self.large = !self.large;

    if (self.large)
        newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
    else
        newTransform = CATransform3DIdentity;

    [UIView animateWithDuration:1.0
                          delay:0.0
                        options:UIViewAnimationOptionAllowUserInteraction
                     animations:^{
                         self.subview.layer.transform = newTransform;
                     }
                     completion:NULL];
}

或者,如果您想将frame尺寸从 100x100 切换到 200x200 并返回:

- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
    CALayer *currentLayer = self.subview.layer.presentationLayer;

    [self.subview.layer removeAllAnimations];

    CGRect newFrame = currentLayer.frame;

    self.subview.frame = currentLayer.frame;

    self.large = !self.large;

    if (self.large)
        newFrame.size = CGSizeMake(200.0, 200.0);
    else
        newFrame.size = CGSizeMake(100.0, 100.0);

    [UIView animateWithDuration:1.0
                          delay:0.0
                        options:UIViewAnimationOptionAllowUserInteraction
                     animations:^{
                         self.subview.frame = newFrame;
                     }
                     completion:NULL];
}

顺便说一句,虽然对于真正快速的动画通常并不重要,但对于像您这样的慢速动画,您可能希望将反转动画的持续时间设置为与您在当前动画中的进展程度相同(例如,如果您在 0.5 秒内进入一个 3.0 秒的动画,那么当您反转时,您可能不想花 3.0 秒来反转您目前所做的动画的一小部分,而只需要 0.5 秒)。因此,这可能看起来像:

- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
    CFTimeInterval duration = kAnimationDuration;             // default the duration to some constant
    CFTimeInterval currentMediaTime = CACurrentMediaTime();   // get the current media time
    static CFTimeInterval lastAnimationStart = 0.0;           // media time of last animation (zero the first time)

    // if we previously animated, then calculate how far along in the previous animation we were
    // and we'll use that for the duration of the reversing animation; if larger than
    // kAnimationDuration that means the prior animation was done, so we'll just use
    // kAnimationDuration for the length of this animation

    if (lastAnimationStart)
        duration = MIN(kAnimationDuration, (currentMediaTime - lastAnimationStart));

    // save our media time for future reference (i.e. future invocations of this routine)

    lastAnimationStart = currentMediaTime;

    // if you want the animations to stay relative the same speed if reversing an ongoing
    // reversal, you can backdate the lastAnimationStart to what the lastAnimationStart
    // would have been if it was a full animation; if you don't do this, if you repeatedly
    // reverse a reversal that is still in progress, they'll incrementally speed up.

    if (duration < kAnimationDuration)
        lastAnimationStart -= (kAnimationDuration - duration);

    // grab the state of the layer as it is right now

    CALayer *currentLayer = self.subview.layer.presentationLayer;

    // cancel any animations in progress

    [self.subview.layer removeAllAnimations];

    // set the transform to be as it is now, possibly in the middle of an animation

    self.subview.layer.transform = currentLayer.transform;

    // toggle our flag as to whether we're looking at large view or not

    self.large = !self.large;

    // set the transform based upon the state of the `large` boolean

    CATransform3D newTransform;

    if (self.large)
        newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
    else
        newTransform = CATransform3DIdentity;

    // now animate to our new setting

    [UIView animateWithDuration:duration
                          delay:0.0
                        options:UIViewAnimationOptionAllowUserInteraction
                     animations:^{
                         self.subview.layer.transform = newTransform;
                     }
                     completion:NULL];
}
于 2013-04-30T16:16:24.490 回答
1

您可以使用一个常见的技巧来做到这一点,但有必要编写一个单独的方法来收缩(以及另一个类似的方法来扩展):

- (void) shrink {  
    [UIView animateWithDuration:0.3
                     animations:^{
                        self.myView.transform = shrinkALittleBitMatrix;
                     }
                     completion:^(BOOL finished){
                          if (continueShrinking && size>0) {
                              size=size-1;
                              [self shrink];      
                           }
                     }];
}

所以现在,诀窍是将收缩的 3 秒动画分解为 10 个动画(当然,也可以超过 10 个),每个 0.3 秒,在其中您收缩整个动画的 1/10 shrinkALittleBitMatrix:. 每个动画完成后,只有当 bool ivarcontinueShrinking为 true 且 int ivarsize为正数时才调用相同的方法(全尺寸视图的 size=10,最小尺寸的视图 size=0)。当您按下按钮时,您将 ivar 更改continueShrinking为 FALSE,然后调用expand. 这将在不到 0.3 秒内停止动画。

好吧,您必须填写详细信息,但我希望它会有所帮助。

于 2013-04-30T16:13:10.720 回答
0

第一:如何删除或取消带有视图的动画?

[view.layer removeAllAnimations] 

如果视图有很多动画,比如一个动画是从上到下移动,另一个是从左到右移动;

您可以取消或删除这样的特殊动画:

[view.layer removeAnimationForKey:@"someKey"];
// the key is you assign when you create a animation 
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"someKey"]; 

当你这样做时,动画将停止,它将调用它的委托:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag

如果 flag == 1,表示动画完成。如果flag == 0,表示动画没有完成,可能会被取消、移除。

第二:所以,你可以在这个委托方法中做你想做的事。

如果你想在删除代码执行时获取视图的框架,你可以这样做:

currentFrame = view.layer.presentationlayer.frame;

笔记:

当您获取当前帧并删除动画时,视图也会动画一段时间,因此currentFrame 不是设备屏幕中的最后一帧。

我现在无法解决这个问题。如果有一天我可以,我会更新这个问题。

于 2016-12-27T11:51:01.830 回答