问题描述
我为 UIView 设置了动画,这样当用户触摸切换按钮时它会缩小,当用户再次触摸按钮时它会扩展回原始大小.到目前为止,一切正常.问题是动画需要一些时间 - 例如3 秒.在那段时间里,我仍然希望用户能够与界面进行交互.因此,当用户在动画仍在进行的情况下再次触摸按钮时,动画应该在原处停止并反转.
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.
在 Apple Q&As 我找到了一种立即暂停所有动画的方法:
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;
}];
}
}
推荐答案
除了下面的(我们从表现层抓取当前状态,停止动画,从保存的表现层重置当前状态,以及启动新动画),有一个更简单的解决方案.
In addition to the below (in which we grab the current state from the presentation layer, stop the animation, reset the current state from the saved presentation layer, and initiate the new animation), there is a much easier solution.
如果做基于块的动画,如果你想在 8.0 之前的 iOS 版本中停止一个动画并启动一个新的动画,你可以简单地使用 UIViewAnimationOptionBeginFromCurrentState
选项.(在 iOS 8 中有效,默认行为不仅从当前状态开始,而且以反映当前位置和当前速度的方式进行,因此在很大程度上完全不必担心这个问题. 有关详细信息,请参阅 WWDC 2014 视频构建可中断和响应式交互.)
If doing block-based animations, if you want to stop an animation and launch a new animation in iOS versions prior to 8.0, you can simply use the UIViewAnimationOptionBeginFromCurrentState
option. (Effective in iOS 8, the default behavior is to not only start from the current state, but to do so in a manner that reflects both the current location as well as the current velocity, rendering it largely unnecessary to worry about this issue at all. See WWDC 2014 video Building Interruptible and Responsive Interactions for more information.)
[UIView animateWithDuration:3.0
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
animations:^{
// specify the new `frame`, `transform`, etc. here
}
completion:NULL];
您可以通过停止当前动画并从当前动画停止的位置开始新动画来实现这一点.您可以使用 Quartz 2D 做到这一点:
将 QuartzCore.framework 添加到您的项目如果你还没有.(在当代版本的 Xcode 中,通常没有必要显式执行此操作,因为它会自动链接到项目.)
Add QuartzCore.framework to your project if you haven't already. (In contemporary versions of Xcode, it is often unnecessary to explicitly do this as it is automatically linked to the project.)
如果您还没有导入必要的标头(同样,在当代版本的 Xcode 中不需要):
Import the necessary header if you haven't already (again, not needed in contemporary versions of Xcode):
#import <QuartzCore/QuartzCore.h>
让您的代码停止现有动画:
Have your code stop the existing animation:
[self.subview.layer removeAllAnimations];
获取对当前表示层的引用(即当前视图的状态):
Get a reference to the current presentation layer (i.e. the state of the view as it is precisely at this moment):
CALayer *currentLayer = self.subview.layer.presentationLayer;
根据 presentationLayer
中的当前值重置 transform
(或 frame
或其他):
Reset the transform
(or frame
or whatever) according to the current value in the presentationLayer
:
self.subview.layer.transform = currentLayer.transform;
现在从 transform
(或 frame
或其他)动画到新值:
Now animate from that transform
(or frame
or whatever) to the new value:
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
将所有这些放在一起,这是一个例程,可将我的变换比例从 2.0x 切换到识别和返回:
Putting that all together, here is a routine that toggles my transform scale from 2.0x to identify and back:
- (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 并返回:
Or if you wanted to toggle frame
sizes from 100x100 to 200x200 and back:
- (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秒).因此,这可能看起来像:
By the way, while it generally doesn't really matter for really quick animations, for slow animations like yours, you might want to set the duration of the reversing animation to be the same as how far you've progressed in your current animation (e.g., if you're 0.5 seconds into a 3.0 second animation, when you reverse, you probably don't want to take 3.0 seconds to reverse that small portion of the animation that you have done so far, but rather just 0.5 seconds). Thus, that might look like:
- (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];
}
这篇关于如何停止和反转 UIView 动画?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!