IOS隐式动画

来源:互联网 发布:windows桌面程序开发 编辑:程序博客网 时间:2024/05/21 12:51

个人博客 goofyy技术宅

开发环境:OS X10.11 beta 6IDE:    Xcode 7 beta 6开发语言:swift2.0

博客地址:www.goofyy.com/blog/367.html

Core Animation基于一个假设说: 屏幕上的任何东西都可以(或者可能)做动画。动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则他会一直存在。

改变Calayer的一个属性(颜色,大小等)可以作为动画的属性的时候,所做的改变并不能够直接在视图上直接体现出来,而是需要一个过渡,开发者不需要太多其他的工作,

首先做一个隐式动画的例子:-改变图层颜色

class ViewController: UIViewController {    var layerView:CALayer = CALayer()    var button: UIButton = UIButton()    override func viewDidLoad() {        super.viewDidLoad()        self.initView()        // Do any additional setup after loading the view, typically from a nib.    }    override func didReceiveMemoryWarning() {        super.didReceiveMemoryWarning()        // Dispose of any resources that can be recreated.    }    func initView() {        layerView.backgroundColor = UIColor.redColor().CGColor        layerView.frame = CGRectMake(20, 20, 50, 50)        self.view.layer.addSublayer(layerView)                    button.frame = CGRectMake(0, 340, 20, 20)            button.backgroundColor = UIColor.blackColor()            button.addTarget(self, action: "buttonAction", forControlEvents: .TouchDown)            self.view.addSubview(button)    }        func buttonAction() {        layerView.backgroundColor = UIColor.blueColor().CGColor        layerView.frame = CGRectMake(100, 100, 200, 200)            }}

出来的整体效果是


这种就是最为简单的一个隐式动画,根据名称我们可以理解:没有指定具体的动画类型,只是改变一个或者多个属性,然后扔给Core Animation,由Core Animation来决定动画,同时Core Animation支持显式动画。

CATransaction

改变属性的时候,Core Aniamtion的执行时间和执行的事件托管给了CATransaction类,像名称一样,来管理CoreAnimation的事务,CATransaction这个类没有属性或者实例方法,不能实例化,只可以调用begin和commit来将属性改变事务进行入栈和出栈。

        CATransaction.begin()        CATransaction.commit()        CATransaction.setAnimationDuration(<#T##dur: CFTimeInterval##CFTimeInterval#>)

常用的方法如上,setAnimationDuration用来设置隐式动画的时间,默认是0.25秒,所以刚刚的默认视图当中,动画就一闪而过,任何可以做动画的图层属性都会被添加到栈顶的事务都可以用这些方法来设置。

说到这里,让我们来设置一下动画吧。

 func buttonAction() {        CATransaction.begin()        CATransaction.setAnimationDuration(5)        layerView.backgroundColor = UIColor.blueColor().CGColor        layerView.frame = CGRectMake(100, 100, 200, 200)         CATransaction.commit()    }

这里的commit 和 begin方法。比较类似于UIView的beginAnimation 和 commitAnimation方法。都是在完成视图或者图层由于改变属性而做的动画。

Block

在UIView的Block当中,完成动画结束的时候,会提供一定的动作,CATranscation接口也提供了一样的方法:setCompletionBlock

具体流程是 

   回调setCompletionBlock -> CATranscation完成栈内属性改变引起的动画

让代码来说明问题吧:还是刚刚的代码。我们把颜色的改变写到setCompletionBlock回调里面

func buttonAction() {        CATransaction.begin()        CATransaction.setAnimationDuration(2)        layerView.backgroundColor = UIColor.blueColor().CGColor        CATransaction.setCompletionBlock { () -> Void in            self.layerView.frame = CGRectMake(100, 100, 200, 200)        }                 CATransaction.commit()    }


明显看到图层在移动到指定位置后,颜色开始由红色转变为蓝色。不过此时位置移动相对快很多,这是因为位置移动是在事务提交出栈之后执行的,也就是说,CATransaction设置栈内的2秒并没有对setCompletionBlock回调中属性转变动画起作用,回调中的动画依旧是0.25秒。而颜色转变还是在事务栈内,所以是2秒。这样大家应该就容易明白了吧。

UIView关联图层

之前做的都是一些单独的图层操作,下面我们做一个UIView视图关联图层的动画,而不是单独的一个图层CALayer。

class ViewController: UIViewController {    var view1 = UIView()    var button1 = UIButton()    var layer1 = CALayer()    override func viewDidLoad() {        super.viewDidLoad()      view1 = UIView(frame: CGRectMake(50, 50, 100, 100))        view1.layer.backgroundColor = UIColor.redColor().CGColor        self.view.addSubview(view1)        button1.addTarget(self, action: "animation", forControlEvents:UIControlEvents.TouchDown)        button1.setTitle("点击我", forState: UIControlState.Normal)        button1.frame = CGRectMake(0, 0, 40, 40)        button1.backgroundColor = UIColor.greenColor()        self.view.addSubview(button1)                layer1.backgroundColor = UIColor.yellowColor().CGColor        layer1.frame = CGRectMake(50, 200, 50, 50)        self.view.layer.addSublayer(layer1)        // Do any additional setup after loading the view, typically from a nib.    }    override func didReceiveMemoryWarning() {        super.didReceiveMemoryWarning()        // Dispose of any resources that can be recreated.    }    func animation() {                CATransaction.begin()        CATransaction.setAnimationDuration(1)        self.view1.layer.backgroundColor = UIColor.greenColor().CGColor        layer1.frame = CGRectMake(20, 20, 100, 100)        layer1.backgroundColor = UIColor.grayColor().CGColor        CATransaction.commit()            }    }

看一下具体效果


这里我在代码里面添加了一个单独的CALyer(初始颜色是黄色,最终颜色是灰色)和一个UIView(初始颜色是红色,最终颜色是绿色).对比一下可以发现。单独图层的CALayer的有过渡效果,但是UIView的图层的颜色瞬间切换到绿色。似乎平滑的过渡效果被禁用了一般。

为何会这样呢???

Core Animation通常对所有可动画属性做动画,但是UIView关联的图层把它给禁用了,让我们看一下CALayer先

当CALayer的属性改变的时候,会调用actionForKey的方法,传递属性的名称等等。看一下CALayer的头文件

    public class func defaultActionForKey(event: String) -> CAAction?        /* Returns the action object associated with the event named by the     * string 'event'. The default implementation searches for an action     * object in the following places:     *     * 1. if defined, call the delegate method -actionForLayer:forKey:     * 2. look in the layer's `actions' dictionary     * 3. look in any `actions' dictionaries in the `style' hierarchy     * 4. call +defaultActionForKey: on the layer's class     *     * If any of these steps results in a non-nil action object, the     * following steps are ignored. If the final result is an instance of     * NSNull, it is converted to `nil'. */        public func actionForKey(event: String) -> CAAction?        /* A dictionary mapping keys to objects implementing the CAAction     * protocol. Default value is nil. */

在这里我们可以看到在传递完属性后,实质上也就是上面几步。

#.图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法。如果有,直接调用并返回结果。#.如果没有委托,或者委托没有实现-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。#.如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。#.最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法。

总结来说。就是我把我更改的属性名称传递给你了,你在委托里面没有找到,或者你找到了委托,但是委托没有实现actionForLayer的方法,又或者是你传递的属性不是可动画属性,或者style找不到相应的行为,那这样四样都木有,那还干吊。这时候CALayer就不干了。也就没有了平滑的动画过渡。

明白了这一点。再来分析一下UIView的关联图层layer

每个UIView对它关联的图层都扮演了一个委托,并且提供了-actionForLayer:forKey的实现方法。当不在一个动画块的实现中,UIView对所有图层行为返回nil,但是在动画block范围之内,它就返回了一个非空值

简而言之。就是在动画块范围之内,actionForLayer会返回一个非空,但是在动画块范围之外,就会返回一个nil来禁用UIView的关联图层的过渡动画。

依旧是写一段代码测试一下

class ViewController: UIViewController {    var view1 = UIView()    override func viewDidLoad() {        super.viewDidLoad()        view1.frame = CGRectMake(50, 50, 100, 100)        self.view.addSubview(view1)        print(view1.actionForLayer(view1.layer, forKey: "backgroundColor"))        UIView.beginAnimations(nil, context: nil)        print(view1.actionForLayer(view1.layer, forKey: "backgroundColor"))        UIView.commitAnimations()                // Do any additional setup after loading the view, typically from a nib.    }    override func didReceiveMemoryWarning() {        super.didReceiveMemoryWarning()        // Dispose of any resources that can be recreated.    }}

运行结果

Optional(<null>)Optional(<CABasicAnimation: 0x7fc5b07778f0>)

这也就证实了那段晦涩难懂的话,不过还是记着那句简单的。。就是在动画块范围之内,actionForLayer会返回一个非空,但是在动画块范围之外,就会返回一个nil来禁用UIView的关联图层的过渡动画。

当然事务CATranscation也可以来禁用动画过渡效果。

CATransaction.setDisableActions(true)

呈现与模型

在我们设置了图层的属性的时候,我们开始动画的时候,发现动画是有一个过渡效果的,但是其实真实情况是,当你一旦改变了某个图层的属性之后其实它的属性是立即更新的,但是屏幕并没有直接发生变化,因为你设置的属性并没有直接改变图层的外观。

在iOS中,屏幕每秒钟重绘60次。如果动画时长比60分之一秒要长,Core Animation就需要在设置一次新值和新值生效之间,对屏幕上的图层进行重新组织。这意味着CALayer除了“真实”值(就是你设置的值)之外,必须要知道当前显示在屏幕上的属性值的记录。

每个图层属性的显示值都被存储在一个叫做呈现图层的独立图层当中,他可以通过-presentationLayer方法来访问。这个呈现图层实际上是模型图层的复制,但是它的属性值代表了在任何指定时刻当前外观效果。换句话说,你可以通过呈现图层的值来获取当前屏幕上真正显示出来的值

7.4

大多数情况下,你不需要直接访问呈现图层,你可以通过和模型图层的交互,来让Core Animation更新显示。两种情况下呈现图层会变得很有用,一个是同步动画,一个是处理用户交互。

#-如果你在实现一个基于定时器的动画(见第11章“基于定时器的动画”),而不仅仅是基于事务的动画,这个时候准确地知道在某一时刻图层显示在什么位置就会对正确摆放图层很有用了。#-如果你想让你做动画的图层响应用户输入,你可以使用-hitTest:方法(见第三章“图层几何学”)来判断指定图层是否被触摸,这时候对呈现图层而不是模型图层调用-hitTest:会显得更有意义,因为呈现图层代表了用户当前看到的图层位置,而不是当前动画结束之后的位置。

依旧贴代码

class ViewController: UIViewController {    var layer = CALayer()    override func viewDidLoad() {        super.viewDidLoad()        layer.backgroundColor = UIColor.redColor().CGColor        layer.frame = CGRectMake(100, 100, 100, 100)        self.layer.position = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2)        self.view.layer.addSublayer(layer)        // Do any additional setup after loading the view, typically from a nib.    }    override func didReceiveMemoryWarning() {        super.didReceiveMemoryWarning()        // Dispose of any resources that can be recreated.    }        override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {        let touch = touches.first?.locationInView(self.view)        if((self.layer.presentationLayer()?.hitTest(touch!)) != nil) {            self.layer.backgroundColor = UIColor.yellowColor().CGColor        } else {            CATransaction.begin()            CATransaction.setAnimationDuration(1)            self.layer.position = touch!            CATransaction.commit()        }    }}

看一下效果

hello3

打完收工。哈哈哈。


1 0