Swift_ios_UIView动画,CA核心动画那些事(2)

Swift_ios_UIView动画,CA核心动画那些事(2)_第1张图片
秒学SWIFT

花了将近一周的时间去学习ios动画,因为对于一个ios开发者来说,动画内容绝对是一门必修课。听了不少课,也看了不少文章,终于对动画有了初步的了解和自己的一些小总结。但是傻傻笨笨的我,给自己挖了一个坑,为了填这个坑花了快两天时间,真够笨的!不过最终还是完美解决,小小成就感就来了!

关于动画,网上流传着许许多多的文章,基本上都适合初学者入门。那些文章大概思路都是这样的:
1.介绍什么是动画
2.动画可以分为UIView动画和CA动画。(其他动画暂时忽略)
3.UIView动画分为常规模式和闭包模式。现在主要用闭包模式。
4.UIView闭包模式有基本“杜蕾斯”动画,杜蕾斯弹性动画,转场动画,关键帧动画。
5.CA动画有基本动画,转场动画,关键帧动画,组动画,弹性动画。
6.UIView动画和CA动画的关系,即UIView动画是CA动画的封装。各有优势各有特色。

无可否认的是这些对自己在初步认识动画阶段,起到了很大的帮助作用,起码让自己对动画有个大概的了解。但是仅仅这些,还是会让初学者掉进坑里,比如我。所以学习动画以后的总结经验,就不可或缺了。这才不会让自己第二次掉进同一个坑。待会我要记录下自己怎么掉坑,填坑的。

我是先学习CA动画的,明白了CA动画能够细微调整的意义,也见识了CA动画是怎么让开发者去掌控每一个环节的。这个过程中我也笔记了基础的知识:(有一部分摘抄,一部分是自己总结)

//CAAnimation:
//所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用,应该使用它具体的子类
//duration:动画的持续时间
//repeatCount:动画的重复次数
//repeatDuration:动画的重复时间
//removedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards
//fillMode:决定当前对象在非active时间段的行为.比如动画开始之前,动画结束之后
//beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间
//timingFunction:速度控制函数,控制动画运行的节奏
//delegate:动画代理
//keyPath: 通过指定CALayer的一个属性名称达到相应的动画效果,比如说,指定"position"为keyPath,就修改CALayer的position属性值,以达到平移的动画效果


//A. CGAffineTransform
//从CG就可以看出它是属于Core Graphics的东西,实际上UIView的transform属性就是CGAffineTransform类型,用它可以做二维平面上的缩放、旋转、平移。
//B.CATransform3D(layer)
//它可以做到让图层在三维空间内平移、旋转等。

  • CABasicAnimation动画比较简单,不做多介绍,只留笔记重点。如果有误,欢迎指正。
    //A.CABasicAnimation
    //CABasicAnimation(keyPath: "transform")可以实现2D和3D动画。取决于keyPath。
    //如果keyPath: "transform" 则是3D动画。rotation属性才能体现3D效果
    //如果keyPath: "transform.rotation" 则是2D动画。
    //2D动画变换前的原始状态。view.transform = CGAffineTransformIdentity
    //3D动画变换前的原始状态view.layer.transform = CATransform3DIdentity
  • CAKeyFrameAnimation动画功能强大,有必要对其属性阐述一下。
    //B. CAKeyFrameAnimation
    //CApropertyAnimation的子类,跟CABasicAnimation的区别是: CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation会使用一个NSArray保存这些数值
    //属性解析:
    //values:就是上述的NSArray对象。里面的元素称为”关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
    //path:可以设置一个CGPathRef\CGMutablePathRef,让层跟着路径移动。path只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略
    //keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的
    //CABasicAnimation可看做是最多只有2个关键帧的CAKeyframeAnimation
    //这里有必要提供一下快速构建values的方法
    let arr = [(20,30),(100,100),(100,300),(50,300)].map{ (x:Int,y:Int) -> NSValue in
    NSValue(CGPoint: CGPoint(x: x, y: y))

      }
      
      keyAnimate.values = arr
    
  • CAAnimationGroup也比较简单,就是对多个动画的组合。
    //C. CAAnimationGroup
    //CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行.支持多个动画组合。
    //属性解析:
    //animations:用来保存一组动画对象的NSArray
    //默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间

  • CATransition是一个比较有意思的动画,转场效果挺多。
    //D. CATransition
    //CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点
    //UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果
    //属性解析:
    //type:动画过渡类型
    /*
    fade
    push
    moveIn
    reveal
    cube
    oglFlip
    suckEffect
    rippleEffect
    pageCurl
    pageUnCurl
    cameraIrisHollowOpen
    cameraIrisHollowClose
    */
    //subtype:动画过渡方向
    //startProgress:动画起点(在整体动画的百分比)
    //endProgress:动画终点(在整体动画的百分比)

  • CASpringAnimation是弹性动画,能够表现出非常性感细腻的效果
    //E.CASpringAnimation
    属性:默认值
    damping:10.0
    mass :1.0
    stiffness:100.0
    initialVelocity:0.0

     let sprintAni = CASpringAnimation(keyPath: "position.y")
      sprintAni.damping = 10
      sprintAni.mass = 5
      sprintAni.stiffness = 50
      sprintAni.initialVelocity = 3
      
      sprintAni.duration = 2
      sprintAni.toValue = 300
          
      sprintAni.fillMode = kCAFillModeForwards
      sprintAni.removedOnCompletion = false
      
      yourView.layer.addAnimation(sprintAni, forKey: "anykey")
    

以上是CA动画的类型,我学习完它再去学UIView动画,所以知道UIView动画其实就是CA动画的封装,优点是快捷方便,UIView的弹性动画完美体现了这点。缺点是不能细微调整。这里不作UIView的详细介绍。

虽然UIView动画是对CA核心动画的封装,但还是有必要对他们加以总结,这可是目前在网上找不到的宝贵经验哦!(经验可能有误,欢迎指正)

//UIView的动画跟CAAnimation动画的异同:
//1.都能控制动画开始执行时刻。uiview的delay。CA中的beginTime。
//2.都能在动画结束后实现控制。uiview有闭包。CA中有代理函数didFinish。
//3.uiView有弹性动画和关键帧动画,CA中也有,而且更为丰富。
//4.uiView组合动画用cgaffinetransformconcat。CA中用CAanimationGroup,并支持多组合。
//    uiview 的多组合则可以通过创建多个uiview.animation来实现。多是指两个以上。
//    这里的组合是指为同一个对象的不同属性进行组合。
//    如果是不同对象要实现同一个动画,则直接在uiview的内容中添加。或者直接在CA中赋予多个对象的layer。
//5.uiView实现2D或者3D动画,取决于里面设置的动画属性。若设置的是layer层,则可以实现3D动画。(rotation属性可以体现)
//  CA动画则取决于key。如果是transform,则可以3D.如果是transform.rotation,则可以是2D。
//    CA中transform属性有rotation,scale,translation。
//    CA中key还可以是bounds,position,opacity。这里的position等价于uiview的center。
//6.uiview动画完毕之后属性已经更改。CA动画则不会改变实际位置,即使表面改变了。

下面我则要记录下我在学习UIView动画的时候是怎么给自己挖坑的,并怎么最终把坑填上获得小小成就感的。其实当完美解决问题的那一刻,发现代码是如此的简单,可就为了那一段代码,让我费劲了力气,花尽了脑汁才得以解决。只怪自己经验不足咯!都说怪我咯希望能帮助到有同样困惑的人儿

关键字:中断,终止,中止,取消,停止UIView动画

问题发现:

  • UIView动画在duration内,也就是正在执行的过程中,我再次触发了同样的动画,此时动画就会不正常显示。

问题起源:

  • 发现这个问题的时候,其实很多人就想到可能会转用CA动画去实现,因为CA动画在执行过程中,再次触发的话,它会重新来过,并不会出现错乱。我也是想到了这个办法,但是我就想知道在UIView动画中怎么解决这个问题的!所以问题就这样起源了~

问题解决:

  • 1.首先肯定是想到再次触发前先把上一次动画取消掉,想想应该是很快就把问题给解决了吧,因为从逻辑上并没有什么错误。于是我触发的前面加了一句self.textView?.layer.removeAllAnimations()
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {      
        self.textView?.layer.removeAllAnimations()
        self.animation()   
    }

可是问题真的解决了吗?当然不是。添加后的现象是我再次触发时,动画立马停止了。看起来后面self.animation()并没有执行一样。

  • 2.于是我开始请教百度叫兽,失望的是几乎把百度翻了个遍,也没找到答案。纳闷了,难道只有我才遇到这个问题吗?只有我经验浅脑子笨才掉这个坑吗?唯一在网上找到一个相关的文章《如何中止UIView动画?》
    http://samwei12.gitcafe.io/2015/09/09/%E5%A6%82%E4%BD%95%E5%8F%96%E6%B6%88UIView%E5%8A%A8%E7%94%BB/ 上也有。又是OC版本的,OC就OC吧,抱着一线希望把OC代码转换成Swift后,一执行丫的还是不管用!梦想再次破灭~

  • 3.这时想到了swift交流群,在群上一问三不知,这该如何是好。大神都不出来帮我~

  • 4.还是自己找原因吧。在UIView动画执行完的闭包里面添加一些打印信息吧。于是我添加了print("finish")
    { (finish:Bool) -> Void in
    if finish {print("finish")}
    }
    此时我在第二次又触发动画的时候发现,只打印了一次finish!这finish是第一次动画执行的还是第二次动画执行的?从现象上就很好解释了,肯定是第二次动画打印的finish。而且还有一个现象就是,第二次触发动画的时候,立马就打印finish,这也就是为什么看不到第二次动画的执行!原来本意是要停掉上一次正在执行的动画,再接着执行第二次动画。现在问题是第二次也被停掉了!!!到底问题出现在哪里???灵光一闪,突然想到了延时!就是停掉第一次动画的时候,延时一下,再执行第二次动画看行不行?
    override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
    self.textView?.layer.removeAllAnimations()
    self.performSelector("animation", withObject: nil, afterDelay: 0.3)
    }

  • 5.duang~的一下,成功了!只要在执行动画的前面添加一个细小的延时,就可以完美解决了这个可恶的问题!后面我再测了一下,把afterDelay改成0 ,也同样成功了,这这又如何解释,就留给大家吧

问题回首:

  • 解决的代码非常简单,可对我来说真的容易么?在没有百度君君的帮助下,孤军奋战,血战到底,我容易么?
有了UIView动画的填坑经验,我自个再到CA中去解决类似问题就迎刃而解了!什么?刚刚不是说CA中不存在这种问题吗??这里有必要说明一点,就是当CA动画是非无止境动画(就是会停止的动画),在还没停止之前再次触发,是不会发生这些错乱问题的。

然而要是CA动画是个无止境的动画,也就是如果动画委托协议的animationDidStop中再次调用动画函数的话,这时再来个触发相同动画,动画就是乱得一塌糊涂了!接下来就记录一下怎么轻松解决这个问题的。
   @IBAction func next(sender: AnyObject) {
    self.transition()
    }

  func transition(){
    let transition = CATransition()
    transition.delegate = self
    //动画过渡类型
    transition.type = "pageCurl"
    
    //动画过渡类型方向
    transition.subtype = kCATransitionFromLeft
    
    transition.duration = 1
    
    transition.setValue("second", forKey: "whichAnimation")
    
    self.iv.layer.addAnimation(transition, forKey: nil)
  }

  override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
        switch anim.valueForKey("whichAnimation") as! String{
        case "one"    :
                        print("hello")         
        case "second" :
                        print("finish")    
                        self.next("repeat")
        default :print("grandre")
        }
  }

这样的代码确实能够运行,能够循环调用动画,实现无止境。但是问题是当再次点击按键触发动画的话,这代码的bug就一漏无遗了。

  • 解决初探1:再次触发之前,去掉所有动画。
    @IBAction func next(sender: AnyObject) {
    self.iv.layer.removeAllAnimations()
    self.transition()
    }
    结果:失败。原因:self.iv.layer.removeAllAnimations()执行后会调用委托协议,导致死循环。

  • 解决初探2:添加“是否自动完成动画”标志。如果是自动完成一轮动画,则执行委托协议代码,如果不是自动完成,则不执行。从而避免了死循环。
    @IBAction func next(sender: AnyObject) {
    ifAutoFinishAnimate = false
    self.iv.layer.removeAllAnimations()//这里没打印是因为标志置false了
    self.transition()
    }
    func transition(){
    let transition = CATransition()
    transition.delegate = self
    //动画过渡类型
    transition.type = "pageCurl"

      //动画过渡类型方向
      transition.subtype = kCATransitionFromLeft
      
      transition.duration = 1
      //        一定要在加载动画之前设置setValue
      transition.setValue("second", forKey: "whichAnimation")
      
      self.iv.layer.addAnimation(transition, forKey: nil)
      ifAutoFinishAnimate = true  //动画完成之后恢复标志,才能执行委托协议代码
    }
      override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
       if ifAutoFinishAnimate == true{
          switch anim.valueForKey("whichAnimation") as! String{
          case "one" :print("hello")
          case "second" :print("finish")
              self.next("2")
          default :print("baba")
          }
        }
     }
    

    结果:失败!现象是“根本停不下来!”此时原因应该就是UIView动画的原因一样了!


  • 解决初探3:添加延时。
    @IBAction func next(sender: AnyObject) {
    ifAutoFinishAnimate = false
    self.iv.layer.removeAllAnimations()//这里没打印是因为标志置false了
    performSelector("transition", withObject: nil, afterDelay: 0.3)
    }
    结果:Done!完美解决!这经验真管用!afterDelay改成0,这次就不行咯!至于为什么,同样留给大家思考吧。所以记录所遇到的问题并加以总结是对自己非常有帮助的。

谨以此文章记录自己在学习动画路上遇到的问题,也希望能够通过分享总结,分享故事帮助到碰上同样问题的人儿。

你可能感兴趣的:(Swift_ios_UIView动画,CA核心动画那些事(2))