前言

在直达一样篇认识CoreAnimation中笔者介绍了系统的动画库CoreAnimation,使用动画库有许多利益,这里虽不再进行再叙述。那么本篇将承接上同样首的始末,使用提到的功底之卡通相关类来兑现动画效果,效果图放上:

约上得看来demo主要是逐级变和形变两种动画,在更早前的文章,我们就用UIView的动画片接口就了同样的动画,而这次以变成CoreAnimation来形成这些干活儿

关于图层

在iOS中,每一个UIView都具备一个及的绑定的CALayer图层对象,其负责视图内容之绘图和展示。跟前者一样,CALayer否不无树状的子图层结构,以及相似之接口方法。CALayer举凡图层的基类,主要提供了视图显示范围、图层结构接口等性,我们经过下她的子类。下面是平截以控制器的界面中心上加一个圈的紫图层:

override func viewDidLoad() {
    super.viewDidLoad()

    let layer = CAShapeLayer()
    layer.fillColor = UIColor.purpleColor().CGColor
    layer.path = UIBezierPath(arcCenter: CGPoint(x: UIScreen.mainScreen().bounds.width / 2, y: UIScreen.mainScreen().bounds.height / 2), radius: 100, startAngle: 0, endAngle: 2.0*CGFloat(M_PI), clockwise: false).CGPath
    self.view.layer.addSublayer(layer)
}

同样的,每一个CALayer有一个sublayers的数组属性,我们为可以遍历这个数组来成功移除子视图之类的操作:

for sublayer in self.view.layer.sublayers! {
    print("\(sublayer)")
    sublayer.removeFromSuperlayer()
}

是因为核心动画框架的卡通都是基于CALayer的图层进行添加贯彻的,所以图层的长移除方法是无限常用的措施。当然,还有一个addAnimation(anim:forKey:)接口用来让图层添加动画

基础动画

基本功动画CABasicAnimation举凡最常用来兑现动画效果的卡通片类,其后续自CAAnimation卡通基类,为图层动画效果实现了一个keyPath性能,我们由此安装这个特性来啊相应的keyPath属性值执行动画效果。动画类提供了fromValuetoValue片只属于性用来装动画的开局与终止的价,比如下面一截代码让上加到视图上之紫图层变得透明:

@IBAction func actionToAnimatedLayer(sender: AnyObject) {
    let animation = CABasicAnimation(keyPath: "opacity")
    animation.fromValue = NSNumber(double: 1)
    animation.toValue = NSNumber(double: 0)
    animation.duration = 1
    layer.addAnimation(animation, forKey: nil)
}

上面的代码用动画表现了当1秒内叫图层的opacity属性从10的经过。但上面不难看出在动画结束后,紫色的图层没有保持opacity等于0的状态,而是返回了动画最初步的状态。这是怎吧?

每当达成一致篇中笔者提到了当各级一个CALayer饱受在在模型呈现渲染老三种图层树,正是这些图层树共同作用来就隐式动画。那么以基本动画的时光,实际上CABasicAnimation会冲动画时抬高计算出每一样幅的卡通片属性之值,然后实时提交给呈现树来显示对应时间点的视图效果,在动画结束时CAAnimation靶会自动从图层上移除。而由于当整整动画过程模型树的价值没有改变,所以当动画结束之早晚呈现树会晤另行由模型树取图层的特性重新绘制。对这个,存在这几种植缓解方案:

  • 每当落实动画的时光以修改opacity,保证模型树的多寡并
    @IBAction func actionToAnimatedLayer(sender: AnyObject) {
    let animation = CABasicAnimation(keyPath: “opacity”)
    animation.fromValue = NSNumber(double: 1)
    animation.toValue = NSNumber(double: 0)
    animation.fillMode = kCAFillModeForwards
    animation.removedOnCompletion = false
    animation.duration = 1

        layer.addAnimation(animation, forKey: nil)
    }
    
  • 取消CAAnimation的机动移除,并且安装以动画结束后保持动画的收尾状态
    @IBAction func actionToAnimatedLayer(sender: AnyObject) {
    let animation = CABasicAnimation(keyPath: “opacity”)
    animation.fromValue = NSNumber(double: 1)
    animation.toValue = NSNumber(double: 0)
    animation.fillMode = kCAFillModeForwards
    animation.removedOnCompletion = false
    animation.duration = 1
    layer.addAnimation(animation, forKey: nil)
    }

  • 贯彻动画代理方。综合上面两种方式的操作
    @IBAction func actionToAnimatedLayer(sender: AnyObject) {
    let animation = CABasicAnimation(keyPath: “opacity”)
    animation.fromValue = NSNumber(double: 1)
    animation.toValue = NSNumber(double: 0)
    animation.fillMode = kCAFillModeForwards
    animation.removedOnCompletion = false
    animation.duration = 1

        animation.setValue(layer, forKey: "animatedLayer")
        animation.delegate = self
        layer.addAnimation(animation, forKey: nil)
    }
    
    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
        if anim is CABasicAnimation {
            let animation = anim as! CABasicAnimation
            if let layer = animation.valueForKey("animatedLayer") as? CALayer {
                layer.setValue(animation.toValue, forKey: animation.keyPath!)
                layer.removeAllAnimations()
            }
        }
    }
    

互相较前面少栽艺术,实现代理然后设置属性之做法有点糊涂且低效的觉得。但在一些应用场景下,我们需要在动画结束时变除图层或其它操作,通过落实代理是无限好之做法。其他常用之keyPath动画值可以当这里查看

动画组

随后上面的动画效果,我怀念如果在潜移默化的根底及搭一个形变动画,那么自己要创造两独CABasicAnimation靶来就就同样办事:

@IBAction func actionToAnimatedLayer(sender: AnyObject) {
    let opacity = CABasicAnimation(keyPath: "opacity")
    opacity.fromValue = NSNumber(double: 1)
    opacity.toValue = NSNumber(double: 0)
    opacity.duration = 1

    layer.addAnimation(opacity, forKey: "opacity")

    let scale = CABasicAnimation(keyPath: "transform")
    scale.fromValue = NSValue(CATransform3D: CATransform3DIdentity)
    scale.toValue = NSValue(CATransform3D: CATransform3DMakeScale(2, 2, 2))
    scale.duration = 1

    layer.addAnimation(scale, forKey: "scale")
}

而外上面立段代码之外,在CoreAnimation框架中提供了一个CAAnimationGroup看似来用大半只卡通对象成成一个目标上加至图层上。从使用实现之角度而言,并无会见及方的代码来其他出入,却得以给代码的逻辑更是清晰:

@IBAction func actionToAnimatedLayer(sender: AnyObject) {
    // create animations

    let group = CAAnimationGroup()
    group.animations = [opacity, scale]
    group.duration = 1
    layer.addAnimation(group, forKey: "group")
}

按钮动画

第一是卡通片中之形变和透亮渐变分别对应transform以及opacity两个keyPath,其次,动画图层不是按钮本身的图层,因此还亟需添加额外的一个图层。另外,动画存在外扩和内扩的动画片效果,因此我们还索要定义一个枚举来区分:

enum LXDAnimationType {
    case Inner
    case Outer
}

在swift的extension吃未支持添加储值属性,因此我们要以到runtime的动态绑定来好对按钮包括动画类型、动画颜色两只特性的恢弘:

private var kAnimationTypeKey: UInt = 0
private var kAnimationColorKey: UInt = 1
extension UIButton {

    enum LXDAnimationType {
        case Inner
        case Outer
    }

    //MARK: - Expand property
    var animationType: LXDAnimationType? {
        get {
            if let type = (objc_getAssociatedObject(self, &kAnimationTypeKey) as? String) {
                return LXDAnimationType(rawValue: type)
            }
            return nil
        }
        set {
            guard newValue != nil else { return }
            self.clipsToBounds = (newValue == .Inner)
            objc_setAssociatedObject(self, &kAnimationTypeKey, newValue!.rawValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    var animationColor: UIColor {
        get {
            if let color = objc_getAssociatedObject(self, &kAnimationColorKey) {
                return color as! UIColor
            }
            return UIColor.whiteColor()
        }
        set {
            objc_setAssociatedObject(self, &kAnimationColorKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

属下是哪些管我们以点击按钮的时光可执行我们的卡通片。这里我们经过重写按钮的sendAction(action:to:forEvent:)法来实施动画,这个办法以每次按钮发送一个风波频仍会让调用。同理,当用户点击按钮时为会见调用这个法子:

//MARK: - Override
public override func sendAction(action: Selector, to target: AnyObject?, forEvent event: UIEvent?) {
    super.sendAction(action, to: target, forEvent: event)

    if let type = animationType {
        var rect: CGRect?
        var radius = self.layer.cornerRadius

        var pos = touchPoint(event)
        let smallerSize = min(self.frame.width, self.frame.height)
        let longgerSize = max(self.frame.width, self.frame.height)
        var scale = longgerSize / smallerSize + 0.5

        switch type {
        case .Inner:
            radius = smallerSize / 2
            rect = CGRect(x: 0, y: 0, width: radius*2, height: radius*2)
            break

        case .Outer:
            scale = 2.5
            pos = CGPoint(x: self.bounds.width/2, y: self.bounds.height/2)
            rect = CGRect(x: pos.x - self.bounds.width, y: pos.y - self.bounds.height, width: self.bounds.width, height: self.bounds.height)
            break
        }

        let layer = animateLayer(rect!, radius: radius, position: pos)
        let group = animateGroup(scale)
        self.layer.addSublayer(layer)
        group.setValue(layer, forKey: "animatedLayer")
        layer.addAnimation(group, forKey: "buttonAnimation")
    }
}

public override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
    if let layer = anim.valueForKey("animatedLayer") as? CALayer {
        layer .removeFromSuperlayer()
    }
}


//MARK: - Private
private func touchPoint(event: UIEvent?) -> CGPoint {
    if let touch = event?.allTouches()?.first {
        return touch.locationInView(self)
    } else {
        return CGPoint(x: self.frame.width/2, y: self.frame.height/2)
    }
}

private func animateLayer(rect: CGRect, radius: CGFloat, position: CGPoint) -> CALayer {
    let layer = CAShapeLayer()
    layer.lineWidth = 1
    layer.position = position
    layer.path = UIBezierPath(roundedRect: rect, cornerRadius: radius).CGPath

    switch animationType! {
    case .Inner:
        layer.fillColor = animationColor.CGColor
        layer.bounds = CGRect(x: 0, y: 0, width: radius*2, height: radius*2)
        break

    case .Outer:
        layer.strokeColor = animationColor.CGColor
        layer.fillColor = UIColor.clearColor().CGColor
        break
    }
    return layer
}

private func animateGroup(scale: CGFloat) -> CAAnimationGroup {
    let opacityAnim = CABasicAnimation(keyPath: "opacity")
    opacityAnim.fromValue = NSNumber(double: 1)
    opacityAnim.toValue = NSNumber(double: 0)

    let scaleAnim = CABasicAnimation(keyPath: "transform")
    scaleAnim.fromValue = NSValue(CATransform3D: CATransform3DIdentity)
    scaleAnim.toValue = NSValue(CATransform3D: CATransform3DMakeScale(scale, scale, scale))

    let group = CAAnimationGroup()
    group.animations = [opacityAnim, scaleAnim]
    group.duration = 0.5
    group.delegate = self
    group.fillMode = kCAFillModeBoth
    group.removedOnCompletion = false
    return group
}

扩充之后的按钮只要设置animationType其一特性之后就会见落实在点击时之动画片效果

animateButton.animationType = .Outer

尾话

相比于国外的运用,国内的动画效果使展示内敛得差不多,甚至群的app是尚未考虑了动画制作的。但是当倒端支付已然是同一片血海的今天,漂亮的动画片效果仍会吗您的以带来有,前提是您的下要依谱——单纯的动效留不歇人。因此,掌握动画是生死攸关的一致件基本技能。本文demo

上一篇:认识CoreAnimation
下一篇:定时器动画

转载请注明本文作者及转载地址

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图