美文网首页iOS开发iOS猛码计划iOS图形图像视频处理
iOS实践:通过核心动画完成过山车

iOS实践:通过核心动画完成过山车

作者: 非典型技术宅 | 来源:发表于2017-05-15 21:51 被阅读7562次

    呼哧,终于今天到了最后一篇啦,也是醉了,弄了两三个月。从最开始计划只写三篇就好了,结果自己没把握好,一点点加成了今天这个样子。因为增加的内容太多,也差点变成太监文,不过好在没有放弃自己。所以各位行行好,要是看上去觉得还不错,就点个赞,打赏小的点儿。这玩意儿写的我是头发乱发,两眼通红。哇哇哇哇~

    接下来要写啥,确实还没想好。现在的感觉就是胸口的一块大石头没有了,要去尽情的嗨皮!!!!

    之前在一个网站上看到了一个HTML5/SVG实现的过山车动画,点这里看网页版。 觉得很棒,想想咱们iOS也完全可以实现,正好还可以全面回顾一下之前分享过的关于iOS中间动画系列会使用到的各个内容。不过今天的内容稍微有点多,我呢尽量只说最重要的部分,这里面所有的内容都是通过代码绘制出来的。

    实现后的效果图:(这也是为了简书抓图用的,不知道为啥现在如果是gif,简书不会当成文章的缩略图。好心烦~)


    Paste_Image.png

    完成后的动态图:

    过山车.gif

    1. 思路和所用到的内容

    1.1 思维导图

    过山车思维导图.png

    1.2 所用到的知识

    在这里,我们使用到了:

    • CALayer、CAShapeLayer、CAGradientLayer三种layer。
    • UIBeizerPath的使用,包括二次贝塞尔曲线、三次贝塞尔曲线的应用,使用BeizerPath绘画圆。
    • CAKeyframeAnimation的应用。
      所有上面的内容之前的文章里面都有仔细的写过怎么使用哒,要是不清楚的小伙伴们可以翻翻之前的文章。几乎绝大部分的内容都在iOS动画系列这个里面。

    1.3 最耗费时间的地方

    特别想拿出来说说这个最耗费时间的东东。想都不用想,当然是火车轨道比较麻烦啦。但是这个对我来说还不是花费时间最长的,花费时间最长的居然是那两座雪山。为了绘画那两座雪山,还有山上面的积雪简直是费老鼻子劲了。所以火车轨道、雪山俺会单独拿出两小节来说说这个令人头疼的玩意。

    2. 辅助元素的创建(背景颜色、草坪、大地、小树、云彩)

    辅助元素完成后的效果图:


    Paste_Image.png

    2.1 渐变的天空背景

    使用CAGradientLayer进行设置,就是一个最基本的应用,让成45度角进行变换。
    受篇幅限制,代码我就不贴了,在源代码里面自己看吧。注释写的还算比较详细啦,自我感觉。哈哈~
    CAGradientLayer的基础部分可以看看这个文章,第九篇:iOS动画系列之九:实现点赞的动画及播放起伏指示器

    2.2 草坪

    主要是使用两个二次贝塞尔曲线实现的。

        [leftLawnPath moveToPoint:leftStartPoint];
        [leftLawnPath addLineToPoint:CGPointMake(0, k_SIZE.height - 100)];
        
        //    画一个二次贝塞尔曲线
        [leftLawnPath addQuadCurveToPoint:CGPointMake(k_SIZE.width / 3.0, k_LAND_BEGIN_HEIGHT) controlPoint:CGPointMake(k_SIZE.width / 5.0, k_SIZE.height - 100)];
        
        leftLawn.path = leftLawnPath.CGPath;
        leftLawn.fillColor = [UIColor colorWithDisplayP3Red:82.0 / 255.0 green:177.0 / 255.0 blue:52.0 / 255.0 alpha:1.0].CGColor;
        [self.view.layer addSublayer:leftLawn];
        
        CAShapeLayer *rightLawn = [[CAShapeLayer alloc] init];
        UIBezierPath *rightLawnPath = [[UIBezierPath alloc] init];
    

    二次贝塞尔曲线

    -(void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
    
    二次贝塞尔曲线.png

    2.3 云彩动画的实现

    云彩的漂浮就是通过CAKeyframeAnimation,让其沿着绘画的直线曲线进行运动。

        CALayer *cloud = [[CALayer alloc]init];
        cloud.contents = (__bridge id _Nullable)([UIImage imageNamed:@"cloud"].CGImage);
        cloud.frame = CGRectMake(0, 0, 63, 20);
        [self.view.layer addSublayer:cloud];
        
        UIBezierPath *cloudPath = [[UIBezierPath alloc] init];
        [cloudPath moveToPoint:CGPointMake(k_SIZE.width + 63, 50)];
        [cloudPath addLineToPoint:CGPointMake(-63, 50)];
        
        CAKeyframeAnimation *ani = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        ani.path = cloudPath.CGPath;
        ani.duration = 30;
        ani.autoreverses = NO;
        ani.repeatCount = CGFLOAT_MAX;
        ani.calculationMode = kCAAnimationPaced;
        
        [cloud addAnimation:ani forKey:@"position"];
    

    2.4 大地、小树的实现

    就是分别创建了大地和小树的CALayer,为了使用不同的方法,大地我们通过backgroundColor填充了图片。小树的Layer,我们通过设置contents进行了图片填充。

    //大地的背景填充
        _landLayer.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ground"]].CGColor;
    //小树的背景设置
        treeLayer.contents = (__bridge id _Nullable)(tree.CGImage);
    
    

    为了能够层次不齐的放置小树,所以用了几个循环,在不同的y轴位置,添加了若干个小树。

    3. 雪山的实现

    雪山的实现其实并不是特别难,主要是很繁琐。自己还忘记了一个初中的小公式,在这个地方耽误了点时间。

    3.1 雪山的思路

    以一座雪山为例子,乍一看,以为雪山分成了两部分:雪山下半部分+山顶的雪。很快的,自己就放弃了这个思路。这样的话,中间的曲线部分画起来简直就要了人命了。所以就换了一个思路:先画一个全部被雪覆盖满的山体,然后在这个之上再画一个有棕色土地的部分。

    完成后是这个样子的:


    雪山.png

    3.2 雪山绘画的步骤

    STEP ONE:


    覆盖满白雪的雪山.png

    STEP TWO:


    给雪山添加棕色山体.png

    STEP THREE:


    第二坐被白雪覆盖的雪山.png

    STEP FOUR:


    雪山.png

    3.3 需要注意的点

    在画山的过程中,最复杂的是找到山上左右两侧山坡上边缘的那个点的CGPoint。
    以第一座山左边上坡上开始有雪的那个点来说。其实要计算的是从山脚到山顶两点之间的连线上任意一点的坐标。知道了X轴坐标,要计算Y轴坐标。
    这个就是咱们初中学到的计算公式,y = kx + b。 k是斜率,b是截距。起点、终点已经知道了,可以很容易的计算出斜率k。根据k,再计算出b。这样给出这条线段上任意一点x轴坐标,就能轻易的算出y轴坐标了。xy都知道了,CGPoint不就知道了嘛。

    - (CGPoint)calculateWithXValue:(CGFloat)xvalue startPoint:(CGPoint)startPoint endpoint:(CGPoint)endpoint{
        //    求出两点连线的斜率
        CGFloat k = (endpoint.y - startPoint.y) / (endpoint.x - startPoint.x);
        CGFloat b = startPoint.y - startPoint.x * k;
        CGFloat yvalue = k * xvalue + b;
        return CGPointMake(xvalue, yvalue);
    }
    

    3.4 以左边山为例

        //    左边第一座山顶,其实就是一个白色的三角形
        CAShapeLayer *leftSnowberg = [[CAShapeLayer alloc] init];
        UIBezierPath *leftSnowbergPath = [[UIBezierPath alloc] init];
        
        //    把bezierpath的起点移动到雪山左下角
        [leftSnowbergPath moveToPoint:CGPointMake(0, k_SIZE.height - 120)];
        
        //    画一条线到山顶
        [leftSnowbergPath addLineToPoint:CGPointMake(100, 100)];
        
        //    画一条线到右下角->左下角->闭合
        [leftSnowbergPath addLineToPoint:CGPointMake(k_SIZE.width / 2, k_LAND_BEGIN_HEIGHT)];
        [leftSnowbergPath addLineToPoint:CGPointMake(0, k_LAND_BEGIN_HEIGHT)];
        [leftSnowbergPath closePath];
        
        leftSnowberg.path = leftSnowbergPath.CGPath;
        leftSnowberg.fillColor = [UIColor whiteColor].CGColor;
        [self.view.layer addSublayer:leftSnowberg];
        
    
        //    开始画山体没有被雪覆盖的部分
        CAShapeLayer *leftSnowbergBody = [[CAShapeLayer alloc] init];
        UIBezierPath *leftSnowbergBodyPath = [[UIBezierPath alloc] init];
        
        //    把bezierpath的起点移动到雪山左下角相同的位置
        CGPoint startPoint = CGPointMake(0, k_SIZE.height - 120);
        CGPoint endPoint = CGPointMake(100, 100);
        CGPoint firstPathPoint = [self calculateWithXValue:20 startPoint:startPoint endpoint:endPoint];
        [leftSnowbergBodyPath moveToPoint:startPoint];
        
        [leftSnowbergBodyPath addLineToPoint:firstPathPoint];
        [leftSnowbergBodyPath addLineToPoint:CGPointMake(60, firstPathPoint.y)];
        [leftSnowbergBodyPath addLineToPoint:CGPointMake(100, firstPathPoint.y + 30)];
        [leftSnowbergBodyPath addLineToPoint:CGPointMake(140, firstPathPoint.y)];
        [leftSnowbergBodyPath addLineToPoint:CGPointMake(180, firstPathPoint.y - 20)];
        
        CGPoint secondPathPoint = [self calculateWithXValue:(k_SIZE.width / 2 - 125) startPoint:endPoint endpoint:CGPointMake(k_SIZE.width / 2, k_LAND_BEGIN_HEIGHT)];
        [leftSnowbergBodyPath addLineToPoint:CGPointMake(secondPathPoint.x - 30, firstPathPoint.y)];
        
        [leftSnowbergBodyPath addLineToPoint:secondPathPoint];
        
        [leftSnowbergBodyPath addLineToPoint:CGPointMake(k_SIZE.width / 2, k_LAND_BEGIN_HEIGHT)];
        [leftSnowbergBodyPath addLineToPoint:CGPointMake(0, k_LAND_BEGIN_HEIGHT)];
        [leftSnowbergBodyPath closePath];
        
        leftSnowbergBody.path = leftSnowbergBodyPath.CGPath;
        UIColor *snowColor = [UIColor colorWithDisplayP3Red:139.0 /255.0 green:92.0 /255.0 blue:0.0 /255.0 alpha:1.0];
        leftSnowbergBody.fillColor = snowColor.CGColor;
        [self.view.layer addSublayer:leftSnowbergBody];
    

    4. 轨道的实现

    Paste_Image.png

    轨道这部分主要就是花了几个二次贝塞尔曲线,三次贝塞尔曲线。那我们用最复杂的绿色这个带圆圈的轨道来分享一下。它是由三部分组成的,考虑到在最后我们会让过山车从右边进入,跑到左边去,我们就从最右侧开始画起。
    最右侧有一个二次贝塞尔曲线,中间画了一个圆圈,左边是一个三次贝塞尔曲线。画完了之后,使用图片进行填充就完成了90%的工作。
    为了让轨道看起来更好看一些,对轨道的边缘进行镂空,内部填充色变成透明。

    4.1 绘画的步骤

    1,先画最右边的弧线,一个二次贝塞尔曲线。


    Paste_Image.png

    2,画一个圆圈。注意控制圆的半径以及圆心的位置。

    Paste_Image.png

    3,画最左边的那条曲线,一个三次贝塞尔曲线。其实就是有两个控制点的曲线。

    Paste_Image.png

    4,将曲线进行闭合。

    Paste_Image.png

    5,把曲线的背景颜色填充为准备好的小格子。

    Paste_Image.png

    6,为了让轨道看起来更加逼真,让曲线的边缘变成虚线。

    Paste_Image.png

    4.2 三次贝塞尔曲线

    - (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
    

    起点用moveToPoint的方法进行设定,endPoint:贝塞尔曲线的终点;controlPoint1:控制点1;controlPoint2:控制点2。

    曲线是由起点趋向控制点1,之后趋向控制点2,最后到达终点的曲线。

    Paste_Image.png

    4.3 代码实现

    绿色轨道绘制部分的代码:

        //    绿色铁轨的火车从右侧进入,所以从右侧开始绘画。需要画三条曲线,右边一条+中间的圆圈+左边一条
        UIBezierPath *path = [[UIBezierPath alloc] init];
        [path moveToPoint:CGPointMake(k_SIZE.width + 10, k_LAND_BEGIN_HEIGHT)];
        [path addLineToPoint:CGPointMake(k_SIZE.width + 10, k_SIZE.height - 70)];
        [path addQuadCurveToPoint:CGPointMake(k_SIZE.width / 1.5, k_SIZE.height - 70) controlPoint:CGPointMake(k_SIZE.width - 150, 200)];
    
        //    画圆圈
        [path addArcWithCenter:CGPointMake(k_SIZE.width / 1.6, k_SIZE.height - 140) radius:70 startAngle:M_PI_2 endAngle:2.5 * M_PI clockwise:YES];
        
        [path addCurveToPoint:CGPointMake(0, k_SIZE.height - 100) controlPoint1:CGPointMake(k_SIZE.width / 1.8 - 60, k_SIZE.height - 60) controlPoint2:CGPointMake(150, k_SIZE.height / 2.3)];
    
        [path addLineToPoint:CGPointMake(- 10, k_LAND_BEGIN_HEIGHT)];
    
        _greenTrack.path = path.CGPath;
        _greenTrack.fillColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"green"]].CGColor;
    

    轨道边缘镂空的代码:

        //    为了能够让弧线更好看一点,需要加入镂空的虚线
        CAShapeLayer *trackLine = [[CAShapeLayer alloc] init];
        trackLine.lineCap = kCALineCapRound;
        trackLine.strokeColor = [UIColor whiteColor].CGColor;
        
        trackLine.lineDashPattern = @[@1.0,@6.0];
        trackLine.lineWidth = 2.5;
        trackLine.fillColor = [UIColor clearColor].CGColor;
        trackLine.path = path.CGPath;
        [_greenTrack addSublayer:trackLine];
    

    -----------------------写在最后-------------------------------------------------
    如果看官你觉得确实这篇文章有点收获,那就给个赞或者粉一下呗。再进一步,如果您认可小的这点儿辛苦,打赏点可乐钱,小的愿意给爷笑一个&

    OC版本的源代码在这里:http://git.oschina.net/atypical/rollercoaster
    群众要是呼声比较高,那就还是老惯例,随后再写swift版本的。要是没啥人有兴趣,俺就拿着大家大赏的银子去买点小片儿看看,乐呵乐呵。

    -----------------------华丽分割线,iOS动画系列全集链接-------------------------------------------------
    第一篇:iOS动画系列之一:通过实战学习CALayer和透视的原理。做一个带时分秒指针的时钟动画(上)
    第二篇:iOS动画系列之二:通过实战学习CALayer和透视的原理。做一个带时分秒指针的时钟动画。包含了OC和Swift两种源代码(下)
    第三篇:iOS动画系列之三:Core Animation。介绍了Core Animation的常用属性和方法。
    第四篇:CABasic Animation。iOS动画系列之四:基础动画之平移篇
    第五篇:CABasic Animation。iOS动画系列之五:基础动画之缩放篇&旋转篇
    第六篇:iOS动画系列之六:利用CABasic Animation完成带动画特效的登录界面
    第七篇:iOS动画系列之七:实现类似Twitter的启动动画
    第八篇:iOS动画系列之八:使用CAShapeLayer绘画动态流量图
    第九篇:iOS动画系列之九:实现点赞的动画及播放起伏指示器
    第十篇:实战系列:绘制过山车场景

    相关文章

      网友评论

      • 天马流星权:大佬,我现在有个需求 ,就是要动态填充曲线的下方,,就是类似于你上面的过山车,只不过那个过山车的轨道下面是填充色,而且要跟着车慢慢填充.就像心电图,慢慢填充那样子...想破头了,想不出什么思路.能给个思路么????
        非典型技术宅:之前我的文章有一个动态流量图的,你看看。基本上我的初步的思路会是轨道的填充色也是一个层,按照车的轨迹来计算轨道的轨迹。填充。
      • 得得得得得儿:我都是先赞后看
        非典型技术宅:@厉害了我的小码哥 哈哈哈。多谢大佬点赞
      • 小小志伟:你的思维导图用的是哪个软件,免费的不
        非典型技术宅:@小小志伟 Mindnode
      • 江山此夜寒:大佬,还有一个问题,核心动画的交互事件是怎么写的
        江山此夜寒:@非典型技术宅 点击,拖动之类手势
        非典型技术宅:@江山此夜寒 你说的交互事件是指什么?
      • 江山此夜寒:大佬,程序进入后台,再进入前台,动画就没了啊,需要再哪里设置
        江山此夜寒:@非典型技术宅 大神,处理是怎么做的
        非典型技术宅:@江山此夜寒 对啊,因为没有做处理啊
      • 偏偏就是祢:你的demo为什么运行后UI界面会乱
        非典型技术宅:@一颗浮躁的心想静下来 因为我没有做屏幕适配。需要横屏。
      • 谢衣丶:整个系列看完了..从基础到这里 写的很完整 !!
        非典型技术宅:@谢衣丶 哇塞,好感动的!!!!真的有人会从头看!!!!
      • 公子墨香:真心佩服,把一项技术钻研的这么精细透彻!
      • 布袋的世界:高质量啊!简书真是个好地方!
        布袋的世界:@非典型技术宅 别介 大牛
        非典型技术宅:@布袋的世界 谢谢大爷打赏
      • ethan_cun:大神,怎么设置黄色和绿色轨道填充图片的大小,就是格子图片的大小?
        ethan_cun:@非典型技术宅 curveLayer.fillColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"yellow"]].CGColor; 我这样填充 显示图片超级大 怎么回事
        非典型技术宅:@EthanCun 填充会自动平铺的。可以下载源代码看哈,注释也是写的很详细呐。
      • 7eg:为何你那么叼:clap:
        非典型技术宅:@来自深圳的代码搬运工 汪汪汪,主人赏块肉我就叼走了
      • 子达如何:这种动画应该交给设计师完成,然后直接导出执行代码:)推荐工具CoreAnimator。
        不过,工程师能掌握设计师的技能也是非常牛逼的。
      • a1634a52a033:我可能学的是假的IOS
        非典型技术宅:@Pard 真的iOS 都不写这玩意儿:stuck_out_tongue_winking_eye:
      • 046b43ba1867:真希望不是最后一篇啊...以泪洗面:sob::sob:
        非典型技术宅:@mynSoo 哈哈哈哈哈。下个系列就开始了。:stuck_out_tongue_closed_eyes:
      • f1535a71d2d8:不错哦 小伙
        非典型技术宅:@浮尘4848 谢谢老大支持
      • WolfTin:厉害了!我的歌~
        非典型技术宅:@TianMaoTao 抬举抬举
      • 格调main:看着很不错,留个脚印
        非典型技术宅:客官,除了脚印,再留点什么吧:kissing_heart:
      • 凯文Kevin21:可以把MindNode 这个思维导图破解版安装包发个百度云链接给我吗。 或者发一下我邮箱, 260595314@qq.com 谢了。 我在网上找了好久都没找到。
        非典型技术宅:@爱哭的毛毛虫_ 刚才搜索了一下淘宝有卖哒,才几块钱。不要费尽找了,直接买一个算了。
        凯文Kevin21:@非典型技术宅 soga ,好吧。
        非典型技术宅:@爱哭的毛毛虫_ 这个。。。那个。。。。木有啦。当时也是身边有朋友有,我就直接拿来用了。
      • petter102:辛苦了。
        非典型技术宅:@petter102 首长辛苦,小的不累。
      • Clark_new:写的很详细,有机会仔细研究下,对动画比较陌生!
        Clark_new:@非典型技术宅 嗯啊 谢谢啦
        非典型技术宅:@Clark_new 可以看看这个系列之前的文章。前面的都非常基础。
      • saman0:厉害
        非典型技术宅:@saman0 你swift 写的不错啊
      • 殇永:今天是蜂筹上线30天,只要复制粘贴此条消息到3个微信群/QQ群,你的支付宝里就会增加500元。我试过了,蜂筹上线30天是真的,但500块是假的,而且还会被骂。
        非典型技术宅:@殇永 妈呀,这广告也是醉了
      • layjoy:厉害了:+1:
        非典型技术宅:@layjoy 咱们这是在对暗号嘛?:stuck_out_tongue_closed_eyes:
        layjoy: @非典型技术宅 我命由我
        非典型技术宅:@layjoy 德玛西亚
      • Dark_Angel:厉害了:+1:
        非典型技术宅:@Dark_Angel 扎西德勒
      • a8304ea8f57e:求小片资源呀:stuck_out_tongue_winking_eye:
        非典型技术宅:@林同 调皮!?同求资源
      • CrazySteven:这个可以研究一下~niubility。。。
        非典型技术宅:@CrazySteven 其实没啥技术含量,有点耐心就好
      • W_C__L:英雄所见略同啊,一年前我也写过这个动画,不过文章没你写的那么细,要像你学习…http://blog.csdn.net/wang631106979/article/details/51737456
        非典型技术宅:@北国之恋秦 人家才是大牛,一年前就写过啦!向大牛学习。
        北国之恋秦: @W_C__L 都是大牛
        非典型技术宅:@W_C__L 厉害!!!!哈哈哈,那我就不用写swift 的了。大家看你的就好。
      • 落影loyinglin:666 写代码玩出了mc的感觉
        非典型技术宅:@落影loyinglin 我羞射了
      • 九零后_:思维导图用的什么工具?
        非典型技术宅:@九零后_ MindManager老牌思维导图,功能好强大。Xmind也是大厂作品。我用MindNode就只是因为当时正好别人手头上面有这个安装包,就一直用了。
        九零后_:@非典型技术宅 求推荐:smile:
        非典型技术宅:@九零后_ MindNode。 MAC上有很多还不错用的导图工具哈。
      • MR_LIXP:火钳刘明
        非典型技术宅:@MR_LP 哈哈,承蒙抬举。还查了一下火钳刘明啥意思。。。。:sweat:
      • nenhall:这个流逼:+1::+1::+1::+1:
        非典型技术宅:@neghao 一起学习一起进步哈。

      本文标题:iOS实践:通过核心动画完成过山车

      本文链接:https://www.haomeiwen.com/subject/bmckxxtx.html