简体   繁体   English

圆到矩形转换动画

[英]Circle to rectangle transformation animation

I am very new to iOS and I need to do following animation: 我是iOS的新手,需要做以下动画:

在此处输入图片说明

Transformation of the circle to rectangle should be smooth, but in above animation it's not very smooth. 圆到矩形的转换应该是平滑的,但是在上面的动画中,它不是很平滑。

What I did is create a circle and a rectangle using following code 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)
    }

But I have no idea how to do my animation, please can anyone help me? 但是我不知道如何制作动画,请问有人可以帮我吗?

If you want both the scaling up and corner radius reduction to happen at the same time, you can simplify the code from my other answer significantly . 如果要同时按比例增加和圆角半径减小到在同一时间发生,你可以简化从我的其他答案代码显著

You now no longer need to 'chain' the animations together, so you can add them both to a single CAAnimationGroup and run them concurrently. 现在,您不再需要将动画“链接”在一起,因此可以将它们都添加到单个CAAnimationGroup并同时运行它们。

The properties we use will remain almost identical, except with the addition of a groupAnim property and deletion of the cornerRadiusUndoAnim . 我们使用的属性将保持几乎相同,除了增加了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)

    ...        

We can now just add the setup for the CAAnimationGroup , adding both our corner radius animation and our scaling animation 现在,我们可以添加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

}

Finally, we can run the group animation when the view gets touched, and both animations will run concurrently together (and auto-reverse when done). 最后,我们可以在触摸视图时运行组动画,并且两个动画将同时运行(完成后自动反转)。

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

Result 结果

在此处输入图片说明


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

In order to get a smooth animation, you want to look at animating the cornerRadius property instead of messing about with bezier paths. 为了获得平滑的动画,您需要研究如何对cornerRadius属性进行动画处理,而不是弄乱贝塞尔曲线路径。

So the animation is going to go something like this: 因此动画将如下所示:

  1. Animate corner radius from current value down to zero 从当前值到零动画角半径
  2. Animate width out to the screen width 动画宽度到屏幕宽度
  3. Reverse width animation 反宽度动画
  4. Reverse corner radius animation 反角半径动画

So, let's get started by defining some properties that we're going to be working with: 因此,让我们开始定义一些将要使用的属性:

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)

    ...

Here we define the layer we're going to be working with, the animations, the duration of one of the animation 'segments' and the size of the CALayer . 在这里,我们定义将要使用的层,动画,动画“段”之一的持续时间以及CALayer的大小。

Next, let's setup our animations in the viewDidLoad 接下来,让我们在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

}

Nothing too difficult here, we just define our layer & animation properties. 这里没有什么难的,我们只需定义我们的图层和动画属性。 I also added some timing functions in order to make the animation look nice and smooth, instead of linear. 我还添加了一些计时功能,以使动画看起来更优美,更流畅,而不是线性的。

Next, let's start our animation. 接下来,让我们开始动画。 I'm going to be doing this in the touchesBegan function, but you can put this anywhere. 我将在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()
}

Here we add our width and cornerRadius animations, assigning a delayed start to the width animation. 在这里,我们添加了widthcornerRadius动画,为width动画分配了一个延迟的开始。

What's with the CATransation you ask? 您问的CATransation是什么? Well once the cornerRadius animation ends, Core Animation will snap the layer back to it's presentation layer. 好的,一旦cornerRadius动画结束,Core Animation就会将图层捕捉回到其表示层。 We don't want that, so we're going to set the value directly, while also making sure that Core Animation doesn't add an implicit animation when we do so. 我们不想要那样,所以我们将直接设置该值,同时还要确保Core Animation这样做时不会添加隐式动画。 Using a CATransaction avoids this as it's considered bad practice to use removedOnCompletion = false & fillMode = kCAFillModeForwards . 使用CATransaction可以避免这种情况,因为使用removedOnCompletion = falsefillMode = kCAFillModeForwards被认为是不好的做法。

Finally, we want to undo the corner radius animation, once the width animation has reversed. 最后,一旦宽度动画反转,我们想撤消角半径动画。 We can do this as we assigned a delegate to the width animation earlier, so we can override the animationDidStop function. 我们可以在前面为宽度动画分配一个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()
}

Again, we use a CATransaction to set the cornerRadius back to its original value. 同样,我们使用CATransactioncornerRadius设置回其原始值。 And that's it! 就是这样!


Final Result 最后结果

在此处输入图片说明


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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM