You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:11:07