连续调用animateWithDuration导致动画失效问题咨询
问题分析与解决方案
核心原因
搞懂这个问题得先记住UIView动画的一个关键特性:在动画块里设置视图属性时,视图的「模型值」(就是直接view.alpha拿到的值)会立刻变成目标值,根本不等动画跑完。如果后续又提交一个目标值和当前模型值完全一样的动画,UIKit会直接判定这个动画没必要执行,当场触发completion块,而且finished参数会是YES(因为不是被打断,是直接“完成”了无意义的动画)。
另外你用的UIViewAnimationOptionBeginFromCurrentState选项,本来是让新动画从用户看到的实时状态开始,但碰到目标值和模型值一致的情况,它不仅不会帮你执行动画,还会把之前正在跑的动画直接取消掉——这就是问题的导火索。
点击both按钮时的完整执行流程
我给你拆解一下当时到底发生了什么,你就明白为啥视图啥都没显示:
- 初始状态:
bkgView的alpha不管是模型值还是用户看到的呈现值,都是0。 - 执行
show方法:- 动画块里设
alpha = 1,模型值瞬间变成1,UIKit计划了一个0.3秒的动画,让呈现层从0慢慢变到1。
- 动画块里设
- 紧接着执行
flash方法:- 动画块里又设
alpha = 1,但此时模型值已经是1了,等于没改任何属性。 - UIKit直接取消了
show方法里的动画(因为BeginFromCurrentState选项),呈现层立刻跳到模型值1——但这个变化快到用户根本看不见。 - 因为动画没实际效果,
completion块马上就执行了,finished是YES。
- 动画块里又设
- 执行
completion里的淡出动画:- 动画块里设
alpha = 0,模型值又瞬间变回0,UIKit计划了一个延迟3秒、时长0.5秒的动画,让呈现层从1变到0。 - 但此时之前的动画都被取消了,呈现层会立刻跟着模型值变回0,所以用户全程看到的都是透明的视图,完全没机会看到它显示出来!
- 动画块里设
解决方案
知道了原因,解决就简单了,核心就是避免模型值被反复横跳,同时确保动画只在需要的时候执行:
方案1:优化flash方法,先检查当前状态
修改flash方法,先看看视图是不是已经显示了,没必要重复做淡入动画,直接启动淡出就行:
-(void)flash { if (self->bkgView.alpha != 1.0) { [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){ self->bkgView.alpha = 1.0; } completion:^(BOOL finished){ if (finished) { [self startFadeOutAnimation]; } }]; } else { // 已经是显示状态,直接启动淡出 [self startFadeOutAnimation]; } } // 把淡出动画抽成单独方法,方便复用 -(void)startFadeOutAnimation { [UIView animateWithDuration:0.5 delay:3 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){ self->bkgView.alpha = 0; } completion:^(BOOL finished){}]; }
方案2:用UIViewPropertyAnimator(iOS 10+)
如果你是iOS 10及以上版本,用UIViewPropertyAnimator会更灵活,它支持手动控制动画的启停,能避免UIKit的自动优化坑:
@property (nonatomic, strong) UIViewPropertyAnimator *currentAnimator; -(void)show { // 先取消当前正在跑的动画 [self.currentAnimator stopAnimation:YES]; // 创建淡入动画并启动 self.currentAnimator = [[UIViewPropertyAnimator alloc] initWithDuration:0.3 curve:UIViewAnimationCurveLinear animations:^{ self->bkgView.alpha = 1.0; }]; [self.currentAnimator startAnimation]; } -(void)flash { // 取消当前动画 [self.currentAnimator stopAnimation:YES]; // 创建淡入动画,完成后启动淡出 self.currentAnimator = [[UIViewPropertyAnimator alloc] initWithDuration:0.3 curve:UIViewAnimationCurveLinear animations:^{ self->bkgView.alpha = 1.0; }]; [self.currentAnimator addCompletion:^(UIViewAnimatingPosition finalPosition) { if (finalPosition == UIViewAnimatingPositionEnd) { // 延迟3秒启动淡出动画 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self.currentAnimator = [[UIViewPropertyAnimator alloc] initWithDuration:0.5 curve:UIViewAnimationCurveLinear animations:^{ self->bkgView.alpha = 0; }]; [self.currentAnimator startAnimation]; }); } }]; [self.currentAnimator startAnimation]; }
总结
说白了就是UIView动画的模型值即时更新特性,加上连续调用时的动画取消,导致模型值在瞬间反复跳变,呈现层根本没机会展示中间状态。通过提前检查属性状态,或者用更灵活的动画控制器,就能完美解决这个问题。
内容的提问来源于stack exchange,提问作者Ecuador
相关产品推荐
相关产品推荐

