iOS动画:UIViewPropertyAnimator动画之交互(16)

本文详细介绍了使用UIViewPropertyAnimator在iOS应用中实现3Dtouch动画的过程,包括动画的开始、更新、完成和取消等关键步骤,以及如何通过预览交互代理实现复杂的动画效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UIViewPropertyAnimator对象有三个属性:
image_1
isRunning:只读属性,表明动画是否处于运动状态,默认值为false,当调用startAnimation()时变为true,如果动画暂停(paused)、停止(stopped)、完成(finish),它将再次变为false。
isReversed:可读可写属性,默认值为false,当设置为true时,动画将反转执行到初始状态。
state:只读属性,默认值为inactive,这意味着你刚刚创建动画还没有调用任何方法。
调用以下方法会变成active状态:
调用startAnimation()来启动动画;
在没有开始动画的情况下调用pauseAnimation();
设置fractionComplete属性将动画“倒回”到某个位置。

动画完成后,state将切换回inactive状态。如果你调用stopAnimation(),state属性将变成stopped状态,在这种状态下,你唯一能做的就是停止整个动画,调用finishAnimation(at:)将动画变回inactive状态。

UIViewPropertyAnimator只能按照特定的顺序切换状态,你不能从inactive切换到stopped,也不能从stopped切换到active。

UIViewPropertyAnimator在iOS11之后有一个新的属性pausesOnCompletion,当设置为true时,一旦动画运行完成,它将不自动停止而是进入暂停状态,这时候你将可以从暂停状态继续做你后续的工作。

流程图如下
image_2

3D touch交互动画(iOS10之后)

3D touch交互主要使用UIPreviewInteractionDelegate协议的方法,这里不介绍3D touch如何使用,仅仅介绍3D touch动画。

  public func previewInteractionDidCancel(_ previewInteraction: UIPreviewInteraction) {
    owner.cancelPreview()
  }
  
  func previewInteractionShouldBegin(_ previewInteraction: UIPreviewInteraction) -> Bool {
    
    if let indexPath = collectionView?.indexPathForItem(at: previewInteraction.location(in: collectionView!)),
      let cell = collectionView?.cellForItem(at: indexPath) as? IconCell {
      
      owner.startPreview(for: cell.icon)
    }
    return true
  }
  
  func previewInteraction(_ previewInteraction: UIPreviewInteraction, didUpdatePreviewTransition transitionProgress: CGFloat, ended: Bool) {
    owner.updatePreview(percent: transitionProgress)
    
    if ended {
      owner.finishPreview()
    }
  }

我们将在应用内实现这种效果:
image_3
在AnimatorFactory.swift中创建动画:

  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: UIBlurEffectStyle.dark)
      view.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
    }
    animator.addCompletion { (_) in
      blurView.effect = UIBlurEffect(style: UIBlurEffectStyle.dark)
    }
    return animator
  }

在LockScreenViewController.swift中添加属性:

  var startFrame: CGRect?//动画的初始坐标
  var previewView: UIView?//icon的快照,用于3D touch显示
  var previewAnimator: UIViewPropertyAnimator?//动画
  let previewEffectView = IconEffectView(blur: .extraLight)//重按后icon的边框边框
  let blurView = UIVisualEffectView(effect: nil)//3D touch后的背景模糊效果

3D touch开始时,设置属性值:

  func startPreview(for forView: UIView) {
    previewView?.removeFromSuperview()//移除旧的icon快照
    previewView = forView.snapshotView(afterScreenUpdates: false)
    view.insertSubview(previewView!, aboveSubview: blurView)//
    previewView?.frame = forView.convert(forView.bounds, to: view)//设置快照的坐标在原icon之上
    startFrame = previewView?.frame//记住初始坐标
    addEffectView(below: previewView!)//将icon边框效果插入icon快照下面
    
    previewAnimator = AnimatorFactory.grow(view: previewEffectView, blurView: blurView)//创建动画,动画还未开始
  }
  
    func addEffectView(below forView: UIView) {
    previewEffectView.removeFromSuperview()
    previewEffectView.frame = forView.frame
    forView.superview?.insertSubview(previewEffectView, belowSubview: forView)
  }

3D touch更新回调后更新动画到指定位置:

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

下面我们来实现动画的完成和取消方法。

继续在AnimatorFactory.swift中添加动画:

  static func reset(frame: CGRect, view: UIVisualEffectView, blurView: UIVisualEffectView) -> UIViewPropertyAnimator {//还原
    return UIViewPropertyAnimator(duration: 0.5, dampingRatio: 0.7, animations: {
      view.transform = .identity
      view.frame = frame
      view.contentView.alpha = 0
      
      blurView.effect = nil
    })
  }

在LockScreenViewController中实现3D touch还原的动画:

  func cancelPreview() {//用户轻按时还原动画
    if let previewAnimator = previewAnimator {
      previewAnimator.isReversed = true
      previewAnimator.startAnimation()
    }
  }

还原的动画就完成啦,下面添加完成的动画。
在AnimatorFactory.swift中的grow方法,我们已经添加了完成的block,我们只需对其进行修改即可:

    animator.addCompletion { position in
      switch position {
        case .start:
          blurView.effect = nil
        case .end:
          blurView.effect = UIBlurEffect(style: .dark)
        default: break
      }
    }

position是一个UIViewAnimatingPosition类型的枚举值,分别代表动画是在开始停止,结束后停止,还是当前位置停止。 通常你都会收到结束的枚举值。它有三个枚举值:start、end、current,如果你的动画自然完成或者以其他方式结束,它将返回end值;如果你反转动画并在开始的位置完成,那么它将返回start;如果你中途停止动画并在那里完成,它将返回current值。

动画在开始位置完成(即轻按3D touch)后需要移除icon快照和边框,在LockScreenViewController的cancelPreview方法中添加代码:

	previewAnimator.addCompletion { (position) in
        switch position {
          case .start:
            self.previewView?.removeFromSuperview()
            self.previewEffectView.removeFromSuperview()
        default: break
        }
    }

addCompletion可以添加多个,并不会对之前添加的有任何影响。
3D touch的完成和还原动画就实现啦。下面我们来设置3D touch弹出的菜单(即之前添加的icon快照的边框previewEffectView,我们只需要改变其位置和大小即可)。

在AnimatorFactory中添加动画:

  static func complete(view: UIVisualEffectView) -> UIViewPropertyAnimator {
    return UIViewPropertyAnimator(duration: 0.3, dampingRatio: 0.7, animations: {
      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)
    })
  }

在LockScreenViewController中3D touch完成的回调里添加动画:

  func finishPreview() {
    previewAnimator?.stopAnimation(false)//false表示将动画停止,需要手动调用finishAnimation完成动画,true表示动画停止,并将动画置于inactive状态,不需要调用finishAnimation
    previewAnimator?.finishAnimation(at: .end)
    previewAnimator = nil
    AnimatorFactory.complete(view: previewEffectView).startAnimation()
  }

下面添加点击背景取消3D touch的动画。
在finishPreview中添加代码:

    blurView.effect = UIBlurEffect(style: .dark)
    blurView.isUserInteractionEnabled = true
    blurView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismissMenu)))

设置dimissMenu的动画:

  @objc func dismissMenu() {
    let reset = AnimatorFactory.reset(frame: startFrame!, view: previewEffectView, blurView: blurView)
    reset.addCompletion { (_) in
      self.previewEffectView.removeFromSuperview()
      self.previewView?.removeFromSuperview()
      self.blurView .isUserInteractionEnabled = false
    }
    reset.startAnimation()
  }

运行效果:
image_4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值