21、iOS动画开发:UIViewPropertyAnimator的进阶与交互应用

iOS动画开发:UIViewPropertyAnimator的进阶与交互应用

1. 应用运行与图标重载动画

运行应用程序,点击“Show More”,即可欣赏到流畅的弹簧驱动动画。为了实现图标重载,可以在方法底部添加以下代码:

widgetView.expanded = showsMore 
widgetView.reload()

这段代码会触发集合视图的 reloadData() 方法,从而重新加载所有图标。再次尝试动画,你会根据小部件的高度看到不同数量的图标。

2. 内置视图过渡

为了完善这个动画,可以使用 UIViewPropertyAnimator 结合内置的视图过渡效果。这里以将小部件按钮的标题从“Show More”切换为“Show Less”为例,使用交叉淡入淡出效果。在 toggleShowMore(_:) 方法中,定义动画块后、定义动画器之前添加以下代码:

let textTransition = { 
  UIView.transition( 
    with: sender,  
    duration: 0.25,  
    options: .transitionCrossDissolve, 
    animations: { 
      sender.setTitle( 
        self.showsMore ? "Show Less" : "Show More",  
        for: .normal) 
    }, 
    completion: nil) 
}

然后在添加动画到动画器的位置,添加 textTransition 并设置0.5的延迟因子:

toggleHeightAnimator?.addAnimations(animations) 
toggleHeightAnimator?.addAnimations(textTransition, delayFactor: 0.5) 
toggleHeightAnimator?.startAnimation()

这样就可以实现按钮标题的平滑切换效果。如果想尝试其他过渡效果,可以将 .transitionCrossDissolve 替换为 .transitionFlipFromTop

3. UIViewPropertyAnimator的关键点

  • 模糊效果动画 :使用 UIViewPropertyAnimator 可以对 UIBlurEffect 的值进行动画处理,创建令人印象深刻的模糊动画。
  • 自定义动画时序 :通过使用两个控制点的自定义贝塞尔曲线,可以创建自己的自定义动画时序,摆脱预定义的缓动常量。
  • 混合过渡API :可以将预定义的UIKit过渡API(如 UIView.transition(with:duration:options:animations:completion) )与其他 UIViewPropertyAnimator 动画混合使用,它们可以很好地协同工作。

4. 挑战:加法动画

当使用 UIView.animate(withDuration:...) 时,对同一视图属性添加动画是累加的。例如,在视图从点A移动到点B的过程中,如果中途改变主意,决定将视图移动到点C,UIKit会尝试“平滑”地将视图过渡到新的轨迹。

对于这个挑战,如果在 toggleShowMore(_:) 中,前一个动画尚未完成时再次切换菜单状态,应该向现有的 toggleHeightAnimator 添加动画,而不是创建新的动画器。具体步骤如下:
1. 使用 isRunning 检查 toggleHeightAnimator 是否正在运行。
2. 由于 isRunning 可能不太可靠,还需要检查 fractionComplete 是否小于1。
3. 如果动画器已经创建,暂停它,添加新的动画块,然后继续动画。
4. 如果效果不理想,可以尝试将初始弹簧速度调整为零或完全替换弹簧。

以下是示例代码:

if let toggleHeightAnimator = toggleHeightAnimator, toggleHeightAnimator.isRunning, toggleHeightAnimator.fractionComplete < 1 {
    toggleHeightAnimator.pauseAnimation()
    toggleHeightAnimator.addAnimations(newAnimationBlock)
    toggleHeightAnimator.startAnimation()
}

5. 动画状态机

UIViewPropertyAnimator 具有状态机的行为,可以提供有关动画当前状态的各种信息。主要有三个属性可以帮助确定当前状态:
| 属性 | 描述 |
| ---- | ---- |
| isRunning | 只读属性,指示动画器的动画是否正在运行。默认值为 false ,调用 startAnimation() 时变为 true ,动画暂停、停止或自然完成时变为 false 。 |
| isReversed | 默认值为 false ,表示动画从起始状态播放到结束状态。将其设置为 true ,动画将反向播放到初始状态。 |
| state | 只读属性,确定动画器是处于活动状态并正在进行动画,还是处于其他被动状态。默认值为 inactive ,表示动画器刚刚创建且尚未调用任何方法。 |

状态转换规则如下:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([Inactive]):::startend -->|startAnimation()| B(Active):::process
    A -->|pauseAnimation()| B
    A -->|set fractionComplete| B
    B -->|Animation Complete| A
    B -->|stopAnimation()| C(Stopped):::process
    C -->|finishAnimation(at:)| A

6. 交互式3D触摸动画

在这部分,将创建一个类似于iPhone主屏幕上3D触摸交互的交互式动画。需要一个支持3D触摸的iOS设备或模拟器的Force Touch触控板。

6.1 代码准备

  1. 打开 WidgetView.swift ,找到符合 UIPreviewInteractionDelegate 的扩展,这些是用户按压小部件视图时UIKit调用的委托方法。
  2. 这些委托方法已经连接到 LockScreenViewController 中的相关方法:
    • 3D触摸开始时,调用 LockScreenViewController.startPreview(for:)
    • 用户按压更用力(或更轻)时,重复调用 LockScreenViewController.updatePreview(percent:)
    • 预览交互成功完成时,调用 LockScreenViewController.finishPreview()
    • 用户在未完成预览手势的情况下抬起手指时,调用 LockScreenViewController.cancelPreview()

6.2 添加属性

打开 LockScreenViewController.swift ,添加以下属性:

var startFrame: CGRect? 
var previewView: UIView? 
var previewAnimator: UIViewPropertyAnimator?
let previewEffectView = IconEffectView(blur: .extraLight)

6.3 实现 startPreview(for:) 方法

func startPreview(for forView: UIView) { 
  previewView?.removeFromSuperview() 
  if let preview = forView.snapshotView(afterScreenUpdates: false) { 
    previewView = preview 
    view.insertSubview(preview, aboveSubview: blurView) 
    preview.frame = forView.convert(forView.bounds, to: view) 
    startFrame = preview.frame 
    addEffectView(below: preview)
  } 
}

6.4 实现 addEffectView(below:) 方法

func addEffectView(below forView: UIView) { 
  previewEffectView.removeFromSuperview() 
  previewEffectView.frame = forView.frame 
  forView.superview?.insertSubview( 
    previewEffectView, 
    belowSubview: forView) 
}

6.5 创建动画工厂方法

打开 AnimatorFactory.swift ,添加 grow 方法:

static func grow( 
  view: UIVisualEffectView, 
  blurView: UIVisualEffectView 
) -> UIViewPropertyAnimator { 
  view.contentView.alpha = 0 
  view.transform = .identity 
  let animator = UIViewPropertyAnimator( 
    duration: 0.5, curve: .easeIn) 
  animator.addAnimations { 
    blurView.effect = UIBlurEffect(style: .dark) 
    view.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) 
  } 
  animator.addCompletion { _ in 
    blurView.effect = UIBlurEffect(style: .dark) 
  }
  return animator 
}

6.6 实现 updatePreview(percent:) 方法

func updatePreview(percent: CGFloat) { 
  previewAnimator?.fractionComplete = max(0.01, min(0.99, percent)) 
}

6.7 实现取消和完成方法

  • 取消预览
func cancelPreview() { 
  if let previewAnimator = previewAnimator { 
    previewAnimator.isReversed = true 
    previewAnimator.startAnimation() 
    previewAnimator.addCompletion { position in 
      switch position { 
      case .start: 
        self.previewView?.removeFromSuperview() 
        self.previewEffectView.removeFromSuperview() 
      default: break 
      } 
    }
  } 
}
  • 完成预览
func finishPreview() { 
  previewAnimator?.stopAnimation(false) 
  previewAnimator?.finishAnimation(at: .end) 
  previewAnimator = nil 
  AnimatorFactory.complete(view: previewEffectView).startAnimation()
}

通过以上步骤,就可以实现一个完整的交互式3D触摸动画,当用户按压图标时,会出现模糊背景和菜单展开的效果。

7. 交互式3D触摸动画的细节优化

7.1 动画工厂方法的调整

在之前的 grow 方法中,其完成块在动画反向播放时会出现问题,导致黑暗模糊效果异常显示。因此需要对完成块进行调整,以确保动画在正向和反向播放时都能正确处理模糊效果。

static func grow( 
  view: UIVisualEffectView, 
  blurView: UIVisualEffectView 
) -> UIViewPropertyAnimator { 
  view.contentView.alpha = 0 
  view.transform = .identity 
  let animator = UIViewPropertyAnimator( 
    duration: 0.5, curve: .easeIn) 
  animator.addAnimations { 
    blurView.effect = UIBlurEffect(style: .dark) 
    view.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) 
  } 
  animator.addCompletion { position in 
    switch position { 
      case .start: 
        blurView.effect = nil 
      case .end: 
        blurView.effect = UIBlurEffect(style: .dark) 
      default: 
        break 
    } 
  }
  return animator 
}

7.2 解决图标不可交互问题

当用户取消按压图标时,图标快照会覆盖原图标,导致图标无法再次交互。为了解决这个问题,在 cancelPreview 方法中添加代码,在重置动画完成后移除图标快照和图标框架。

func cancelPreview() { 
  if let previewAnimator = previewAnimator { 
    previewAnimator.isReversed = true 
    previewAnimator.startAnimation() 
    previewAnimator.addCompletion { position in 
      switch position { 
      case .start: 
        self.previewView?.removeFromSuperview() 
        self.previewEffectView.removeFromSuperview() 
      default: break 
      } 
    }
  } 
}

7.3 显示图标菜单的动画

为了显示图标菜单,在 AnimatorFactory 中添加 complete 方法,创建一个简单的弹簧动画,用于淡入菜单、重置变换并将视图框架动画到图标上方的位置。

static func complete(view: UIVisualEffectView) -> 
UIViewPropertyAnimator { 
  return UIViewPropertyAnimator( 
    duration: 0.3,  
    dampingRatio: 0.7) {
    view.contentView.alpha = 1 
    view.transform = .identity 
    view.frame = CGRect( 
      x: view.frame.minX - view.frame.minX / 2.5, 
      y: view.frame.maxY - 140, 
      width: view.frame.width + 120, 
      height: 60 
    ) 
  } 
}

8. 总结与回顾

8.1 核心知识点总结

  • UIViewPropertyAnimator的强大功能 :可以实现模糊效果动画、自定义动画时序,还能与预定义的UIKit过渡API混合使用。
  • 动画状态管理 :通过 isRunning isReversed state 三个属性,能够清晰地了解动画的当前状态,并根据状态进行相应的操作。
  • 交互式动画实现 :以3D触摸动画为例,展示了如何利用 UIViewPropertyAnimator 创建交互式动画,包括动画的启动、暂停、反向播放以及完成处理。

8.2 操作流程回顾

操作步骤 描述
运行应用与图标重载 在方法底部添加代码触发集合视图的 reloadData() 方法,重新加载图标。
内置视图过渡 toggleShowMore(_:) 方法中添加过渡动画,实现按钮标题的平滑切换。
加法动画挑战 检查 toggleHeightAnimator 状态,添加新动画块实现加法动画。
动画状态机 根据 isRunning isReversed state 属性管理动画状态。
交互式3D触摸动画 从代码准备、属性添加、方法实现到动画工厂方法创建,逐步实现完整的交互式动画。

8.3 未来展望

通过对 UIViewPropertyAnimator 的深入学习和实践,我们可以创建出更加复杂和精彩的动画效果。在未来的开发中,可以进一步探索其与其他动画技术的结合,为用户带来更加流畅和富有交互性的界面体验。同时,不断尝试新的动画创意和应用场景,提升应用的吸引力和竞争力。

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B(运行应用与图标重载):::process
    B --> C(内置视图过渡):::process
    C --> D(加法动画挑战):::process
    D --> E(动画状态机管理):::process
    E --> F(交互式3D触摸动画实现):::process
    F --> G([结束]):::startend

综上所述,掌握 UIViewPropertyAnimator 的使用方法和技巧,对于iOS动画开发至关重要。通过不断地实践和优化,我们可以创造出更加出色的动画效果,为用户带来更好的视觉体验。

基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究(Matlab代码实现)内容概要:本文围绕“基于数据驱动的Koopman算子的递归神经网络模型线性化”展开,旨在研究纳米定位系统的预测控制方法。通过结合数据驱动技术Koopman算子理论,将非线性系统动态近似为高维线性系统,进而利用递归神经网络(RNN)建模并实现系统行为的精确预测。文中详细阐述了模型构建流程、线性化策略及在预测控制中的集成应用,并提供了完整的Matlab代码实现,便于科研人员复现实验、优化算法并拓展至其他精密控制系统。该方法有效提升了纳米级定位系统的控制精度动态响应性能。; 适合人群:具备自动控制、机器学习或信号处理背景,熟悉Matlab编程,从事精密仪器控制、智能制造或先进控制算法研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①实现非线性动态系统的数据驱动线性化建模;②提升纳米定位平台的轨迹跟踪预测控制性能;③为高精度控制系统提供可复现的Koopman-RNN融合解决方案; 阅读建议:建议结合Matlab代码逐段理解算法实现细节,重点关注Koopman观测矩阵构造、RNN训练流程模型预测控制器(MPC)的集成方式,鼓励在实际硬件平台上验证并调整参数以适应具体应用场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值