如何实现可交互的 UIView 隐式动画

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何实现可交互的 UIView 隐式动画相关的知识,希望对你有一定的参考价值。

参考技术A   这一篇教程将结合一个小 demo 讲解, 最终代码在 github 上可以下载到. demo 的运行效果如图所示, 通过手势拖动顶端红色矩形到虚线位置, 拖动距离超过 50% 松手, 矩形会自动移动到目标位置, 少于 50% 松手, 矩形自动归位:

  实现一个可交互的动画大概有这么几种思路:

  在响应 GestureRecognizer 的方法中实时更新 view 的状态, 在 GestureRecognizer 结束的时候创建动画完成后续的部分.

  在 GestureRecognizer 开始的时候创建显式动画, 随着 Gesture 变化, 改变动画的 progress, 在 GestureRecognizer 结束之后完成动画.

  在 GestureRecognizer 开始的时候创建隐式动画, 随着 Gesture 变化, 改变动画的 progress, 在 GestureRecognizer 结束之后完成动画.

  注意 2 和 3 只有显式和隐式的区别.

  对于简单的 view, 单一的动画 (比如 demo 中的这种, 线性移动一个简单的 view), 三种方法差别不大. 而对于复杂的 view (view 中嵌套多个 subView), 多个动画 (view 和 subview 都有各自的动画), 如果在不考虑可交互的前提下, 隐式的动画已经可以满足需求, 那么方法 3 可能会比方法 1 和 2 简洁得多, 本文将对方法 3 进行讲解.

  要实现方法 3, 首先要了解 CALayer 的几个属性: speed, timeOffset, beginTime. 对于这几个属性, Controlling Animation Timing 这篇教程图文并茂地给出了直观的解释, 必读, 这里只做简单介绍:

  speed: 控制动画的速度, 比如设置为 1 (默认), 动画正常运行, 设置为 0 动画暂停, 设置为 -1 动画反向运行.

  timeOffset: 控制动画在整个动画时间轴上的位置, 比如一个 3 秒的动画, timeOffset 为 1.5, layer 就会处于整个动画过程中间的那个状态.

  beginTime: 动画的开始时间.

  有了这些基础, 我们就可以着手实现了, 首先我们需要建立一个 CALayer 的 Category, 添加一些动画辅助代码:

  - (void)gs_pauseAnimation
  self.speed = 0;
  self.timeOffset = 0;
  
  
  - (void)gs_recoverToDefaultState
  self.speed = 1;
  self.timeOffset = 0;
  self.beginTime = 0;
  
  
  - (void)gs_setTimeProgress:(CFTimeInterval)timeProgress
  self.timeOffset = timeProgress;
  
  
  - (void)gs_continueAnimationWithTimeProgress:(CFTimeInterval)timeProgress
  [self gs_recoverToDefaultState];
  
  self.beginTime = [self convertTime:CACurrentMediaTime() fromLayer:nil] - timeProgress;
  
  
  - (void)gs_continueReverseAnimationWithTimeProgress:(CFTimeInterval)timeProgress animationDuration:(CFTimeInterval)duration
  [self gs_recoverToDefaultState];
  
  self.beginTime = [self convertTime:CACurrentMediaTime() fromLayer:nil] - (duration - timeProgress);
  self.timeOffset = duration;
  self.speed = -1;
  
  gs_pauseAnimation: 通过将 speed 设置为 0, 来暂停动画.

  gs_recoverToDefaultState: 将 layer 的各个属性恢复到默认状态

  gs_setTimeProgress: 在 gs_pauseAnimation 的基础上, 设置动画的时间进度.

  gs_continueAnimationWithTimeProgress: 从 timeProgress 时间进度开始, 继续播放之前暂停了的动画

  这里首先调用了 gs_recoverToDefaultState, 是为了让 convertTime:fromLayer: 得出正确的时间.

  [self convertTime:CACurrentMediaTime() fromLayer:nil] 将当前系统时间转换到 layer 的时间上, 减去 timeProgress, 假设这是一个 3 秒的动画, timeProgress 为 1 秒, 则这条代码就表示 layer 的动画在一秒之前就已经开始, 所以动画将从运行到第 1 秒的状态开始继续播放.

  如果不能理解, 可以结合一下文档中这篇: How do I pause all animations in a layer tree?
转载

如何禁用“隐式”或自动动画

【中文标题】如何禁用“隐式”或自动动画【英文标题】:How To Disable "Implicit" or Automatic Animations 【发布时间】:2011-05-13 17:54:58 【问题描述】:

我的代码遇到了一些障碍。我正在使用OpenFlow——Apple 的封面流替代方案,目前免费供开发人员使用。在演示中,一切似乎都很好。 “流”通过使用 UIView 动画进行动画处理。

我已将演示改编为可在 iPad 上运行。除了由于某种原因视图现在隐式动画之外,一切都运行良好。我不明白为什么会这样。我什至不认为隐式动画在 iOS 中可用。

我真的可以使用一些帮助来弄清楚为什么会发生这种情况,以及如何禁用它们。

【问题讨论】:

How to disable CALayer implicit animations?的可能重复 伙计们,10 年后一切都不同了:***.com/a/56980329/294884 【参考方案1】:

好吧,后来我头疼了很多,浪费了很多时间,我弄清楚了我的代码到底发生了什么。我以为我正在体验隐式动画,但我不明白为什么会突然发生这种情况。

我决定我最好尝试理解隐式动画,所以我自己尝试找出如何在受控情况下实现它们。我从未见过隐式动画发生的原因是因为我一直在使用 UIView 或其子类之一。

我了解到,如果您从 CALayer 开始并严格使用图层,则对许多属性的所有更改都将隐含动画。

当您看到 UIView(及其后代)都自动支持图层并具有 CALayer 属性时,可能会有些困惑(我知道对我来说是这样)。

从不如此,很明显 UIView 以某种方式覆盖了其 CALayer 属性的隐式动画机制。所以如果你想要隐式动画,你必须直接使用 CALayer,而不是仅仅假设因为 UIView 有一个 CALayer 属性,它会表现得一样。

至于我遇到的错误...这可能是我遇到的最奇怪的错误。一切,无论我尝试什么,都是在没有任何动画代码的情况下对值的任何更改进行动画处理。罪魁祸首最终是嵌套的 UIView 动画块。

注意以下事项,看看您是否立即发现问题:

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.2];
//animate something
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelay:.8];
[UIView setAnimationDuration:.4];
//animate something else
[UIView commitAnimations];

我未能用另一个 [UIView commitAnimations] 终止嵌套块。它实际上是在我的程序中泄漏动画。一切都是动画,甚至是完全不同的类中的代码。这个错误被压扁了......到下一个!

【讨论】:

以上是关于如何实现可交互的 UIView 隐式动画的主要内容,如果未能解决你的问题,请参考以下文章

在 UIView 动画期间难以允许用户交互

如何禁用“隐式”或自动动画

拖动 UIView 时如何实现流畅的动画

UICollectionView的隐式动画的问题

给予uiview animation有哪几种动画曲线

使用 UIView 动画从 CATransition 实现 kCATransitionPush