如何实现带圆角衔接效果的形状过渡动画?
实现矩形衔接处带圆角的过渡动画方案
我需要实现从最左侧形状到最右侧形状的过渡动画,核心难点是中间帧里两个矩形衔接处的圆角效果。
当前我用三个视图实现的代码如下:
class TestRoundedCornerViews: UIView { let leftView = UIView() let middleView = UIView() let rightView = UIView() override init(frame: CGRect) { super.init(frame: frame) let cornerRadius: CGFloat = 16 leftView.layer.cornerRadius = cornerRadius leftView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner] self.addSubview(self.leftView) middleView.layer.cornerRadius = cornerRadius middleView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner] self.addSubview(self.middleView) rightView.layer.cornerRadius = cornerRadius rightView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner] self.addSubview(self.rightView) } // MARK: Layout override func layoutSubviews() { super.layoutSubviews() let leftSideWidth: CGFloat = 100 let leftViewWidth: CGFloat = leftSideWidth / 2 let middleViewWidth: CGFloat = leftViewWidth let rightViewWidth: CGFloat = 40 leftView.frame = CGRect( x: 0, y: 0, width: leftViewWidth, height: self.bounds.height ) middleView.frame = CGRect( x: leftView.frame.maxX, y: 0, width: middleViewWidth, height: self.bounds.height ) rightView.frame = CGRect( x: middleView.frame.maxX, y: 0, width: rightViewWidth, height: self.bounds.height ) } required init?(coder: NSCoder) { fatalError() } }
目前的实现效果里,视图衔接处没有圆角,而且不知道怎么完成到最终形状的过渡动画。
解决方案:用CAShapeLayer实现路径动画
不用三个独立视图,改用CAShapeLayer绘制完整路径,通过动画路径的变化来实现过渡,这样能精准控制衔接处的圆角效果。
具体实现代码
class RoundedTransitionView: UIView { private let shapeLayer = CAShapeLayer() private let cornerRadius: CGFloat = 16 // 0对应初始态,1对应最终态,控制动画进度 private var transitionProgress: CGFloat = 0 { didSet { updateShapePath() } } override init(frame: CGRect) { super.init(frame: frame) setupBaseLayer() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupBaseLayer() { shapeLayer.fillColor = UIColor.systemBlue.cgColor // 可根据需求修改颜色 layer.addSublayer(shapeLayer) updateShapePath() } override func layoutSubviews() { super.layoutSubviews() updateShapePath() } // 根据进度动态生成路径 private func updateShapePath() { let path = UIBezierPath() let totalHeight = bounds.height // 初始态与最终态的宽度参数,可根据你的实际需求调整 let initialLeftWidth: CGFloat = 100 let finalLeftWidth: CGFloat = 60 let rightBarWidth: CGFloat = 40 // 计算当前状态下的各部分宽度和位置 let currentLeftWidth = initialLeftWidth - (initialLeftWidth - finalLeftWidth) * transitionProgress let rightBarX = currentLeftWidth + (initialLeftWidth - currentLeftWidth) * transitionProgress // 绘制左侧矩形的左上角圆角 path.move(to: CGPoint(x: cornerRadius, y: 0)) path.addArc(withCenter: CGPoint(x: cornerRadius, y: cornerRadius), radius: cornerRadius, startAngle: .pi * 1.5, endAngle: .pi, clockwise: false) // 左侧矩形的左下角圆角 path.addLine(to: CGPoint(x: 0, y: totalHeight - cornerRadius)) path.addArc(withCenter: CGPoint(x: cornerRadius, y: totalHeight - cornerRadius), radius: cornerRadius, startAngle: .pi, endAngle: .pi * 0.5, clockwise: false) // 左侧底部到衔接处的圆角 path.addLine(to: CGPoint(x: currentLeftWidth, y: totalHeight)) let bottomCornerCenterX = currentLeftWidth + cornerRadius * transitionProgress path.addArc(withCenter: CGPoint(x: bottomCornerCenterX, y: totalHeight - cornerRadius), radius: cornerRadius, startAngle: .pi * 0.5, endAngle: 0, clockwise: false) // 衔接处到右侧条的底部 path.addLine(to: CGPoint(x: rightBarX + rightBarWidth, y: totalHeight)) // 右侧条的右下角圆角 path.addArc(withCenter: CGPoint(x: rightBarX + rightBarWidth - cornerRadius, y: totalHeight - cornerRadius), radius: cornerRadius, startAngle: 0, endAngle: .pi * 0.5, clockwise: true) // 右侧条的右上角圆角 path.addLine(to: CGPoint(x: rightBarX + rightBarWidth, y: cornerRadius)) path.addArc(withCenter: CGPoint(x: rightBarX + rightBarWidth - cornerRadius, y: cornerRadius), radius: cornerRadius, startAngle: .pi * 0.5, endAngle: .pi * 1.5, clockwise: true) // 右侧条顶部到衔接处的圆角 path.addLine(to: CGPoint(x: rightBarX, y: 0)) let topCornerCenterX = currentLeftWidth + cornerRadius * (1 - transitionProgress) path.addArc(withCenter: CGPoint(x: topCornerCenterX, y: cornerRadius), radius: cornerRadius, startAngle: .pi * 1.5, endAngle: .pi, clockwise: false) // 回到起点闭合路径 path.addLine(to: CGPoint(x: cornerRadius, y: 0)) shapeLayer.path = path.cgPath } // 调用这个方法启动过渡动画 func startTransition() { let animation = CABasicAnimation(keyPath: "transitionProgress") animation.fromValue = 0 animation.toValue = 1 animation.duration = 1.5 // 动画时长可调整 animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) animation.fillMode = .forwards animation.isRemovedOnCompletion = false self.layer.add(animation, forKey: "shapeTransition") // 同步更新属性,确保动画结束后状态正确 transitionProgress = 1 } }
关键说明
- 用单个
CAShapeLayer替代多视图方案,通过动态生成路径实现衔接处的圆角过渡,动画过程中路径平滑变化,不会出现生硬的拼接问题。 transitionProgress变量控制动画进度,从0到1对应初始态到最终态,你可以根据实际需求调整初始和最终的宽度参数。- 衔接处的圆角通过动态计算圆心位置实现,随着进度变化,圆心逐步移动,让圆角自然过渡消失或出现。
内容的提问来源于stack exchange,提问作者Miles
相关产品推荐
相关产品推荐

