UIScrollview 动画从视图到视图的过渡
UIScrollview Animation Transition From View To View
我试图在用户滚动分页视图时执行过渡动画,即:从 第 1 页 到 第 2 页 .
不幸的是,我无法复制它。
下面附上我做的:
class OnboardingParallaxImageView: BaseUIView, UIScrollViewDelegate {
let allImages = [#imageLiteral(resourceName: "onboarding_handshake_icon"), #imageLiteral(resourceName: "onboarding_paylinc_icon")]
var activeCurrentPage = 1
let bgView: UIImageView = {
let image = #imageLiteral(resourceName: "onboard_bg_gradient")
let view = UIImageView(image: image)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let firstImageView: UIImageView = {
let view = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let secondImageView: UIImageView = {
let view = UIImageView()
view.isHidden = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var firstImageHeightAnchor: NSLayoutConstraint?
var firstImageWidthAnchor: NSLayoutConstraint?
var secondImageHeightAnchor: NSLayoutConstraint?
var secondImageWidthAnchor: NSLayoutConstraint?
override func setupViews() {
super.setupViews()
addSubview(bgView)
addSubview(firstImageView)
addSubview(secondImageView)
bgView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
bgView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
bgView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
bgView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
firstImageWidthAnchor = firstImageView.widthAnchor.constraint(equalTo: widthAnchor)
firstImageWidthAnchor?.isActive = true
firstImageHeightAnchor = firstImageView.heightAnchor.constraint(equalTo: heightAnchor)
firstImageHeightAnchor?.isActive = true
firstImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
firstImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
firstImageView.image = allImages[0]
secondImageWidthAnchor = secondImageView.widthAnchor.constraint(equalTo: widthAnchor)
secondImageWidthAnchor?.isActive = true
secondImageHeightAnchor = secondImageView.heightAnchor.constraint(equalTo: heightAnchor)
secondImageHeightAnchor?.isActive = true
secondImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
secondImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
secondImageView.image = allImages[1]
}
override func layoutSubviews() {
super.layoutSubviews()
let frameWidth = frame.size.width
secondImageHeightAnchor?.constant = -(frameWidth - 32)
secondImageWidthAnchor?.constant = -(frameWidth - 32)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offSet = scrollView.contentOffset.x
let frameWidth = frame.size.width / 2
let toUseConstant = (CGFloat(abs(offSet)) / frameWidth)
if activeCurrentPage == 1 {
if offSet <= 0 {
firstImageHeightAnchor?.constant = 0
firstImageWidthAnchor?.constant = 0
firstImageView.isHidden = false
secondImageView.isHidden = true
} else {
firstImageHeightAnchor?.constant += -(toUseConstant)
firstImageWidthAnchor?.constant += -(toUseConstant)
firstImageView.isHidden = false
secondImageHeightAnchor?.constant += -(toUseConstant)
secondImageWidthAnchor?.constant += -(toUseConstant)
secondImageView.isHidden = false
secondImageView.alpha = toUseConstant
}
}
UIView.animate(withDuration: 0.5) {
self.layoutSubviews()
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.activeCurrentPage = scrollView.currentPage
}
}
这是我能够实现的结果:
我怎样才能从 A 过渡到 B 而没有任何有趣的行为。
谢谢
您可能要考虑为此使用框架,而不是重新发明轮子。例如 Hero。
有一些基本的 examples 可以帮助您入门。从本质上讲,这为您提供了一个易于使用的选项,通过定义每个 UI-Component 应该如何根据过渡进度进行行为来编码两个 UIViewController 之间的过渡行为。
我在ImageView的帧上使用关键帧动画实现了它。但是您也可以在尝试时使用基于约束的动画来实现它。只需根据您的方便用约束常量的更改替换我在这里所做的帧大小变化。
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var imgView1: UIImageView! //HANDSHAKE image on TOP
@IBOutlet weak var imgView2: UIImageView! //CREDIT_CARD and PHONE image
@IBOutlet weak var imgView3: UIImageView! //SINGLE_HAND image
let minimumFrameSize = CGSize(width: 0.0, height: 0.0)
let meetingPointFrameSize = CGSize(width: 150.0, height: 150.0)
let maximumFrameSize = CGSize(width: 400.0, height: 400.0)
override func viewDidLoad() {
super.viewDidLoad()
let centerOfImages = CGPoint(x: self.view.frame.width/2 , y: self.view.frame.height/2)
//initial state
self.imgView1.frame.size = maximumFrameSize
self.imgView1.alpha = 1.0
self.imgView2.frame.size = minimumFrameSize
self.imgView2.alpha = 0.0
self.imgView3.frame.size = minimumFrameSize
self.imgView3.alpha = 0.0
self.imgView1.center = centerOfImages
self.imgView2.center = centerOfImages
self.imgView3.center = centerOfImages
UIView.animateKeyframes(withDuration: 5.0, delay: 2.0, options: [UIView.KeyframeAnimationOptions.repeat,
UIView.KeyframeAnimationOptions.calculationModeLinear],
animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: {
self.imgView1.frame.size = self.meetingPointFrameSize
self.imgView1.alpha = 0.3
self.imgView1.center = centerOfImages
self.imgView2.frame.size = self.meetingPointFrameSize
self.imgView2.alpha = 0.3
self.imgView2.center = centerOfImages
/*image 1 and image 2 meets at certain point where
image 1 is decreasing its size and image 2 is increasing its size simultaneously
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25, animations: {
self.imgView1.frame.size = self.minimumFrameSize
self.imgView1.alpha = 0.0
self.imgView1.center = centerOfImages
self.imgView2.frame.size = self.maximumFrameSize
self.imgView2.alpha = 1.0
self.imgView2.center = centerOfImages
/* image 1 has decreased its size to zero and
image 2 has increased its size to maximum simultaneously
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.4, relativeDuration: 0.1, animations: {
self.imgView2.frame.size = self.maximumFrameSize
self.imgView2.alpha = 1.0
self.imgView2.center = centerOfImages
/* Hold for a moment
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.6, relativeDuration: 0.15, animations: {
self.imgView2.frame.size = self.meetingPointFrameSize
self.imgView2.alpha = 0.3
self.imgView2.center = centerOfImages
self.imgView3.frame.size = self.meetingPointFrameSize
self.imgView3.alpha = 0.3
self.imgView3.center = centerOfImages
/*image 2 and image 3 meets at certain point where
image 2 is decreasing its size and image 3 is increasing its size simultaneously
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25, animations: {
self.imgView2.frame.size = self.minimumFrameSize
self.imgView2.alpha = 0.0
self.imgView2.center = centerOfImages
self.imgView3.frame.size = self.maximumFrameSize
self.imgView3.alpha = 1.0
self.imgView3.center = centerOfImages
/* image 2 has decreased its size to zero and
image 3 has increased its size to maximum simultaneously
*/
})
}) { (finished) in
/*If you have any doubt or need more enhancement or in case you need my project. Feel free to ask me.
Please ping me on skype/email:
ojhashubham29@gmail.com
or connect me on Twitter
@hearthackman
*/
}
}
}
实现流畅行为的最简单方法是使用 UIViewPropertyAnimator
。
为每个图像视图设置初始值:
override func viewDidLoad() {
super.viewDidLoad()
firstImageView.image = // your image
secondImageView.image = // your image
secondImageView.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
secondImageView.alpha = 0
}
然后为每个 imageView
创建 属性 animator
lazy var firstAnimator: UIViewPropertyAnimator = {
// You can play around with the duration, curve, damping ration, timing
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeIn, animations: {
self.firstImageView.image.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
self.firstImageView.image.alpha = 0
})
return animator
}()
和第二个
lazy var secondAnimator: UIViewPropertyAnimator = {
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeIn, animations: {
self.secondImageView.transform = CGAffineTransform(scaleX: 1, y: 1)
self.secondImageView.alpha = 1
})
return animator
}()
现在 scrollViewDidScroll
当您计算完成百分比时,只需更新动画师:
firstAnimator.fractionComplete = calculatedPosition
secondAnimator.fractionComplete = calculatedPosition
您可以对多个视图应用相同的方法
我试图在用户滚动分页视图时执行过渡动画,即:从 第 1 页 到 第 2 页 .
不幸的是,我无法复制它。
下面附上我做的:
class OnboardingParallaxImageView: BaseUIView, UIScrollViewDelegate {
let allImages = [#imageLiteral(resourceName: "onboarding_handshake_icon"), #imageLiteral(resourceName: "onboarding_paylinc_icon")]
var activeCurrentPage = 1
let bgView: UIImageView = {
let image = #imageLiteral(resourceName: "onboard_bg_gradient")
let view = UIImageView(image: image)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let firstImageView: UIImageView = {
let view = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let secondImageView: UIImageView = {
let view = UIImageView()
view.isHidden = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var firstImageHeightAnchor: NSLayoutConstraint?
var firstImageWidthAnchor: NSLayoutConstraint?
var secondImageHeightAnchor: NSLayoutConstraint?
var secondImageWidthAnchor: NSLayoutConstraint?
override func setupViews() {
super.setupViews()
addSubview(bgView)
addSubview(firstImageView)
addSubview(secondImageView)
bgView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
bgView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
bgView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
bgView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
firstImageWidthAnchor = firstImageView.widthAnchor.constraint(equalTo: widthAnchor)
firstImageWidthAnchor?.isActive = true
firstImageHeightAnchor = firstImageView.heightAnchor.constraint(equalTo: heightAnchor)
firstImageHeightAnchor?.isActive = true
firstImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
firstImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
firstImageView.image = allImages[0]
secondImageWidthAnchor = secondImageView.widthAnchor.constraint(equalTo: widthAnchor)
secondImageWidthAnchor?.isActive = true
secondImageHeightAnchor = secondImageView.heightAnchor.constraint(equalTo: heightAnchor)
secondImageHeightAnchor?.isActive = true
secondImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
secondImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
secondImageView.image = allImages[1]
}
override func layoutSubviews() {
super.layoutSubviews()
let frameWidth = frame.size.width
secondImageHeightAnchor?.constant = -(frameWidth - 32)
secondImageWidthAnchor?.constant = -(frameWidth - 32)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offSet = scrollView.contentOffset.x
let frameWidth = frame.size.width / 2
let toUseConstant = (CGFloat(abs(offSet)) / frameWidth)
if activeCurrentPage == 1 {
if offSet <= 0 {
firstImageHeightAnchor?.constant = 0
firstImageWidthAnchor?.constant = 0
firstImageView.isHidden = false
secondImageView.isHidden = true
} else {
firstImageHeightAnchor?.constant += -(toUseConstant)
firstImageWidthAnchor?.constant += -(toUseConstant)
firstImageView.isHidden = false
secondImageHeightAnchor?.constant += -(toUseConstant)
secondImageWidthAnchor?.constant += -(toUseConstant)
secondImageView.isHidden = false
secondImageView.alpha = toUseConstant
}
}
UIView.animate(withDuration: 0.5) {
self.layoutSubviews()
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.activeCurrentPage = scrollView.currentPage
}
}
这是我能够实现的结果:
我怎样才能从 A 过渡到 B 而没有任何有趣的行为。
谢谢
您可能要考虑为此使用框架,而不是重新发明轮子。例如 Hero。
有一些基本的 examples 可以帮助您入门。从本质上讲,这为您提供了一个易于使用的选项,通过定义每个 UI-Component 应该如何根据过渡进度进行行为来编码两个 UIViewController 之间的过渡行为。
我在ImageView的帧上使用关键帧动画实现了它。但是您也可以在尝试时使用基于约束的动画来实现它。只需根据您的方便用约束常量的更改替换我在这里所做的帧大小变化。
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var imgView1: UIImageView! //HANDSHAKE image on TOP
@IBOutlet weak var imgView2: UIImageView! //CREDIT_CARD and PHONE image
@IBOutlet weak var imgView3: UIImageView! //SINGLE_HAND image
let minimumFrameSize = CGSize(width: 0.0, height: 0.0)
let meetingPointFrameSize = CGSize(width: 150.0, height: 150.0)
let maximumFrameSize = CGSize(width: 400.0, height: 400.0)
override func viewDidLoad() {
super.viewDidLoad()
let centerOfImages = CGPoint(x: self.view.frame.width/2 , y: self.view.frame.height/2)
//initial state
self.imgView1.frame.size = maximumFrameSize
self.imgView1.alpha = 1.0
self.imgView2.frame.size = minimumFrameSize
self.imgView2.alpha = 0.0
self.imgView3.frame.size = minimumFrameSize
self.imgView3.alpha = 0.0
self.imgView1.center = centerOfImages
self.imgView2.center = centerOfImages
self.imgView3.center = centerOfImages
UIView.animateKeyframes(withDuration: 5.0, delay: 2.0, options: [UIView.KeyframeAnimationOptions.repeat,
UIView.KeyframeAnimationOptions.calculationModeLinear],
animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: {
self.imgView1.frame.size = self.meetingPointFrameSize
self.imgView1.alpha = 0.3
self.imgView1.center = centerOfImages
self.imgView2.frame.size = self.meetingPointFrameSize
self.imgView2.alpha = 0.3
self.imgView2.center = centerOfImages
/*image 1 and image 2 meets at certain point where
image 1 is decreasing its size and image 2 is increasing its size simultaneously
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25, animations: {
self.imgView1.frame.size = self.minimumFrameSize
self.imgView1.alpha = 0.0
self.imgView1.center = centerOfImages
self.imgView2.frame.size = self.maximumFrameSize
self.imgView2.alpha = 1.0
self.imgView2.center = centerOfImages
/* image 1 has decreased its size to zero and
image 2 has increased its size to maximum simultaneously
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.4, relativeDuration: 0.1, animations: {
self.imgView2.frame.size = self.maximumFrameSize
self.imgView2.alpha = 1.0
self.imgView2.center = centerOfImages
/* Hold for a moment
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.6, relativeDuration: 0.15, animations: {
self.imgView2.frame.size = self.meetingPointFrameSize
self.imgView2.alpha = 0.3
self.imgView2.center = centerOfImages
self.imgView3.frame.size = self.meetingPointFrameSize
self.imgView3.alpha = 0.3
self.imgView3.center = centerOfImages
/*image 2 and image 3 meets at certain point where
image 2 is decreasing its size and image 3 is increasing its size simultaneously
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25, animations: {
self.imgView2.frame.size = self.minimumFrameSize
self.imgView2.alpha = 0.0
self.imgView2.center = centerOfImages
self.imgView3.frame.size = self.maximumFrameSize
self.imgView3.alpha = 1.0
self.imgView3.center = centerOfImages
/* image 2 has decreased its size to zero and
image 3 has increased its size to maximum simultaneously
*/
})
}) { (finished) in
/*If you have any doubt or need more enhancement or in case you need my project. Feel free to ask me.
Please ping me on skype/email:
ojhashubham29@gmail.com
or connect me on Twitter
@hearthackman
*/
}
}
}
实现流畅行为的最简单方法是使用 UIViewPropertyAnimator
。
为每个图像视图设置初始值:
override func viewDidLoad() {
super.viewDidLoad()
firstImageView.image = // your image
secondImageView.image = // your image
secondImageView.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
secondImageView.alpha = 0
}
然后为每个 imageView
创建 属性 animatorlazy var firstAnimator: UIViewPropertyAnimator = {
// You can play around with the duration, curve, damping ration, timing
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeIn, animations: {
self.firstImageView.image.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
self.firstImageView.image.alpha = 0
})
return animator
}()
和第二个
lazy var secondAnimator: UIViewPropertyAnimator = {
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeIn, animations: {
self.secondImageView.transform = CGAffineTransform(scaleX: 1, y: 1)
self.secondImageView.alpha = 1
})
return animator
}()
现在 scrollViewDidScroll
当您计算完成百分比时,只需更新动画师:
firstAnimator.fractionComplete = calculatedPosition
secondAnimator.fractionComplete = calculatedPosition
您可以对多个视图应用相同的方法