程序员日常

为你的UIView添加一个动画Layer

2018-05-21  本文已影响4人  听风轻咛

我想你一定用过UIView, 我想你也一定知道CALayer是什么,具体细节的东西请自行谷歌,我们今天要用Layer搞一点事情;

总结几点性质

  1. UIView继承自UIResponder,用于交互,那么这么看来师承自事件流,属于动作学派;
  2. UIView有一个CALayer的属性,且CALayer继承自NSObject,并且根据苹果文档描述"The base layer class",看来应该是一个layer体系,且layer用于渲染,属于CA阵营,属于图像学派;
  3. layer的代理是view,这样看来真正用于显示的应该是CALayer,鬼知道CALayer是不是跟OpenGL有关系;
  4. 所以如果你要是想做动画,那我给你的建议就是,最好在layer层上做;
  5. 对于,你知道layer是有个叫做anchorPoint属性的,做过cocos2dx的应该知道,你说layer的display里难道没有点opengl的痕迹?
  6. 还记得仿射变换吗,平移+线性变换,layer完全是可以做到的;
  7. layer自身是有绘制能力的,只不过不支持事件响应,但有一点它可以做的到:layer遵从了一个CAMediaTiming的协议,而这个协议就厉害了,配合CACurrentMediaTime,跟系统时钟挂上勾,CPU的时钟周期mach_absolute_time转化成秒数的结果,是一个绝对时间;

上面的结论是瞎扯淡

我们来搞点看的见的

class CircleView: UIView {
    //我要做一个绕圈的动画
    //我不会告诉你我是谷歌过的
    //我觉得你也应该学会
}
    var backgroundLayer: CAShapeLayer?
    var animationLayer: CAShapeLayer?
    //我觉得你应该知道把它们放在哪
    //好吧我还是给你代码对齐吧,这很python
    //给我点颜色,我给你想要的
    //当然,我给你的并不多;
    //我只是一个CAShapeLayer,有形状的框框;
    //有边,有填充;
    func layer(lineColor: UIColor) -> CAShapeLayer {
        
        let layer = CAShapeLayer()
        
        let lineWidth: CGFloat = 5
        let rect = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)
        layer.bounds = rect
        
        //这是个圆
        let path = UIBezierPath(roundedRect: rect, cornerRadius: rect.width / 2)
        layer.lineWidth = lineWidth
        layer.position = CGPoint(x: rect.width / 2, y: rect.height / 2)
        
        layer.path = path.cgPath
        layer.fillColor = UIColor.clear.cgColor
        layer.strokeColor = lineColor.cgColor
        
        layer.strokeStart = 0
        layer.strokeEnd = 1
        
        return layer
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        let bgLayer = self.layer(lineColor: UIColor.green)
        self.layer.addSublayer(bgLayer)
        
        self.backgroundLayer = bgLayer
        
        let animationLayer = self.layer(lineColor: UIColor.orange)
        self.layer.addSublayer(animationLayer)
        animationLayer.isHidden = true
        //关于这个层,我们呆会还要做点其他事情;
        //暂时,它是不被看的到的;
        //所以你可以看到,一个View上是可以添加好多层的;
        //如果你把这些层分区块排列出来,那么我便不加那么多view了;
        //哈哈哈,上面这行我开个玩笑;
        
        self.animationLayer = animationLayer
        
        self.backgroundColor = UIColor.black
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

我们来加一个动画吧!

    let PLAY_ANIMATION_KEY = "animation_key"
    func startAnimation(totalTime: CGFloat) {
        guard totalTime > 0 else {
            return
        }
        guard ((self.animationLayer?.animation(forKey: PLAY_ANIMATION_KEY)) == nil) else {
            return
        }
        
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.duration = CFTimeInterval(totalTime)
        
        animation.delegate = self   //转到它开始动了
        
        animation.fromValue = 0
        animation.toValue = 1
        
        animation.isRemovedOnCompletion = true
        animation.fillMode = kCAFillModeForwards
        
        self.animationLayer?.add(animation, forKey: PLAY_ANIMATION_KEY)
    }
  1. 上面的我们解释一下,首先我们说一个动画是有名字的,用key表示,像上面的PLAY_ANIMATION_KEY
  2. layer添加的动画是CAAnimation类型的,就是说,所有它的子类都可以添加为layer的动画,你可以去挖掘一下CAAnimation一族;
  3. CABasicAnimation添加的动画是需要一个keyPath的,你还需要通过-setFromValue 和-setToValue 来指定一个开始值和结束值,这有点像一个补间动画,输入了起始帧和结束帧,其它马由动画帮你;
image_1ce1gopv01m4lbd41tjr1nel6e39.png-165.6kBimage_1ce1gopv01m4lbd41tjr1nel6e39.png-165.6kB
  1. 我们可以指定CALayer的某个属性名为keyPath,并且对CALayer的这个属性的值进行修改,达到相应的动画效果,随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValue,keyPath内容是CALayer的可动画Animatable属性,关于这个可动画属性,我觉得我还可以去谷歌一车,回头再说吧;
  2. 如果fillMode == kCAFillModeForwards同时removedOnComletion == false,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变;

它开始动了

加入到layer中它会自动开始,并且这开始和结束都有回调哦;

extension CircleView: CAAnimationDelegate {
    func animationDidStart(_ anim: CAAnimation) {
        self.startTime = CACurrentMediaTime()
    }
    
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        
    }
}

暂停和恢复

我们来记录两个时间:

    var startTime: CFTimeInterval!  //动画开始时系统时钟
    var pastTime: CFTimeInterval!   //动画运行过的时间(除去暂停的时间)
    func pauseAnimation(layer: CALayer) {
        let pausetime = layer.convertTime(CACurrentMediaTime(), from: nil)
        layer.timeOffset = pausetime
        layer.speed = 0
        
        print(#function)
        print("start time: \(startTime)")
        print("pause time: \(pausetime)")
        print("系统时钟: \(CACurrentMediaTime())")
        print("以GMT为标准的,2001年一月一日00:00:00这一刻的时间绝对值: \(CFAbsoluteTimeGetCurrent())")
        let pasttime = pausetime - startTime
        print("past time: \(pasttime)")
        self.pastTime = pasttime
    }

所以你只需要搞清楚timeOffsetspeed以及convertTime就可以了;
按我说的做,自己跑一跑,对比下时间,马上清楚,不要再记些什么公式了;

    func resumeAnimation(layer: CALayer) {
        let pausetime = layer.timeOffset
        
        layer.timeOffset = 0
        layer.beginTime = 0
        layer.speed = 1
        
        let begintime = layer.convertTime(CACurrentMediaTime(), to: nil) - pausetime
        layer.beginTime = begintime
        
        print(#function)
        print("系统时钟: \(CACurrentMediaTime())")
        print("begin time: \(begintime)")
    }

那么上面说的需要知道的属性还需要添加一个beginTime

    func stopAnimation() {
        guard self.animationLayer?.animation(forKey: PLAY_ANIMATION_KEY) != nil else {
            return
        }
        
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        self.animationLayer?.timeOffset = 0
        self.animationLayer?.speed = 1
        self.animationLayer?.beginTime = CACurrentMediaTime()
        self.animationLayer?.strokeStart = 0
        self.animationLayer?.strokeEnd = 1
        CATransaction.commit()
        
        self.animationLayer?.removeAnimation(forKey: PLAY_ANIMATION_KEY)
    }

移除我们能看的懂,那么CATransaction呢?

CATransaction?

再去谷歌一车吧!!!

上一篇下一篇

猜你喜欢

热点阅读