前言
我们都知道在日常开发中动画的成分是必不可少的,从那些排行榜单的APP也能看出一款高质量的APP是少不了动画的加持,早期iOS之所以大放异彩,就是因为iOS把动画运用到了系统的各个角落。从事iOS的开发者们也知道并使用动画,使用更多的应该是UIView.animate(withDuration:animations:)
,其次还有使用CABasicAnimation、CAKeyFrameAnimation以及转场动画。这些我们都知道,但更少用到的是这个方式:UIViewPropertyAnimator。
UIViewPropertyAnimator对象允许您对视图进行动画处理,并在动画完成之前动态修改它们。使用属性动画师,您可以从头到尾正常运行动画,或者您可以将它们转换为交互式动画并自行控制时间。动画师对视图的可动画属性进行操作,例如帧,中心,alpha和变换属性,从您提供的块创建所需的动画。
….
如果使用标准初始化方法之一创建动画师,则必须通过调用startAnimation()方法显式启动动画。如果要在创建动画后立即启动动画,请使用runningPropertyAnimator方法而不是标准初始值设定项。
此类采用UIViewAnimating和UIViewImplicitlyAnimating协议,这些协议定义了启动,停止和修改动画的方法。有关这些协议的方法的更多信息,请参阅UIViewAnimating和UIViewImplicitlyAnimating。
更多详细文档可以查看官方文档
初探UIViewPropertyAnimator
UIViewPropertyAnimator是iOS 10引入的,用此类创建动画从上面我们得知必须手动调用开始动画方法:
这个是使用UIView的做法:
UIView.animate(withDuration: 0.25) {
view.frame = view.frame.offsetBy(dx: 150, dy: 0)
}
复制代码
通过UIViewPropertyAnimator实现的做法:
let animator = UIViewPropertyAnimator(duration:0.25, curve: .linear) {
view.frame = view.frame.offsetBy(dx:150, dy:0)
}
animator.startAnimation()
复制代码
从上面看来看区别不大,那还有其他的特点吗?肯定是有的,特别是在交互式动画和可中断动画中。记得在我们关机的时候出现的"滑动来关机"的交互动画;还有从底部滑出控制中心等等就是交互式动画和可中断动画就是最好的案例。
动画状态
由于遵守了UIViewAnimating和UIViewImplicitlyAnimating协议,使用这种方式具有完整的动画逻辑,例如调用startAnimation、pauseAnimation、stopAnimation等方法将会在inactive、active、stopped的三种状态间改变。
当动画开始或暂停的时候为active状态,当控件创建完未开始或执行完毕时候的状态为inactive,inactive和stopped的区别在于:动画调用pauseAnimation函数后进行stopped函数,而animator内部会调用finishAnimation(at:)
函数来标明此动画已完毕,然后进入inactive的状态,最后回调闭包。
动画选项
创建动画时常见的是动画时长、动画时间曲线(UIViewAnimationCurve已经包含大部分可用的曲线)也可以进行自定义。
// 设置动画时间和动画时间曲线
let animator = UIViewPropertyAnimator(duration: 1.0, curve: .easeOut){
aView.center = finalPoint
}
// 使用两个控制点来定义一个贝塞尔时间曲线
let animator = UIViewPropertyAnimator(duration: 1.0, point1: CGPoint(0.1,0.5), point2: CGPoint(0.5, 0.2){
aView.alpha = 0.0
}
// 实现弹簧效果
let animator = UIViewPropertyAnimator(
duration: 1.0,
dampingRatio:0.4){
AView.center = CGPoint(x:0, y:0)
}
// 动画延迟
animator.startAnimation(afterDelay:2.0)
复制代码
动画回调
UIViewPropertyAnimator 遵守了 UIViewImplicitlyAnimating
协议,这个协议赋予 UIViewPropertyAnimator 许多有趣的特性。例如,除了在初始化期间指定的第一个动画 block 外,还可以指定多个动画 block。值得注意的是,虽然我们可以在已经开始的动画里添加动画,但是这会导致剩余动画时间来执行新添加的动画。
// Initialization
let animator = UIViewPropertyAnimator(duration: 2.5, curve: .easeOut){
aView.alpha = 1.0
}
// Another animation block
animator.addAnimation{
aview.center = aNewPosition
}
// add a animation block
animator.addAnimation{
aView.center.y = aNewPosition
}
animator.startAnimation()
复制代码
动画交互
我们知道可以在三个状态间改变,也就可以实现动画的循环。还有一个属性可以控制动画的完成百分比:fractionComplete, 取值范围[0, 1.0]。可以通过此值来获取动画的百分比,也可以通过设置此值来控制动画的百分比。常见的做法通过手势或一些slider等的时候实时改变并控制动画。
animator.fractionComplete = progress
复制代码
还有某些场景需要在动画结束的时候执行一些任务,UIViewPropertyAnimator允许添加执行完成闭包
// position是一个UIViewAnimatingPosition枚举:starting、current、end。通常position的值是end
animator.addCompletion { (position) in
print("Animation completed")
}
复制代码
高级动画实现
这是一个很常见的应用场景,有些页面分享、预览等页面会以此方式来展现内容。用户在使用这个场景时会觉得该APP还不错,自从苹果推出滑动关机以后至今,动画在各个app以及系统应用非常之多,因为人都是喜欢漂亮的、美丽的,都有一种对美感的追求。回到这个图,我们知道做法可以有很多种,一般会采用自定义转场来实现,如果有封装成套的转场接口实现还是比较简单的,但是通过其他方式来做的话,控制动画的进度是一个问题,从上面的介绍我们知道UIView动画是不能控制动画的进度,使用其他动画来实现更为少见。
实现视图的动画
let transitionAnimator = UIViewPropertyAnimator.init(duration: duration, dampingRatio: 1) {
switch state{
case .close:
self.popupView.layer.cornerRadius = 0
self.popupView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
self.bottomConstraint.constant = self.popupViewOffset
self.overlayView.alpha = 0.0
case .open:
self.popupView.layer.cornerRadius = 20
self.popupView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
self.bottomConstraint.constant = 0
self.overlayView.alpha = 0.5
}
self.view.layoutIfNeeded()
}
transitionAnimator.addCompletion { (position) in
switch position{
case .start:
self.currentState = state.change
case .end:
self.currentState = state
case .current:
()
@unknown default:
fatalError()
}
}
transitionAnimator.startAnimation()
复制代码
视图添加手势控制
@objc private func handlePanGesture(_ recognzier: UIPanGestureRecognizer){
switch recognzier.state {
case .began:
self.animateToState(self.currentState.change, duration: 0.5)
self.runningAnimators.forEach{ $0.pauseAnimation() }
self.runningAnimatorProgress = self.runningAnimators.map{ $0.fractionComplete }
case .changed:
let translation = recognzier.translation(in: popupView)
var fraction = -translation.y / popupViewOffset
if self.currentState == .open {
fraction *= -1.0
}
if self.runningAnimators.first?.isReversed ?? false {
fraction *= -1.0
}
for (index, animator) in runningAnimators.enumerated(){
animator.fractionComplete = fraction + self.runningAnimatorProgress[index]
}
case .ended:
self.runningAnimators.forEach{ $0.continueAnimation(withTimingParameters: nil, durationFactor: 0) }
default:
()
}
}
复制代码
总结
我们认识了UIViewPropertyAnimator之后的开发动画可以在一些合适的场景去使用它,因为可以手动控制开始、暂停、恢复以及进度,在很大程度上满足了开发者的功能需求。
动画和UI这方面国外很多设计都很棒,是一种视觉盛宴和非凡的体会,和看美女是一样的感觉,在国内只有少数。