动画 layer.mask 如何变化
How animate layer.mask changes
我正在我的应用程序中创建这个自定义视图,用户可以通过拖动来展开该视图,并具有案例的所有动画。明确一点,我想重现与 IOS 的控制中心几乎相同的动画。到目前为止,我设法获得了几乎所有内容,从手指拖动时视图展开的动画,到使角变圆的可能性。
现在,事实是,除了可以拖动视图之外,我还想在用户在展开之间松开手指时实现一个动画,以使视图返回到其原始高度或完成扩展。为此,我使用了用户停止拖动时视图的位置。
问题是在我尝试为 UIView.layer.mask
设置动画时出现的。通过在互联网上的一些研究,我发现了 CABasicAnimation
class 并在一个函数中实现了它:
func animateExpandableView(withDuration duration: Double) {
CATransaction.begin()
CATransaction.setAnimationDuration(duration)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeInEaseOut))
let maskAnimation = CABasicAnimation(keyPath: "mask")
maskAnimation.fromValue = profileView.layer.mask
let bounds = profileView.frame
let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 8.0, height: 8.0))
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = maskPath.cgPath
maskAnimation.toValue = maskLayer
maskAnimation.duration = duration
profileView.layer.mask = maskLayer
profileView.layer.add(maskAnimation, forKey: "mask")
CATransaction.commit()
}
顺便说一句,这不起作用,而且遮罩的变化不是动画的。
我在实现中哪里出错了?
有人建议我从另一个问题中检查这个 link;我觉得它不是很有用,因为尽管它对我的情况不起作用,但答案是用 C 写的,而不是 Swift.
Animating a CALayer's mask size change
我找到了一个解决方法,可以解决遮罩 属性 无法通过使用计时器重新创建动画来设置动画的问题。基本上,我将 animationDuration
除以 100,并将结果设置为计时器的间隔。然后我用 finalHeight
和 initalHeight
之间的差值除以 100 并将其分配给 animationStep
。通过所有这些设置,我只是 运行 定时器 100 个周期,并且在每个周期中将 animationStep
添加到 maskHeight
属性。这是我的 class:
中的所有代码
class ExpandableView: UIView {
//Timer to animate view's mask
private var animationTimer: Timer? = nil
//the height of each step for the mask's animation
private var animationStep: CGFloat? = nil
private var currentStep: Double = 0
//Variable responsable to return and set self.mask height.
private var maskCornerRadii: CGSize? = nil
private var maskRoundingCorners: UIRectCorner? = nil
//Variable responsable to return and set self.mask height. it's nil if there isn't any mask
private var maskHeight: CGFloat? {
get {return self.layer.mask?.bounds.height}
set {
if newValue != nil && maskRoundingCorners != nil && maskCornerRadii != nil {
let frame = self.bounds
let bounds = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: newValue!)
let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: maskRoundingCorners!, cornerRadii: maskCornerRadii!)
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = maskPath.cgPath
self.layer.mask = maskLayer
}
}
}
//Animation function
func animateMask(from initialHeight: CGFloat, to finalHeight: CGFloat, withDuration duration: Double) {
let timerInterval = duration * 0.01
animationStep = (finalHeight - initialHeight) * 0.01
animationTimer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true, block: { (timer) in
if self.currentStep <= 100 {
guard self.animationStep != nil else {
fatalError("animationStep is nil")
}
guard self.maskHeight != nil else {
fatalError("Mask is nil")
}
self.maskHeight! += self.animationStep!
self.currentStep += 1
} else {
self.animationTimer!.invalidate()
self.currentStep = 0
}
})
}
}
我正在我的应用程序中创建这个自定义视图,用户可以通过拖动来展开该视图,并具有案例的所有动画。明确一点,我想重现与 IOS 的控制中心几乎相同的动画。到目前为止,我设法获得了几乎所有内容,从手指拖动时视图展开的动画,到使角变圆的可能性。
现在,事实是,除了可以拖动视图之外,我还想在用户在展开之间松开手指时实现一个动画,以使视图返回到其原始高度或完成扩展。为此,我使用了用户停止拖动时视图的位置。
问题是在我尝试为 UIView.layer.mask
设置动画时出现的。通过在互联网上的一些研究,我发现了 CABasicAnimation
class 并在一个函数中实现了它:
func animateExpandableView(withDuration duration: Double) {
CATransaction.begin()
CATransaction.setAnimationDuration(duration)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeInEaseOut))
let maskAnimation = CABasicAnimation(keyPath: "mask")
maskAnimation.fromValue = profileView.layer.mask
let bounds = profileView.frame
let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 8.0, height: 8.0))
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = maskPath.cgPath
maskAnimation.toValue = maskLayer
maskAnimation.duration = duration
profileView.layer.mask = maskLayer
profileView.layer.add(maskAnimation, forKey: "mask")
CATransaction.commit()
}
顺便说一句,这不起作用,而且遮罩的变化不是动画的。 我在实现中哪里出错了?
有人建议我从另一个问题中检查这个 link;我觉得它不是很有用,因为尽管它对我的情况不起作用,但答案是用 C 写的,而不是 Swift.
Animating a CALayer's mask size change
我找到了一个解决方法,可以解决遮罩 属性 无法通过使用计时器重新创建动画来设置动画的问题。基本上,我将 animationDuration
除以 100,并将结果设置为计时器的间隔。然后我用 finalHeight
和 initalHeight
之间的差值除以 100 并将其分配给 animationStep
。通过所有这些设置,我只是 运行 定时器 100 个周期,并且在每个周期中将 animationStep
添加到 maskHeight
属性。这是我的 class:
class ExpandableView: UIView {
//Timer to animate view's mask
private var animationTimer: Timer? = nil
//the height of each step for the mask's animation
private var animationStep: CGFloat? = nil
private var currentStep: Double = 0
//Variable responsable to return and set self.mask height.
private var maskCornerRadii: CGSize? = nil
private var maskRoundingCorners: UIRectCorner? = nil
//Variable responsable to return and set self.mask height. it's nil if there isn't any mask
private var maskHeight: CGFloat? {
get {return self.layer.mask?.bounds.height}
set {
if newValue != nil && maskRoundingCorners != nil && maskCornerRadii != nil {
let frame = self.bounds
let bounds = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: newValue!)
let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: maskRoundingCorners!, cornerRadii: maskCornerRadii!)
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = maskPath.cgPath
self.layer.mask = maskLayer
}
}
}
//Animation function
func animateMask(from initialHeight: CGFloat, to finalHeight: CGFloat, withDuration duration: Double) {
let timerInterval = duration * 0.01
animationStep = (finalHeight - initialHeight) * 0.01
animationTimer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true, block: { (timer) in
if self.currentStep <= 100 {
guard self.animationStep != nil else {
fatalError("animationStep is nil")
}
guard self.maskHeight != nil else {
fatalError("Mask is nil")
}
self.maskHeight! += self.animationStep!
self.currentStep += 1
} else {
self.animationTimer!.invalidate()
self.currentStep = 0
}
})
}
}