Animating between two bezier path shapes

后端 未结 3 697
忘掉有多难
忘掉有多难 2021-01-31 12:39

I found it tricky to animate a UIImageView between two states: its original rectangle frame, and a new shape created with a UIBezierPath. There are man

3条回答
  •  既然无缘
    2021-01-31 13:14

    Another approach is to use a display link. It's like a timer, except it's coordinated with the update of the display. You then have the handler of the display link modify the view according to what it should look like at any particular point of the animation.

    For example, if you wanted to animate the rounding of the corners of the mask from 0 to 50 points, you could do something like the following, where percent is a value between 0.0 and 1.0 indicating what percentage of the animation is done:

    let path = UIBezierPath(rect: imageView.bounds)
    let mask = CAShapeLayer()
    mask.path = path.CGPath
    imageView.layer.mask = mask
    
    let animation = AnimationDisplayLink(duration: 0.5) { percent in
        let cornerRadius = percent * 50.0
        let path = UIBezierPath(roundedRect: self.imageView.bounds, cornerRadius: cornerRadius)
        mask.path = path.CGPath
    }
    

    Where:

    class AnimationDisplayLink : NSObject {
        var animationDuration: CGFloat
        var animationHandler: (percent: CGFloat) -> ()
        var completionHandler: (() -> ())?
    
        private var startTime: CFAbsoluteTime!
        private var displayLink: CADisplayLink!
    
        init(duration: CGFloat, animationHandler: (percent: CGFloat)->(), completionHandler: (()->())? = nil) {
            animationDuration = duration
            self.animationHandler = animationHandler
            self.completionHandler = completionHandler
    
            super.init()
    
            startDisplayLink()
        }
    
        private func startDisplayLink () {
            startTime = CFAbsoluteTimeGetCurrent()
            displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
            displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
        }
    
        private func stopDisplayLink() {
            displayLink.invalidate()
            displayLink = nil
        }
    
        func handleDisplayLink(displayLink: CADisplayLink) {
            let elapsed = CFAbsoluteTimeGetCurrent() - startTime
            var percent = CGFloat(elapsed) / animationDuration
    
            if percent >= 1.0 {
                stopDisplayLink()
                animationHandler(percent: 1.0)
                completionHandler?()
            } else {
                animationHandler(percent: percent)
            }
        }
    }
    

    The virtue of the display link approach is that it can be used to animate some property that is otherwise unanimatable. It also lets you to precisely dictate the interim state during the animation.

    If you can use CAAnimation or UIKit block-based animation, that's probably the way to go. But the display link can sometimes be a good fallback approach.

提交回复
热议问题