圆形到矩形的变换动画

Posted

技术标签:

【中文标题】圆形到矩形的变换动画【英文标题】:Circle to rectangle transformation animation 【发布时间】:2016-01-27 08:41:13 【问题描述】:

我对 ios 很陌生,我需要做以下动画:

圆形到矩形的转换应该是平滑的,但在上面的动画中它不是很平滑。

我所做的是使用以下代码 in this tutorial 创建一个圆形和一个矩形:

  Circle : 
        class OvalLayer: CAShapeLayer 

        let animationDuration: CFTimeInterval = 0.3

        override init() 
            super.init()
            fillColor = Colors.red.CGColor
            path = ovalPathSmall.CGPath
        

        required init?(coder aDecoder: NSCoder) 
            fatalError("init(coder:) has not been implemented")
        

        var ovalPathSmall: UIBezierPath 
            return UIBezierPath(ovalInRect: CGRect(x: 50.0, y: 50.0, width: 0.0, height: 0.0))
        

        var ovalPathLarge: UIBezierPath 
            return UIBezierPath(ovalInRect: CGRect(x: 2.5, y: 17.5, width: 95.0, height: 95.0))
        

        var ovalPathSquishVertical: UIBezierPath 
            return UIBezierPath(ovalInRect: CGRect(x: 2.5, y: 20.0, width: 95.0, height: 90.0))
        

        var ovalPathSquishHorizontal: UIBezierPath 
            return UIBezierPath(ovalInRect: CGRect(x: 5.0, y: 20.0, width: 90.0, height: 90.0))
        

        func expand() 
            let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
            expandAnimation.fromValue = ovalPathLarge.CGPath// change ovalPathLarge to ovalPathSmail for animation
            expandAnimation.toValue = ovalPathLarge.CGPath
            expandAnimation.duration = animationDuration
            expandAnimation.fillMode = kCAFillModeForwards
            expandAnimation.removedOnCompletion = false
            addAnimation(expandAnimation, forKey: nil)
        

    

Rectangle : 

    class RectangleLayer: CAShapeLayer 


    override init() 
        super.init()
        fillColor = Colors.clear.CGColor
        lineWidth = 5.0
        path = rectanglePathFull.CGPath
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    var rectanglePathFull: UIBezierPath 
        let rectanglePath = UIBezierPath()
        rectanglePath.moveToPoint(CGPoint(x: 0.0, y: 100.0))
        rectanglePath.addLineToPoint(CGPoint(x: 0.0, y: -lineWidth))
        rectanglePath.addLineToPoint(CGPoint(x: 100.0, y: -lineWidth))
        rectanglePath.addLineToPoint(CGPoint(x: 100.0, y: 100.0))
        rectanglePath.addLineToPoint(CGPoint(x: -lineWidth / 2, y: 100.0))
        rectanglePath.closePath()

//        fillColor = Colors.red.CGColor
        return rectanglePath
    

//    var topLeft: UIBezierPath 

    func animateStrokeWithColor(color: UIColor, view : UIView) 
        strokeColor = color.CGColor

//        CATransaction.setDisableActions(true)
//        view.layer.bounds.size.height = view.layer.bounds.width + 50

        let strokeAnimation: CABasicAnimation = CABasicAnimation(keyPath: "bounds.size.width") //bounds.size.width
        strokeAnimation.fromValue = view.layer.bounds.width
        strokeAnimation.toValue = view.layer.bounds.size.width - 50
        strokeAnimation.duration = 0.4
        addAnimation(strokeAnimation, forKey: nil)
    


my view : 

    protocol HolderViewDelegate:class 
    func animateLabel()


class HolderView: UIView 

    let ovalLayer = OvalLayer()
    let redRectangleLayer = RectangleLayer()

    var parentFrame :CGRect = CGRectZero
    weak var delegate:HolderViewDelegate?

    override init(frame: CGRect) 
        super.init(frame: frame)
        backgroundColor = Colors.clear
    

    required init?(coder: NSCoder) 
        super.init(coder: coder)
    

    func addOval() 
        layer.addSublayer(ovalLayer)
        ovalLayer.expand()
//        NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: "wobbleOval",
//            userInfo: nil, repeats: false)
    

    func expandRectangle()
        NSTimer.scheduledTimerWithTimeInterval(0.45, target: self,
            selector: "drawRedAnimatedRectangle",
            userInfo: nil, repeats: false)
    

    func drawRedAnimatedRectangle() 
        layer.addSublayer(redRectangleLayer)
        redRectangleLayer.animateStrokeWithColor(Colors.red,view: self)
    

但我不知道如何制作我的动画,请谁能帮助我?

【问题讨论】:

【参考方案1】:

如果您希望放大和缩小圆角半径同时发生,您可以显着简化the code from my other answer

您现在不再需要将动画“链接”在一起,因此您可以将它们都添加到单个 CAAnimationGroup 并同时运行它们。

除了添加groupAnim 属性和删除cornerRadiusUndoAnim 之外,我们使用的属性几乎保持不变。

class ViewController2: UIViewController 

    let animLayer = CALayer() // the layer that is going to be animated
    let cornerRadiusAnim = CABasicAnimation(keyPath: "cornerRadius") // the corner radius reducing animation
    let widthAnim = CABasicAnimation(keyPath: "bounds.size.width") // the width animation
    let groupAnim = CAAnimationGroup() // the combination of the corner and width animation
    let animDuration = NSTimeInterval(1.0) // the duration of one 'segment' of the animation
    let layerSize = CGFloat(100) // the width & height of the layer (when it's a square)

    ...        

我们现在可以添加 CAAnimationGroup 的设置,添加我们的圆角半径动画和缩放动画

override func viewDidLoad() 
    super.viewDidLoad()

    let rect = view.frame

    animLayer.backgroundColor = UIColor.blueColor().CGColor // color of the layer, feel free to change
    animLayer.frame = CGRect(x: rect.width-layerSize*0.5, y: rect.height-layerSize*0.5, width: layerSize, height: layerSize)
    animLayer.cornerRadius = layerSize*0.5;
    animLayer.anchorPoint = CGPoint(x: 1, y: 1) // sets so that when the width is changed, it goes to the left
    view.layer.addSublayer(animLayer)

    // decreases the corner radius
    cornerRadiusAnim.duration = animDuration
    cornerRadiusAnim.fromValue = animLayer.cornerRadius
    cornerRadiusAnim.toValue = 0;
    cornerRadiusAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) // timing function to make it look nice

    // increases the width
    widthAnim.duration = animDuration
    widthAnim.fromValue = animLayer.frame.size.width
    widthAnim.toValue = rect.size.width
    widthAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) // timing function to make it look nice

    // adds both animations to a group animation
    groupAnim.animations = [cornerRadiusAnim, widthAnim]
    groupAnim.duration = animDuration;
    groupAnim.autoreverses = true; // auto-reverses the animation once completed


最后,我们可以在视图被触摸时运行组动画,两个动画将同时运行(完成后自动反转)。

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) 
    animLayer.addAnimation(groupAnim, forKey: "anims") // runs both animations concurrently


结果


完整项目:https://github.com/hamishknight/Circle-to-Rect-Animation

【讨论】:

嗨,我的代码有问题:我删除了自动反转,但是当动画结束时,我的视图会立即恢复到原来的状态......有什么想法吗?谢谢!【参考方案2】:

为了获得流畅的动画效果,您希望查看为cornerRadius 属性设置动画,而不是使用贝塞尔路径。

所以动画会变成这样:

    动画圆角半径从当前值降至零 动画宽度超出屏幕宽度 反向宽度动画 反转圆角半径动画

所以,让我们从定义一些我们将要使用的属性开始:

class ViewController: UIViewController 

    let animLayer = CALayer() // the layer that is going to be animated
    let cornerRadiusAnim = CABasicAnimation(keyPath: "cornerRadius") // the corner radius reducing animation
    let cornerRadiusUndoAnim = CABasicAnimation(keyPath: "cornerRadius") // the corner radius increasing animation
    let widthAnim = CABasicAnimation(keyPath: "bounds.size.width") // the width animation
    let animDuration = NSTimeInterval(1.0) // the duration of one 'segment' of the animation
    let layerSize = CGFloat(100) // the width & height of the layer (when it's a square)

    ...

在这里,我们定义了我们将要使用的层、动画、动画“片段”之一的持续时间以及CALayer 的大小。

接下来,让我们在viewDidLoad中设置动画

override func viewDidLoad() 
    super.viewDidLoad()

    let rect = view.frame

    animLayer.backgroundColor = UIColor.blueColor().CGColor // color of the layer, feel free to change
    animLayer.frame = CGRect(x: rect.width-layerSize*0.5, y: rect.height-layerSize*0.5, width: layerSize, height: layerSize)
    animLayer.cornerRadius = layerSize*0.5;
    animLayer.anchorPoint = CGPoint(x: 1, y: 1) // sets so that when the width is changed, it goes to the left
    view.layer.addSublayer(animLayer)

    // decreases the corner radius
    cornerRadiusAnim.duration = animDuration
    cornerRadiusAnim.fromValue = animLayer.cornerRadius
    cornerRadiusAnim.toValue = 0;
    cornerRadiusAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) // timing function to make it look nice


    // inverse of the cornerRadiusAnim
    cornerRadiusUndoAnim.duration = animDuration
    cornerRadiusUndoAnim.fromValue = 0;
    cornerRadiusUndoAnim.toValue = animLayer.cornerRadius
    cornerRadiusUndoAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) // timing function to make it look nice

    // increases the width, and autoreverses on completion
    widthAnim.duration = animDuration
    widthAnim.fromValue = animLayer.frame.size.width
    widthAnim.toValue = rect.size.width
    widthAnim.autoreverses = true
    widthAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) // timing function to make it look nice
    widthAnim.delegate = self // so that we get notified when the width animation finishes


这里没什么难的,我们只是定义我们的图层和动画属性。我还添加了一些计时功能,以使动画看起来漂亮流畅,而不是线性的。

接下来,让我们开始我们的动画。我将在 touchesBegan 函数中执行此操作,但您可以将其放在任何地方。

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) 
    widthAnim.beginTime = CACurrentMediaTime()+animDuration // starts after the corner radius anim has finished

    animLayer.addAnimation(widthAnim, forKey: "widthAnim")
    animLayer.addAnimation(cornerRadiusAnim, forKey: "cornerRadius")

    CATransaction.begin()
    CATransaction.setDisableActions(true) // disables implicit animations
    animLayer.cornerRadius = 0
    CATransaction.commit()

在这里,我们添加了 widthcornerRadius 动画,为宽度动画指定延迟开始。

你问的CATransation 是怎么回事?好吧,一旦cornerRadius 动画结束,Core Animation 就会将该层捕捉回它的表示层。我们不希望这样,所以我们将直接设置该值,同时还要确保 Core Animation 在我们这样做时不会添加隐式动画。使用CATransaction 可以避免这种情况,因为使用removedOnCompletion = falsefillMode = kCAFillModeForwards 被认为是不好的做法。

最后,一旦宽度动画反转,我们要撤消圆角半径动画。我们可以这样做,因为我们之前为宽度动画分配了 delegate,因此我们可以覆盖 animationDidStop 函数。

override func animationDidStop(anim: CAAnimation, finished flag: Bool) 

    animLayer.addAnimation(cornerRadiusUndoAnim, forKey: "cornerRadiusUndo")

    CATransaction.begin()
    CATransaction.setDisableActions(true)
    animLayer.cornerRadius = layerSize*0.5
    CATransaction.commit()

再次,我们使用CATransactioncornerRadius 设置回其原始值。就是这样!


最终结果


完整项目:https://github.com/hamishknight/Circle-to-Rect-Animation

【讨论】:

你知道如何在放大的同时减小cornerRadius吗?两者都应该发生一次。 我添加了另一个答案(因为这个答案已经足够长了!)展示了你将如何做到这一点:)

以上是关于圆形到矩形的变换动画的主要内容,如果未能解决你的问题,请参考以下文章

将矩形 UIImageView 变成圆形

Flutter 尺寸缩放形状颜色阴影变换动画

OpenCV矩形检测

iOS:CAShapeLayer 绘制非矩形图像并为其形状设置动画

snipaste图片标注技巧

图解傅里叶变换(so easy)