将 UIView.layer.clipsToBounds = true 与 CABasicAnimation 结合使用时,并非所有像素都被裁剪
Posted
技术标签:
【中文标题】将 UIView.layer.clipsToBounds = true 与 CABasicAnimation 结合使用时,并非所有像素都被裁剪【英文标题】:Not all pixels clipped when using UIView.layer.clipsToBounds = true in conjunction with CABasicAnimation 【发布时间】:2019-10-18 16:55:59 【问题描述】:我在做什么:
我正在创建一个音乐应用程序。在这个音乐应用程序中,我有一个与 Apple Music 和 Spotify 链接的音乐播放器。音乐播放器在UIImageView
上显示当前歌曲的专辑封面。每当播放歌曲时,UIImageView
就会旋转(就像唱机上的唱片一样)。
我的问题是:
UIImageView
的专辑封面默认为方形。我将UIImageView
的layer
的角四舍五入并将UIImageView.clipsToBounds
设置为等于true
以使其显示为一个圆圈。
每当UIImageView
旋转时,UIImageView
的layer
之外的一些像素(对图像进行四舍五入后被切断的部分)就会渗出。
这是错误的样子: https://www.youtube.com/watch?v=OJxX5PQc7Jo&feature=youtu.be
我的代码:
UIImageView
通过将其layer
的cornerRadius
设置为等于UIImageView.frame.height / 2
并设置UIImageView.clipsToBounds = true
来进行舍入:
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)
【问题讨论】:
【参考方案1】:我将您的代码复制到项目中,我可以重现此问题。但是,如果您将动画添加到另一个 CALayer,这似乎可以解决问题。
extension UIView
static var kRotationAnimationKey: String
return "kRotationAnimationKey"
func makeAnimationLayer() -> CALayer
let results: [CALayer]? = layer.sublayers?.filter( $0.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
【讨论】:
这非常令人鼓舞。让我尝试实现它并回复您! 完美运行。所以让我等了两个小时才给你赏金,所以你今天晚些时候会得到它:)【参考方案2】:剪辑到边界不包括圆角。尝试改用角落遮罩。
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)
【讨论】:
我通过albumArtImageView.layer.cornerRadius = albumArtImageView.frame.height / 2
、albumArtImageView.layer.masksToBounds = true
、albumArtImageView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner,.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
实现了这一点,不幸的是,我得到了同样的行为。以上是关于将 UIView.layer.clipsToBounds = true 与 CABasicAnimation 结合使用时,并非所有像素都被裁剪的主要内容,如果未能解决你的问题,请参考以下文章
Javascript 将正则表达式 \\n 替换为 \n,将 \\t 替换为 \t,将 \\r 替换为 \r 等等