当 UIView.layer.clipsToBounds = true 与 CABasicAnimation 结合使用时,并非所有像素都被剪裁
Not all pixels clipped when using UIView.layer.clipsToBounds = true in conjunction with CABasicAnimation
我在做什么:
我正在创建一个音乐应用程序。在这个音乐应用程序中,我有一个与 Apple Music 和 Spotify 链接的音乐播放器。音乐播放器在 UIImageView
上显示当前歌曲的专辑封面。每当播放歌曲时,UIImageView
都会旋转(就像唱机上的唱片一样)。
我的问题是:
UIImageView
的专辑封面默认为正方形。我将 UIImageView
的 layer
的角四舍五入并将 UIImageView.clipsToBounds
设置为等于 true
以使其显示为一个圆圈。
每当 UIImageView
旋转时,UIImageView
的 layer
之外的一些像素(图像四舍五入后被切除的部分)会渗出。
漏洞如下所示: https://www.youtube.com/watch?v=OJxX5PQc7Jo&feature=youtu.be
我的代码:
通过将 layer
的 cornerRadius
设置为 UIImageView.frame.height / 2
并设置 UIImageView.clipsToBounds = true
:
UIImageView
class MyViewController: UIViewController {
@IBOutlet var albumArtImageView: UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
albumArtImageView.layer.cornerRadius = albumArtImageView.frame.height / 2
albumArtImageView.clipsToBounds = true
//I've also tried the following code, and am getting the same behavior:
/*
albumArtImageView.layer.cornerRadius = albumArtImageView.frame.height / 2
albumArtImageView.layer.masksToBounds = true
albumArtImageView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner,.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
*/
}
}
只要按下一个按钮,UIImageView
就会开始旋转。我使用了以下 extension
来进行 UIView
的旋转:
extension UIView {
func rotate(duration: Double = 1, startPoint: CGFloat) {
if layer.animation(forKey: UIView.kRotationAnimationKey) == nil {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = startPoint
rotationAnimation.toValue = (CGFloat.pi * 2.0) + startPoint
rotationAnimation.duration = duration
rotationAnimation.repeatCount = Float.infinity
layer.add(rotationAnimation, forKey: UIView.kRotationAnimationKey)
}
}
}
我还有以下扩展来结束轮换:
extension UIView {
...
func stopRotating(beginTime: Double!, startingAngle: CGFloat) -> CGFloat? {
if layer.animation(forKey: UIView.kRotationAnimationKey) != nil {
let animation = layer.animation(forKey: UIView.kRotationAnimationKey)!
let elapsedTime = CACurrentMediaTime() - beginTime
let angle = elapsedTime.truncatingRemainder(dividingBy: animation.duration)/animation.duration
layer.transform = CATransform3DMakeRotation((CGFloat(angle) * (2 * CGFloat.pi)) + startingAngle, 0.0, 0.0, 1.0)
layer.removeAnimation(forKey: UIView.kRotationAnimationKey)
return (CGFloat(angle) * (2 * CGFloat.pi)) + startingAngle
} else {
return nil
}
}
}
这是在我的视图控制器上下文中使用这些扩展函数的方式:
class MyViewController: UIViewController {
var songBeginTime: Double!
var currentRecordAngle: CGFloat = 0.0
var isPlaying = false
...
@IBAction func playButtonPressed(_ sender: Any) {
if isPlaying {
if let angle = albumArtImageView.stopRotating(beginTime: songBeginTime, startingAngle: currentRecordAngle) {
currentRecordAngle = angle
}
songBeginTime = nil
} else {
songBeginTime = CACurrentMediaTime()
albumArtImageView.rotate(duration: 3, startPoint: currentRecordAngle)
}
}
}
所以,总的来说,MyViewController
看起来像这样:
class MyViewController: UIViewController {
@IBOutlet var albumArtImageView: UIImageView()
var songBeginTime: Double!
var currentRecordAngle: CGFloat = 0.0
var isPlaying = false
override func viewDidLoad() {
super.viewDidLoad()
albumArtImageView.layer.cornerRadius = albumArtImageView.frame.height / 2
albumArtImageView.clipsToBounds = true
}
@IBAction func playButtonPressed(_ sender: Any) {
if isPlaying {
if let angle = albumArtImageView.stopRotating(beginTime: songBeginTime, startingAngle: currentRecordAngle) {
currentRecordAngle = angle
}
songBeginTime = nil
} else {
songBeginTime = CACurrentMediaTime()
albumArtImageView.rotate(duration: 3, startPoint: currentRecordAngle)
}
}
}
剪辑到边界不包括圆角。尝试改用角遮罩。
class MyViewController: UIViewController {
@IBOutlet var albumArtImageView: UIImageView()
var songBeginTime: Double!
var currentRecordAngle: CGFloat = 0.0
var isPlaying = false
override func viewDidLoad() {
super.viewDidLoad()
albumArtImageView.layer.cornerRadius = albumArtImageView.frame.height / 2
albumArtImageView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner,.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
}
@IBAction func playButtonPressed(_ sender: Any) {
if isPlaying {
if let angle = albumArtImageView.stopRotating(beginTime: songBeginTime, startingAngle: currentRecordAngle) {
currentRecordAngle = angle
}
songBeginTime = nil
} else {
songBeginTime = CACurrentMediaTime()
albumArtImageView.rotate(duration: 3, startPoint: currentRecordAngle)
}
}
}
我将你的代码复制到项目中,我可以重现这个问题。但是,如果将动画添加到另一个 CALayer,这似乎可以解决问题。
extension UIView {
static var kRotationAnimationKey: String {
return "kRotationAnimationKey"
}
func makeAnimationLayer() -> CALayer {
let results: [CALayer]? = layer.sublayers?.filter({ [=10=].name ?? "" == "animationLayer" })
let animLayer: CALayer
if let sublayers = results, sublayers.count > 0 {
animLayer = sublayers[0]
}
else {
animLayer = CAShapeLayer()
animLayer.name = "animationLayer"
animLayer.frame = self.bounds
animLayer.contents = UIImage(named: "imageNam")?.cgImage
layer.addSublayer(animLayer)
}
return animLayer
}
func rotate(duration: Double = 1, startPoint: CGFloat) {
if layer.animation(forKey: UIView.kRotationAnimationKey) == nil {
let animLayer = makeAnimationLayer()
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = startPoint
rotationAnimation.toValue = (CGFloat.pi * 2.0) + startPoint
rotationAnimation.duration = duration
rotationAnimation.repeatCount = Float.infinity
animLayer.add(rotationAnimation, forKey: UIView.kRotationAnimationKey)
}
}
func stopRotating(beginTime: Double!, startingAngle: CGFloat) -> CGFloat? {
let animLayer = makeAnimationLayer()
if animLayer.animation(forKey: UIView.kRotationAnimationKey) != nil {
let animation = animLayer.animation(forKey: UIView.kRotationAnimationKey)!
let elapsedTime = CACurrentMediaTime() - beginTime
let angle = elapsedTime.truncatingRemainder(dividingBy: animation.duration)/animation.duration
animLayer.transform = CATransform3DMakeRotation(CGFloat(angle) * (2 * CGFloat.pi) + startingAngle, 0.0, 0.0, 1.0)
animLayer.removeAnimation(forKey: UIView.kRotationAnimationKey)
return (CGFloat(angle) * (2 * CGFloat.pi)) + startingAngle
}
else {
return nil
}
}