iOS 动画——让你的APP动起来

    这篇文章汇总了几种我在学习与工作中遇到的几种动画,在此记录一下以便自己以后复习,同时也希望与大家交流学习。

Talk is cheap, Show me the code.

1、画线动画及沿路径移动
self.view.backgroundColor = UIColor.init(red: 44/255.0, green: 34/255.0, blue: 85/255.0, alpha: 1)
        let bezierPath = UIBezierPath()
        bezierPath.move(to: CGPoint(x: 50, y: 100))
        bezierPath.addLine(to: CGPoint(x: 325, y: 100))
        bezierPath.addLine(to: CGPoint(x: 80, y: 250))
        bezierPath.addLine(to: CGPoint(x: 275, y: 250))
        bezierPath.addLine(to: CGPoint(x: self.view.frame.width/2.0, y: 220))
        bezierPath.addLine(to: CGPoint(x: self.view.frame.width/2.0, y: 400))
        bezierPath.addLine(to: CGPoint(x: 30, y: 400))
        bezierPath.addLine(to: CGPoint(x: 345, y: 400))
        
        //画线
        let shapeLayer = CAShapeLayer()
        shapeLayer.strokeColor = UIColor.purple.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineWidth = 2
        shapeLayer.lineJoin = kCALineJoinRound
        shapeLayer.lineCap = kCALineCapRound
        shapeLayer.path = bezierPath.cgPath
        view.layer.addSublayer(shapeLayer)
        
        let pathAnim = CABasicAnimation(keyPath: "strokeEnd")
        pathAnim.duration = 5.0
        pathAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        pathAnim.fromValue = 0//开始
        pathAnim.toValue = 1//到100%
        pathAnim.autoreverses = true// 动画按原路径返回
        pathAnim.fillMode = kCAFillModeForwards
        //        pathAnim.isRemovedOnCompletion = false
        pathAnim.repeatCount = Float.infinity
        shapeLayer.add(pathAnim, forKey: "strokeEndAnim")
        
        
        //视图沿路径移动
        let moveV = UIImageView.init(image: UIImage.init(named: ""))
        moveV.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
        moveV.backgroundColor = UIColor.red
        moveV.center = CGPoint(x: 50, y: 100)
        view.addSubview(moveV)
        
        let keyAnima = CAKeyframeAnimation.init(keyPath: "position")
//        keyAnima.delegate = self
        keyAnima.duration = 5.0
        keyAnima.timingFunction = CAMediaTimingFunction.init(name: kCAMediaTimingFunctionEaseInEaseOut)
        keyAnima.path = bezierPath.cgPath
        keyAnima.fillMode = kCAFillModeForwards//动画开始之后layer的状态将保持在动画的最后一帧,而removedOnCompletion的默认属性值是 YES,所以为了使动画结束之后layer保持结束状态,应将removedOnCompletion设置为NO
        keyAnima.isRemovedOnCompletion = false
        moveV.layer.add(keyAnima, forKey: "moveAnimation")
2、水面波浪
 lazy var waveDisplaylink = CADisplayLink()
    
    lazy var firstWaveLayer = CAShapeLayer()
    
    lazy var secondWaveLayer = CAShapeLayer()
    
    var firstWaveColor: UIColor?
    
    /// 水纹振幅
    var waveA: CGFloat = 10
    
    /// 水纹周期
    var waveW: CGFloat = 1/30.0;
    
    /// 位移
    var offsetX: CGFloat = 0
    
    /// 当前波浪高度Y
    var currentK: CGFloat = 0
    
    /// 水纹速度
    var waveSpeed: CGFloat = 0
    
    /// 水纹路宽度
    var waterWaveWidth: CGFloat = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.init(red: 44/255.0, green: 34/255.0, blue: 85/255.0, alpha: 1)
        self.view.layer.masksToBounds = true
        
        
        setUpUI()
        
    }
    
    
    func setUpUI () {
        
        // 波浪宽
        waterWaveWidth = self.view.bounds.size.width
        // 波浪颜色
        firstWaveColor = UIColor.green
        // 波浪速度
        waveSpeed = 0.4 / CGFloat(M_PI)
        // 设置闭环的颜色
        firstWaveLayer.fillColor = UIColor.init(colorLiteralRed: 73/255.0, green: 142/255.0, blue: 178/255.0, alpha: 0.5).cgColor
        // 设置边缘线的颜色
        //        firstWaveLayer.strokeColor = UIColor.blue.cgColor
        firstWaveLayer.strokeStart = 0.0
        firstWaveLayer.strokeEnd = 0.8
        // 设置闭环的颜色
        secondWaveLayer.fillColor = UIColor.init(colorLiteralRed: 73/255.0, green: 142/255.0, blue: 178/255.0, alpha: 0.5).cgColor
        // 设置边缘线的颜色
        //        secondWaveLayer.strokeColor = UIColor.blue.cgColor
        secondWaveLayer.strokeStart = 0.0
        secondWaveLayer.strokeEnd = 0.8
        self.view.layer.addSublayer(firstWaveLayer)
        self.view.layer.addSublayer(secondWaveLayer)
        
        // 设置波浪流动速度
        waveSpeed = 0.05
        // 设置振幅
        waveA = 8
        // 设置周期
        waveW = 2 * CGFloat(M_PI) / self.view.bounds.size.width
        // 设置波浪纵向位置
        currentK = self.view.bounds.size.height / 2 //屏幕居中
        
        waveDisplaylink = CADisplayLink(target: self, selector: #selector(getCurrentWave))
        waveDisplaylink.add(to: RunLoop.current, forMode: .commonModes)
        
    }
    
    
    @objc private func getCurrentWave(disPlayLink: CADisplayLink) {
        // 实时位移
        offsetX += waveSpeed
        setCurrentFirstWaveLayerPath()
    }
    
    private func setCurrentFirstWaveLayerPath() {
        // 创建一个路径
        let path = CGMutablePath()
        var y = currentK
        path.move(to: CGPoint(x: 0, y: y))
        
        for i in 0...Int(waterWaveWidth) {
            y = waveA * sin(waveW * CGFloat(i) + offsetX) + currentK
            path.addLine(to: CGPoint(x: CGFloat(i), y: y))
        }
        
        path.addLine(to: CGPoint(x: waterWaveWidth, y: self.view.bounds.size.height))
        path.addLine(to: CGPoint(x: 0, y: self.view.bounds.size.height))
        path.closeSubpath()
        firstWaveLayer.path = path
        
        // 创建一个路径
        let path2 = CGMutablePath()
        var y2 = currentK
        path2.move(to: CGPoint(x: 0, y: y))
        
        for i in 0...Int(waterWaveWidth) {
            y2 = waveA * sin(waveW * CGFloat(i) + offsetX - waterWaveWidth/2 ) + currentK
            path2.addLine(to: CGPoint(x: CGFloat(i), y: y2))
        }
        
        path2.addLine(to: CGPoint(x: waterWaveWidth, y: self.view.bounds.size.height))
        path2.addLine(to: CGPoint(x: 0, y: self.view.bounds.size.height))
        path2.closeSubpath()
        secondWaveLayer.path = path2
    }
3、雪花飘落动画
func snowAnimation() {
        
        //粒子发射器
        let snowEmitter = CAEmitterLayer()
        //粒子发射的位置
        snowEmitter.emitterPosition = CGPoint(x: 100, y: 30)
        //发射源的大小
        snowEmitter.emitterSize = CGSize(width: self.view.bounds.size.width, height: 0)
        //发射模式
        snowEmitter.emitterMode = kCAEmitterLayerOutline
        //发射源的形状
        snowEmitter.emitterShape = kCAEmitterLayerLine
                
        //创建雪花粒子
        let cell = CAEmitterCell()
        //粒子的名称
        cell.name = "snow"
        //粒子参数的速度乘数因子。越大出现的越快
        cell.birthRate = 1.0
        //存活时间
        cell.lifetime = 120.0
        //粒子速度
        cell.velocity = -10
        //粒子速度范围
        cell.velocityRange = 10
        //粒子y方向的加速度分量
        cell.yAcceleration = 3
        //周围发射角度
        cell.emissionRange = CGFloat(0.5 * M_PI)
        //粒子旋转角度范围
        cell.spinRange = CGFloat(0.25 * Double.pi)
        //粒子图片
        cell.contents = UIImage.init(named: "snow")?.cgImage
        //粒子颜色
        cell.color = UIColor.white.cgColor
        
        //设置阴影
        snowEmitter.shadowOpacity = 1.0
        snowEmitter.shadowRadius = 0.0
        snowEmitter.shadowOffset = CGSize(width: 0.0, height: 1.0)
        snowEmitter.shadowColor = UIColor.white.cgColor
            
        // 将粒子添加到粒子发射器上
        snowEmitter.emitterCells = [cell]
        
        self.view.layer .addSublayer(snowEmitter)
    }
4、烟花动画
/// 烟花粒子动画
    func fireworksAnimation() {
        
        //分为3种粒子,子弹粒子,爆炸粒子,散开粒子
        let fireworkEmitter = CAEmitterLayer()
        fireworkEmitter.emitterPosition = CGPoint(x: self.view.bounds.size.width/2.0, y: self.view.bounds.size.height)
        fireworkEmitter.emitterSize = CGSize(width: self.view.bounds.size.width/2.0, height: 0.0)
        fireworkEmitter.emitterMode = kCAEmitterLayerOutline
        fireworkEmitter.emitterShape = kCAEmitterLayerLine
        fireworkEmitter.renderMode = kCAEmitterLayerAdditive
        fireworkEmitter.seed = (arc4random()%100)+1
        
        // Create the rocket
        let rocket = CAEmitterCell()
        
        rocket.birthRate = 1.0
        rocket.velocity = 380
        rocket.velocityRange = 100
        rocket.yAcceleration = 75
        rocket.lifetime = 1.02
        
        //小圆球图片
        rocket.contents         = UIImage.init(named: "dot")?.cgImage
        rocket.scale            = 0.2;
        rocket.color            = UIColor.yellow.cgColor
        rocket.greenRange       = 1.0;      // different colors
        rocket.redRange         = 1.0;
        rocket.blueRange        = 1.0;
        rocket.spinRange        = CGFloat(M_PI);        // slow spin
        
        // the burst object cannot be seen, but will spawn the sparks
        // we change the color here, since the sparks inherit its value
        let burst = CAEmitterCell()
        
        burst.birthRate         = 1.0;      // at the end of travel
        burst.velocity          = 0;        //速度为0
        burst.scale             = 2.5;      //大小
        burst.redSpeed = -1.5// shifting
        burst.blueSpeed = +1.5
        burst.greenSpeed = +1.0
        burst.lifetime          = 0.35;     //存在时间
        
        // and finally, the sparks
        let spark = CAEmitterCell()
        
        spark.birthRate         = 400;
        spark.velocity          = 125;
        spark.emissionRange     = CGFloat(2 * M_PI);    // 360 度
        spark.yAcceleration     = 75;       // gravity
        spark.lifetime          = 3;
        //星星图片
        spark.contents          = UIImage.init(named: "star")?.cgImage
        spark.scaleSpeed        = -0.2;
        spark.greenSpeed = -0.1;
        spark.redSpeed = 0.4;
        spark.blueSpeed = -0.1;
        spark.alphaSpeed = -0.25;
        spark.spin = CGFloat(2 * M_PI)
        spark.spinRange = CGFloat(2 * M_PI)
        
        // 3种粒子组合,可以根据顺序,依次烟花弹-烟花弹粒子爆炸-爆炸散开粒子
        fireworkEmitter.emitterCells    = [rocket];
        rocket.emitterCells             = [burst];
        burst.emitterCells              = [spark];
        self.view.layer.addSublayer(fireworkEmitter)
    }

5、火苗效果
/// 火焰效果
    func fireAnimation() {
        
        // 发射器在xy平面的中心位置
        fireEmitter.emitterPosition = view.center
        // 发射器的尺寸大小
        //        fireEmitter.emitterSize = CGSize(width: 20, height: 60)
        // 发射器的发射模式
        //        fireEmitter.emitterMode = kCAEmitterLayerOutline
        //        // 发射器的形状
        fireEmitter.emitterShape = kCAEmitterLayerCircle
        // 发射器渲染模式
        fireEmitter.renderMode = kCAEmitterLayerAdditive
        
        // 发射单元 - 火焰
        let fire = CAEmitterCell()
        // 粒子的创建速率,默认为1/s。
        fire.birthRate = 200
        // 粒子存活时间
        fire.lifetime = 0.2
        // 粒子的生存时间容差
        fire.lifetimeRange = 0.5
        fire.color = UIColor.init(colorLiteralRed: 0.8, green: 0.4, blue: 0.2, alpha: 0.1) .cgColor
        fire.contents = UIImage(named: "fire.png")?.cgImage
        fire.name = "fire"
        // 粒子的速度
        fire.velocity = 35
        // 粒子动画的速度容差
        fire.velocityRange = 10
        // 粒子在xy平面的发射角度
        fire.emissionLongitude = CGFloat(M_PI + M_PI_2)
        // 粒子发射角度的容差
        fire.emissionRange = CGFloat(M_PI_2)
        // 缩放速度
        fire.scaleSpeed = 0.3
        // 旋转度
        //        fire.spin = 0.2
        
        fireEmitter.emitterCells = [fire]
        view.layer.addSublayer(fireEmitter)
    }
6、流星动画
var timer : Timer!
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        
        timer.invalidate()
        timer = nil
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.init(red: 44/255.0, green: 34/255.0, blue: 85/255.0, alpha: 1)

        // Do any additional setup after loading the view.
        timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(liuXingAnimation), userInfo: nil, repeats: true)
    }
    
    ///流星动画
    func liuXingAnimation() {
        let rect    = CGRect(x: self.view.bounds.size.width, y: self.view.bounds.height, width: 100, height: 100)
        let emitter = CAEmitterLayer()
        emitter.frame = rect
        self.view.layer.addSublayer(emitter)
        emitter.renderMode   = kCAEmitterLayerAdditive//合并粒子重叠部分的亮度使得看上去更亮
        emitter.emitterPosition     = CGPoint(x: rect.width/2, y:rect.height/2)
        let            cell  = CAEmitterCell()
        let           image  = UIImage(named: "liuxing")!
        cell.contents        = image.cgImage
        cell.birthRate       = 500 //每秒产生150个粒子
        cell.lifetime        = 5.0
        cell.color           = UIColor.init(red: 1, green: 1, blue: 1, alpha: 0.5).cgColor
        
        cell.alphaSpeed      = -0.6//粒子的透明度每过一秒就减少0.4
        
        cell.velocity        = 50
        cell.velocityRange   = 20   //初始速度值变化的范围 30 ~ 70
        cell.emissionLongitude = CGFloat(-Double.pi/2/180*45)//向上(x-y平面的发射方向)
        cell.emissionRange   = CGFloat(Double.pi/2/180)//围绕发射方向的弧度数
        cell.scale           = 0.2
        emitter.emitterCells = [cell]
        self.view.layer.addSublayer(emitter)
        
        let aPath = UIBezierPath()
        let height : UInt32 = UInt32(self.view.bounds.height-50)
        let randomY = CGFloat(arc4random()%height)
        
        aPath.move(to: CGPoint(x: self.view.bounds.width, y: randomY))
        aPath.addLine(to: CGPoint(x: -100, y: self.view.bounds.width/2+randomY))
        
        let animation = CAKeyframeAnimation(keyPath: "position")
        animation.duration = 5;
        animation.timingFunction = CAMediaTimingFunction.init(name: kCAMediaTimingFunctionEaseIn)
        animation.repeatCount = 1
        animation.path = aPath.cgPath
        //        animation.calculationMode = kCAAnimationPaced
        animation.setValue("liuxing", forKey: "liuxing")
        //                animation.rotationMode = kCAAnimationRotateAutocell
        emitter.add(animation, forKey: "moveTheSquare")
//        animation.delegate = self
    }

7、Lottie加载动画

使用这个动画需要在项目中加入Lottie三方库,我在项目中使用了cocoapod。只需要在其中加入 pod 'lottie-ios' 即可。 其中的动画使用AE制作生成的 json 文件。 你也可以去这里下载你喜欢的动效。

animationArr = ["LottieLogo1_masked", "9squares-AlBoardman", "HamburgerArrow", "IconTransitions", "LottieLogo1", "LottieLogo2", "MotionCorpse-Jrcanest", "PinJump"];
        
        let temp = Int(arc4random()%(UInt32(animationArr.count)))
        animationStr = animationArr[temp]
        
        contentView.layer.masksToBounds = true
        animationView = LOTAnimationView(name: animationStr)
        animationView.frame = self.contentView.bounds
        animationView.contentMode = .scaleAspectFill
        self.contentView.addSubview(animationView)
        animationView.play{ (finished) in
            // Do Something
        }

8、后续待更新...

9、后记

如果大家对这些效果有兴趣,可以去github下载源码,若对您有帮助的话,请多多star哦!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,800评论 25 707
  • 父亲,一个对我来说亲切而又陌生的称呼。 父亲,只在我梦里存在的人物。已经记不清他的音容笑貌。 父亲,每当别人这么称...
    七叶薄荷阅读 335评论 0 0
  • LinkedList<E> 是一个类 实现的接口:List、Collection、Iterable、Seriali...
    镜中无我阅读 200评论 0 0
  • 继承:AFHTTPRequestSerializer:NSObject 声明处:AFURLRequestSeria...
    _阿南_阅读 1,110评论 0 1