Анимация преобразования круга в прямоугольник

Я очень новичок в iOS, и мне нужно сделать следующую анимацию:

введите здесь описание изображения

Преобразование круга в прямоугольник должно быть плавным, но в приведенной выше анимации оно не очень плавное.

Я создал круг и прямоугольник, используя следующий код в этом руководстве:

  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)
    }

Но я понятия не имею, как сделать свою анимацию, пожалуйста, кто-нибудь может мне помочь?


person roledene JKS    schedule 27.01.2016    source источник


Ответы (2)


Если вы хотите, чтобы масштабирование и уменьшение радиуса угла происходило одновременно одновременно, вы можете упростить код из моего другого ответа значительно.

Теперь вам больше не нужно «связывать» анимации вместе, поэтому вы можете добавить их обе в один 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

person Hamish    schedule 28.01.2016
comment
Привет, у меня проблема с вашим кодом: я удалил автореверс, но когда анимация закончилась, мой вид мгновенно возвращается в исходное состояние... Есть идеи? Спасибо! - person Paul Bénéteau; 18.11.2017

Чтобы получить плавную анимацию, вам следует обратить внимание на анимацию свойства cornerRadius, а не возиться с путями Безье.

Итак, анимация будет выглядеть примерно так:

  1. Анимировать радиус угла от текущего значения до нуля
  2. Анимировать ширину до ширины экрана
  3. Анимация обратной ширины
  4. Анимация обратного радиуса угла

Итак, давайте начнем с определения некоторых свойств, с которыми мы будем работать:

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()
}

Здесь мы добавляем наши анимации width и cornerRadius, назначая отложенный старт анимации ширины.

Что с CATransation спросите вы? Как только анимация cornerRadius закончится, Core Animation вернет слой обратно к слою презентации. Нам это не нужно, поэтому мы установим значение напрямую, а также убедимся, что Core Animation не добавляет неявную анимацию, когда мы это делаем. Использование CATransaction позволяет избежать этого, поскольку использование removedOnCompletion = false и fillMode = 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()
}

Опять же, мы используем CATransaction, чтобы вернуть cornerRadius исходное значение. Вот и все!


Конечный результат

введите здесь описание изображения


Полный проект: https://github.com/hamishknight/Circle-to-Rect-Animation

person Hamish    schedule 27.01.2016
comment
у вас есть идея о том, как уменьшить угол угла при увеличении масштаба? оба должны произойти один раз. - person roledene JKS; 28.01.2016
comment
Я добавил еще один ответ (поскольку этот уже достаточно длинный!), Показывающий, как вы это сделаете :) - person Hamish; 28.01.2016