使用CGAffineTransform动画时UIView圆角被挤压,如何维持完美圆角?
嘿,这个问题我太熟悉了!之前做卡片缩放动画时也踩过这个坑——用CGAffineTransform做缩放时,圆角跟着“挤变形”,本质原因是仿射变换会直接拉伸view的layer,而你设置的cornerRadius是基于原始尺寸的固定值,当view的宽高比被缩放改变后,原本的圆角自然就不再是完美的圆形/圆角了。
下面给你几个靠谱的解决方案,按推荐程度排序:
方案一:用CAShapeLayer作为动态Mask(最可靠)
这种方法不管是均匀还是非均匀缩放,都能保证圆角始终完美,因为我们用一个动态更新的形状图层作为view的遮罩,完全替代原生的cornerRadius实现:
// 1. 初始化遮罩层 let maskLayer = CAShapeLayer() yourView.layer.mask = maskLayer // 2. 定义更新遮罩路径的方法 @objc private func updateMaskPath() { // 获取当前view经过变换后的实际显示范围 let transformedBounds = yourView.bounds.applying(yourView.transform) // 绘制完美的圆角路径(取宽高中较小值的一半作为圆角半径) let path = UIBezierPath(roundedRect: transformedBounds, cornerRadius: min(transformedBounds.width, transformedBounds.height) / 2) maskLayer.path = path.cgPath } // 3. 初始调用一次,设置初始遮罩 updateMaskPath() // 4. 开始缩放动画 UIView.animate(withDuration: 0.5, animations: { yourView.transform = CGAffineTransform(scaleX: 0.8, y: 1.2) }, completion: { _ in // 动画结束后停止监听 self.displayLink?.invalidate() }) // 5. 添加CADisplayLink,实时更新遮罩路径 let displayLink = CADisplayLink(target: self, selector: #selector(updateMaskPath)) displayLink.add(to: .current, forMode: .common) self.displayLink = displayLink
如果想要更平滑的动画效果,也可以给遮罩层的path属性单独加CABasicAnimation,和view的缩放动画同步执行,避免每一帧更新的微小卡顿。
方案二:使用Layer级动画 + 圆角曲线优化(iOS 13+)
如果你不想用遮罩,也可以直接操作layer的transform,同时开启cornerCurve = .continuous(iOS 13及以上支持),这个属性能让圆角在动画过程中保持更自然的过渡:
// 1. 初始设置圆角 yourView.layer.cornerRadius = min(yourView.bounds.width, yourView.bounds.height) / 2 yourView.layer.masksToBounds = true yourView.layer.cornerCurve = .continuous // 关键:让圆角动画更平滑 // 2. 创建Layer级缩放动画 let scaleAnimation = CABasicAnimation(keyPath: "transform") scaleAnimation.fromValue = CATransform3DIdentity scaleAnimation.toValue = CATransform3DMakeScale(0.8, 1.2, 1.0) scaleAnimation.duration = 0.5 scaleAnimation.fillMode = .forwards scaleAnimation.isRemovedOnCompletion = false // 3. 添加动画到layer yourView.layer.add(scaleAnimation, forKey: "scaleAnimation")
注意:如果是非均匀缩放(x和y缩放比例不同),这个方法可能还是会有轻微的圆角变形,这时候方案一依然是最优解。
方案三:动态更新cornerRadius(适合简单场景)
如果你的动画逻辑比较简单,可以用CADisplayLink监听每一帧,实时更新cornerRadius为当前view显示尺寸的一半:
// 初始圆角设置 yourView.layer.cornerRadius = min(yourView.bounds.width, yourView.bounds.height) / 2 yourView.layer.masksToBounds = true // 开始缩放动画 UIView.animate(withDuration: 0.5, completion: { _ in self.displayLink?.invalidate() }) { yourView.transform = CGAffineTransform(scaleX: 0.8, y: 1.2) } // 实时更新圆角 let displayLink = CADisplayLink(target: self, selector: #selector(updateCornerRadius)) displayLink.add(to: .current, forMode: .common) self.displayLink = displayLink @objc private func updateCornerRadius() { let transformedBounds = yourView.bounds.applying(yourView.transform) yourView.layer.cornerRadius = min(transformedBounds.width, transformedBounds.height) / 2 }
这个方法实现简单,但频繁更新layer属性可能会有轻微的性能损耗,适合小范围的简单动画。
内容的提问来源于stack exchange,提问作者hgwhittle
相关产品推荐
相关产品推荐

