19、iOS 动画开发:UIPercentDrivenInteractiveTransition 与 UIViewPropertyAnimator 实战

iOS 动画开发:UIPercentDrivenInteractiveTransition 与 UIViewPropertyAnimator 实战

1. UIPercentDrivenInteractiveTransition 实现交互过渡

在自定义过渡动画中,通过采用 UIPercentDrivenInteractiveTransition 协议,可以轻松为自定义过渡添加交互性。交互过渡通常由用户手势驱动, UIPanGestureRecognizer 是一个能提供连续手势反馈的实用类。还可以通过设置 UIPercentDrivenInteractiveTransition interactive 属性值,在交互和非交互过渡模式之间进行切换。

下面是一个使弹出过渡具有交互性的挑战示例:
- 步骤 1:在 DetailViewController 中创建弱引用属性以持有动画器
- 从导航控制器栈中获取 MainViewController 的动画器对象。
- 步骤 2:为 DetailViewController 添加平移手势处理程序
- 该处理程序应与 MainViewController 中的方法几乎相同,但区别在于它应弹出当前视图控制器,而不是调用 segue。

以下是关键代码示例:

// 在 DetailViewController 中
weak var animator: YourAnimatorClass?

override func viewDidLoad() {
    super.viewDidLoad()
    if let mainVC = navigationController?.viewControllers.first as? MainViewController {
        animator = mainVC.animator
    }
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
    view.addGestureRecognizer(panGesture)
}

@objc func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
    // 处理手势逻辑,弹出当前视图控制器
    if gesture.state == .ended {
        navigationController?.popViewController(animated: true)
    }
}
2. UIViewPropertyAnimator 基础介绍

UIViewPropertyAnimator 是一个帮助开发者创建交互式、可中断视图动画的类。在 iOS 10 之前,创建基于视图的动画只能使用 UIView.animate(withDuration:...) 系列 API,这些 API 无法让开发者暂停或停止正在运行的动画。而 UIViewPropertyAnimator 可以让开发者控制正在运行的动画,调整它们,并提供有关动画当前状态的详细信息。

不过, UIView.animate(withDuration:...) API 在创建 iOS 动画中仍然发挥着重要作用,因为它们简单易用,适用于一些不需要中断或反转的简单动画。同时, UIViewPropertyAnimator 并没有实现 UIView.animate(withDuration:...) 的所有功能,所以有时仍需要使用旧的 API。

3. 基本动画实现

以下是使用 UIViewPropertyAnimator 创建基本动画的步骤:
1. 打开并运行项目 :启动项目后,会看到一个类似于 iOS 锁屏的界面,初始视图控制器显示一个搜索栏、一个小部件和底部的编辑按钮。
2. 创建初始动画 :打开 LockScreenViewController.swift ,添加 viewWillAppear(_:) 方法,对表格视图进行缩放和透明处理。

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    tableView.transform = CGAffineTransform(scaleX: 0.67, y: 0.67)
    tableView.alpha = 0
}
  1. 创建动画器 :在 viewDidAppear(_:) 方法中,使用 UIViewPropertyAnimator 的便利初始化器创建动画器。
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    let scale = UIViewPropertyAnimator(duration: 0.33, curve: .easeIn)
}

UIViewAnimationCurve 枚举提供了以下曲线选项:
| 选项 | 描述 |
| ---- | ---- |
| easeInOut | 先慢后快再慢 |
| easeIn | 先慢后快 |
| easeOut | 先快后慢 |
| linear | 匀速 |

  1. 添加动画 :使用 addAnimations 方法添加动画块,可以添加多个动画块,还可以设置延迟。
scale.addAnimations {
    self.tableView.alpha = 1.0
}

scale.addAnimations({
    self.tableView.transform = .identity
}, delayFactor: 0.33)
  1. 添加完成块 :使用 addCompletion 方法添加完成块,当动画完成时执行相应操作。
scale.addCompletion { _ in
    print("ready")
}
  1. 启动动画 :调用 startAnimation() 方法启动动画。
scale.startAnimation()
4. 动画代码抽象

为了使代码更简洁,将动画代码提取到一个单独的文件中。创建 AnimatorFactory.swift 文件,并添加以下代码:

import UIKit

enum AnimatorFactory {
    static func scaleUp(view: UIView) -> UIViewPropertyAnimator {
        let scale = UIViewPropertyAnimator(duration: 0.33, curve: .easeIn)
        scale.addAnimations {
            view.alpha = 1.0
        }
        scale.addAnimations({
            view.transform = CGAffineTransform.identity
        }, delayFactor: 0.33)
        scale.addCompletion { _ in
            print("ready")
        }
        return scale
    }
}

然后在 LockScreenViewController.swift 中替换 viewDidAppear(_:) 方法:

override func viewDidAppear(_ animated: Bool) {
    AnimatorFactory.scaleUp(view: tableView).startAnimation()
}
5. 运行动画器

如果只需要一个动画块并且希望立即运行,可以使用 UIViewPropertyAnimator.runningPropertyAnimator 类方法。以下是一个在用户使用搜索栏时淡入模糊层,搜索完成时淡出模糊层的示例:

func toggleBlur(_ blurred: Bool) {
    UIViewPropertyAnimator.runningPropertyAnimator(
        withDuration: 0.5, delay: 0.1, options: .curveEaseOut,
        animations: {
            self.blurView.alpha = blurred ? 1 : 0
        },
        completion: nil
    )
}

同时,实现 UISearchBarDelegate 方法来触发动画:

extension LockScreenViewController: UISearchBarDelegate {
    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        toggleBlur(true)
    }

    func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
        toggleBlur(false)
    }

    func searchBarResultsListButtonClicked(_ searchBar: UISearchBar) {
        searchBar.resignFirstResponder()
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        if searchText.isEmpty {
            searchBar.resignFirstResponder()
        }
    }
}
6. 基本关键帧动画

可以在 UIViewPropertyAnimator 动画块中使用 UIView.animate UIView.animateKeyframes 。以下是创建一个简单抖动关键帧动画的步骤:
1. 创建抖动动画方法 :在 AnimatorFactory.swift 中添加 jiggle(view:) 方法。

@discardableResult
static func jiggle(view: UIView) -> UIViewPropertyAnimator {
    return UIViewPropertyAnimator.runningPropertyAnimator(
        withDuration: 0.33, delay: 0, animations: {
            UIView.animateKeyframes(withDuration: 1, delay: 0,
                                    animations: {
                                        UIView.addKeyframe(withRelativeStartTime: 0.0,
                                                           relativeDuration: 0.25) {
                                            view.transform = CGAffineTransform(rotationAngle: -.pi / 8)
                                        }
                                        UIView.addKeyframe(withRelativeStartTime: 0.25,
                                                           relativeDuration: 0.75) {
                                            view.transform = CGAffineTransform(rotationAngle: +.pi / 8)
                                        }
                                        UIView.addKeyframe(withRelativeStartTime: 0.75,
                                                           relativeDuration: 1.0) {
                                            view.transform = CGAffineTransform.identity
                                        }
                                    },
                                    completion: nil)
        },
        completion: { _ in
            view.transform = .identity
        }
    )
}
  1. 在单元格中调用动画 :打开 IconCell.swift ,添加 iconJiggle() 方法。
func iconJiggle() {
    AnimatorFactory.jiggle(view: icon)
}
  1. 触发动画 :在 WidgetView.swift 中,当用户点击集合视图单元格时,调用 iconJiggle() 方法。
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    if let cell = collectionView.cellForItem(at: indexPath) as? IconCell {
        cell.iconJiggle()
    }
}

通过以上步骤,我们可以利用 UIPercentDrivenInteractiveTransition UIViewPropertyAnimator 实现丰富多样的动画效果,提升用户体验。

下面是一个简单的 mermaid 流程图,展示基本动画的创建流程:

graph LR
    A[打开项目] --> B[创建初始动画]
    B --> C[创建动画器]
    C --> D[添加动画]
    D --> E[添加完成块]
    E --> F[启动动画]

综上所述, UIPercentDrivenInteractiveTransition UIViewPropertyAnimator 为 iOS 动画开发提供了强大的功能和灵活性,开发者可以根据具体需求选择合适的方法来实现各种动画效果。同时,通过代码抽象和合理的设计,可以使代码更加简洁易维护。在实际开发中,还可以进一步探索这些 API 的更多用法,创造出更加精彩的动画效果。

iOS 动画开发:UIPercentDrivenInteractiveTransition 与 UIViewPropertyAnimator 实战

7. 交互过渡与手势驱动的详细分析

在使用 UIPercentDrivenInteractiveTransition 实现交互过渡时,手势驱动是核心。以 UIPanGestureRecognizer 为例,它能提供连续的手势反馈,让过渡动画随着用户的手势操作而动态变化。

下面详细分析手势处理的逻辑:

// 在 DetailViewController 中
weak var animator: YourAnimatorClass?
var interactiveTransition: UIPercentDrivenInteractiveTransition?

override func viewDidLoad() {
    super.viewDidLoad()
    if let mainVC = navigationController?.viewControllers.first as? MainViewController {
        animator = mainVC.animator
    }
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
    view.addGestureRecognizer(panGesture)
}

@objc func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
    let translation = gesture.translation(in: view)
    let progress = abs(translation.x) / view.bounds.width

    switch gesture.state {
    case .began:
        interactiveTransition = UIPercentDrivenInteractiveTransition()
        navigationController?.popViewController(animated: true)
    case .changed:
        interactiveTransition?.update(progress)
    case .ended, .cancelled:
        if progress > 0.5 {
            interactiveTransition?.finish()
        } else {
            interactiveTransition?.cancel()
        }
        interactiveTransition = nil
    default:
        break
    }
}

上述代码中,手势处理逻辑分为三个阶段:
- 开始阶段( .began :创建 UIPercentDrivenInteractiveTransition 实例,并触发弹出过渡动画。
- 变化阶段( .changed :根据手势的平移距离计算过渡进度,并使用 update(_:) 方法更新过渡动画。
- 结束或取消阶段( .ended .cancelled :根据进度判断是否完成过渡,如果进度大于 0.5,则完成过渡;否则取消过渡。

以下是手势处理逻辑的 mermaid 流程图:

graph LR
    A[手势开始] --> B[创建过渡实例并触发动画]
    B --> C{手势变化?}
    C -- 是 --> D[计算进度并更新动画]
    C -- 否 --> E{手势结束或取消?}
    E -- 是 --> F{进度 > 0.5?}
    F -- 是 --> G[完成过渡]
    F -- 否 --> H[取消过渡]
    E -- 否 --> C
8. UIViewPropertyAnimator 的状态管理

UIViewPropertyAnimator 提供了丰富的状态管理功能,开发者可以暂停、继续、反转和停止动画。以下是一些常用的状态管理方法:
| 方法 | 描述 |
| ---- | ---- |
| pauseAnimation() | 暂停正在运行的动画 |
| continueAnimation(withTimingParameters:durationFactor:) | 继续暂停的动画 |
| reverse() | 反转动画的播放方向 |
| stopAnimation(_:) | 停止动画,可以选择是否完成当前帧 |

以下是一个简单的示例,展示如何使用这些方法:

var animator: UIViewPropertyAnimator?

func setupAnimator() {
    animator = UIViewPropertyAnimator(duration: 1.0, curve: .easeInOut) {
        // 动画代码
        self.view.alpha = 0
    }
    animator?.addCompletion { _ in
        print("Animation completed")
    }
}

func pauseAnimation() {
    animator?.pauseAnimation()
}

func continueAnimation() {
    animator?.continueAnimation(withTimingParameters: nil, durationFactor: 1.0)
}

func reverseAnimation() {
    animator?.reverse()
}

func stopAnimation() {
    animator?.stopAnimation(true)
}
9. 动画性能优化

在使用 UIViewPropertyAnimator 进行动画开发时,性能优化是一个重要的考虑因素。以下是一些优化建议:
- 减少不必要的动画 :避免同时运行过多的动画,只在必要时触发动画。
- 使用轻量级视图 :尽量使用轻量级的视图进行动画,减少内存开销。
- 优化动画参数 :合理设置动画的持续时间、延迟和曲线,避免过于复杂的动画效果。
- 异步处理 :对于一些耗时的动画操作,可以考虑使用异步线程进行处理,避免阻塞主线程。

以下是一个优化动画性能的示例:

func performOptimizedAnimation() {
    DispatchQueue.global().async {
        // 模拟耗时操作
        Thread.sleep(forTimeInterval: 0.1)
        DispatchQueue.main.async {
            let animator = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) {
                self.view.alpha = 0
            }
            animator.startAnimation()
        }
    }
}
10. 总结与展望

通过前面的介绍,我们深入了解了 UIPercentDrivenInteractiveTransition UIViewPropertyAnimator 在 iOS 动画开发中的应用。 UIPercentDrivenInteractiveTransition 让我们能够实现交互过渡动画,增强用户体验;而 UIViewPropertyAnimator 则提供了强大的动画创建和管理功能,包括基本动画、关键帧动画、状态管理等。

在实际开发中,我们可以根据具体需求选择合适的动画方法,并结合性能优化技巧,创建出流畅、美观的动画效果。未来,随着 iOS 系统的不断更新,这些动画 API 可能会提供更多的功能和优化,开发者可以持续关注并探索更多的可能性。

以下是一个总结性的 mermaid 流程图,展示整个动画开发的流程:

graph LR
    A[选择动画类型] --> B{交互过渡?}
    B -- 是 --> C[使用 UIPercentDrivenInteractiveTransition]
    B -- 否 --> D[使用 UIViewPropertyAnimator]
    C --> E[处理手势驱动]
    D --> F[创建动画器]
    F --> G[添加动画和完成块]
    G --> H[启动动画]
    E --> I[根据手势更新过渡]
    H --> J{需要状态管理?}
    J -- 是 --> K[使用状态管理方法]
    J -- 否 --> L[结束动画]
    I --> M{手势结束?}
    M -- 是 --> N{完成过渡?}
    N -- 是 --> L
    N -- 否 --> O[取消过渡]
    M -- 否 --> I

总之,掌握 UIPercentDrivenInteractiveTransition UIViewPropertyAnimator 是 iOS 动画开发的重要技能,开发者可以利用这些工具创造出更加出色的应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值