一、动画过程分析

1、拆分动画

正常动画效果如下:

操作 现象 结论
放慢动画 这个效果可以反复多看几次,观察一下这个动画是由那几部分组成的,然后再往下看
添加辅助颜色 可以看出,整个动画有四部分组成:1、左侧竖线 2、右侧竖线 3、三角形 4、底部过渡圆弧
只保留竖线 竖线的动画包括两部分,一部分是开始的“预备”动画和结束时的“惯性”动画;第二步分是在动画过程中和圆弧、三角的“衔接”动画
只保留三角动画 三角动画正向过程是从左侧竖线头部,顺时针绘制一个三角形;逆向过程就是这个动画的逆向
只保留弧线 弧线动画主要的作用就是在正向和逆向过程中桥接左右竖线

2、总结

动画是由两部分组成:
1、竖线在开始时的预备动画和结束时的惯性动画
2、竖线、三角形和弧线的衔接动画

二、动画开发

设:暂停—>播放的状态为正向,播放—>暂停为逆向。
创建一个坐标系,这里设整个容器的边长是a,坐标系的原点为容器的左上角。

1、竖线的准备和惯性动画

为了更清晰的观察,预备动画和其他动画间做了1s的延时。

这个需要多看几遍。可以看出来:
左侧竖线是经历了两个过程:
第一个动画:底部不变,上部分变短;第二个动画:整体向上位移。
右侧竖线也有两个过程:
第一个动画:整体向上位移;第二个动画:底部不变,顶部缩短。

起始状态 第一个过程结束状态 第二个过程结束状态

根据上图,设左侧竖线为L1,方向由上至下,右侧竖线为L2方向由下至上,通过贝塞尔曲线绘制竖线,需要确定竖线的起点和终点。
L1 起始位置起点为:(0.2a,0)终点为:(0.2a,a)
L1 第二个位置起点为:(0.2a,0.4a)终点为:(0.2a,a)
L1 第三个位置起点为:(0.2a,0.2a)终点为:(0.2a,0.8a)

L2 起始状态起点为:(0.8a,a)终点为:(0.8a,0)
L2 第二个位置起点为:(0.8a,0.8a)终点为:(0.8a,-0.2a)
L2 第三个位置起点为:(0.8a,0.8a)终点为:(0.8a,0.2a)

准备动画就是将竖线居中变短的过程,设竖线动画总时长为:positionDuration
代码如下:

//暂停-》播放竖线动画
- (void)linePositiveAnimation {CGFloat a = self.bounds.size.width;//左侧缩放动画UIBezierPath *leftPath1 = [UIBezierPath bezierPath];[leftPath1 moveToPoint:CGPointMake(0.2*a,0.4*a)];[leftPath1 addLineToPoint:CGPointMake(0.2*a,a)];_leftLineLayer.path = leftPath1.CGPath;[_leftLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];//右侧竖线位移动画UIBezierPath *rightPath1 = [UIBezierPath bezierPath];[rightPath1 moveToPoint:CGPointMake(0.8*a, 0.8*a)];[rightPath1 addLineToPoint:CGPointMake(0.8*a,-0.2*a)];_rightLineLayer.path = rightPath1.CGPath;[_rightLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];dispatch_after(dispatch_time(DISPATCH_TIME_NOW,  positionDuration/2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){//左侧位移动画UIBezierPath *leftPath2 = [UIBezierPath bezierPath];[leftPath2 moveToPoint:CGPointMake(a*0.2,a*0.2)];[leftPath2 addLineToPoint:CGPointMake(a*0.2,a*0.8)];_leftLineLayer.path = leftPath2.CGPath;[_leftLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];//右侧竖线缩放动画UIBezierPath *rightPath2 = [UIBezierPath bezierPath];[rightPath2 moveToPoint:CGPointMake(a*0.8,a*0.8)];[rightPath2 addLineToPoint:CGPointMake(a*0.8,a*0.2)];_rightLineLayer.path = rightPath2.CGPath;[_rightLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];});
}

而结束时的惯性动画,无非就是竖线由短边长的过程,和预备动画大同小异。

代码如下:

//播放-》暂停竖线动画
- (void)lineInverseAnimation {CGFloat a = self.bounds.size.width;//左侧位移动画UIBezierPath *leftPath1 = [UIBezierPath bezierPath];[leftPath1 moveToPoint:CGPointMake(0.2*a,0.4*a)];[leftPath1 addLineToPoint:CGPointMake(0.2*a,a)];_leftLineLayer.path = leftPath1.CGPath;[_leftLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];//右侧竖线位移动画UIBezierPath *rightPath1 = [UIBezierPath bezierPath];[rightPath1 moveToPoint:CGPointMake(0.8*a, 0.8*a)];[rightPath1 addLineToPoint:CGPointMake(0.8*a,-0.2*a)];_rightLineLayer.path = rightPath1.CGPath;[_rightLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];dispatch_after(dispatch_time(DISPATCH_TIME_NOW,  positionDuration/2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){//左侧竖线缩放动画UIBezierPath *leftPath2 = [UIBezierPath bezierPath];[leftPath2 moveToPoint:CGPointMake(a*0.2,0)];[leftPath2 addLineToPoint:CGPointMake(a*0.2,a)];_leftLineLayer.path = leftPath2.CGPath;[_leftLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];//右侧竖线缩放动画UIBezierPath *rightPath2 = [UIBezierPath bezierPath];[rightPath2 moveToPoint:CGPointMake(a*0.8,a)];[rightPath2 addLineToPoint:CGPointMake(a*0.8,0)];_rightLineLayer.path = rightPath2.CGPath;[_rightLineLayer addAnimation:[self pathAnimationWithDuration:positionDuration/2] forKey:nil];});
}

2、衔接动画

2-1、 原理分析

为了方便观察,衔接动画时长再放慢一倍

分析:在衔接动画中,左侧竖线是连接着弧线和三角形的,右侧竖线连接着是底部的弧线。而且在整个过程中,只有三角形的动画时贯穿全程的,时长最长,所以设整个衔接动画总时长为animationDuration,也就是三角动画的时长。

正向动画过程:
1、三角形的动画时全程执行的,从开始一直到结束;
2、开始时同时变化的是右侧的竖线,缩短到起点;
3、弧线是连接右侧竖线和左侧竖线的桥梁,当右侧竖线长度缩短为零时,弧线运动到左侧竖线的底部;这时开始缩短弧线,至左侧竖线的底部;
4、左侧竖线在弧线缩短至其底部时也开始缩短,最终长度缩短为零,同时三角形绘制完成。

逆向过程:
仔细观察可知,逆向动画起始就是正向动画的逆过程。
无论在绘制直线还是曲线的时候,都是利用CAShapeLayer+UIBezierPath来实现的,而合理的利用strokeEnd属性就可以实现正向和逆向动画的互相转换。

动画时长:
通过三角形的动画时长可以来确定其他部分的时长:
右侧竖线缩短时长:animationDuration/4
弧线缩放时长(两次):animationDuration/4
左侧竖线缩放时长:animationDuration/4
这样四次动画的时长加起来正好为三角动画的时长。

2-2、绘制三角形

通过图可以看出来,三角形是顺时针拐了三次弯回到的原点。经过了 A->B->C->D->A 五个点完成首尾相接的三角形。
这几个点的坐标分别为:(0.2a,0.2a)、(0.2a,0)、(a,0.5a)、(0.2a,a)、(0.2a,0.2a)

绘制三角形代码如下:

- (void)addTriangleLayer {CGFloat a = self.bounds.size.width;UIBezierPath *path = [UIBezierPath bezierPath];[path moveToPoint:CGPointMake(a*0.2,a*0.2)];[path addLineToPoint:CGPointMake(a*0.2,0)];[path addLineToPoint:CGPointMake(a,a*0.5)];[path addLineToPoint:CGPointMake(a*0.2,a)];[path addLineToPoint:CGPointMake(a*0.2,a*0.2)];_triangleLayer = [CAShapeLayer layer];_triangleLayer.path = path.CGPath;_triangleLayer.fillColor = [UIColor clearColor].CGColor;_triangleLayer.strokeColor = [UIColor redColor].CGColor;_triangleLayer.lineWidth = [self lineWidth];_triangleLayer.lineCap = kCALineCapButt;_triangleLayer.lineJoin = kCALineJoinRound;_triangleLayer.strokeEnd = 0;[self.layer addSublayer:_triangleLayer];
}

2-3、 绘制弧线

由图可知,弧线是从右侧竖线的底部,连接到左侧竖线的底部;即从E点绕圆心O顺时针到F点,角度为π。

代码如下:

- (void)addCircleLayer {CGFloat a = self.bounds.size.width;UIBezierPath *path = [UIBezierPath bezierPath];[path moveToPoint:CGPointMake(a*0.8,a*0.8)];[path addArcWithCenter:CGPointMake(a*0.5, a*0.8) radius:0.3*a startAngle:0 endAngle:M_PI clockwise:true];_circleLayer = [CAShapeLayer layer];_circleLayer.path = path.CGPath;_circleLayer.fillColor = [UIColor clearColor].CGColor;_circleLayer.strokeColor = [UIColor blueColor].CGColor;_circleLayer.lineWidth = [self lineWidth];_circleLayer.lineCap = kCALineCapRound;_circleLayer.lineJoin = kCALineJoinRound;_circleLayer.strokeEnd = 0;[self.layer addSublayer:_circleLayer];
}

2-4、 执行动画

在这个过程中动画都是通过改变strokeEnd属性实现的动画,为了方便,写了一个通用的方法:

- (CABasicAnimation *)strokeEndAnimationFrom:(CGFloat)fromValue to:(CGFloat)toValue onLayer:(CALayer *)layer name:(NSString*)animationName duration:(CGFloat)duration delegate:(id)delegate {CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];strokeEndAnimation.duration = duration;strokeEndAnimation.fromValue = @(fromValue);strokeEndAnimation.toValue = @(toValue);strokeEndAnimation.fillMode = kCAFillModeForwards;strokeEndAnimation.removedOnCompletion = NO;[strokeEndAnimation setValue:animationName forKey:@"animationName"];strokeEndAnimation.delegate = delegate;[layer addAnimation:strokeEndAnimation forKey:nil];return strokeEndAnimation;
}

执行正向动画代码如下:
(弧线动画一部分是通过改变strokeStart属性实现的)

- (void)actionPositiveAnimation {//开始三角动画[self strokeEndAnimationFrom:0 to:1 onLayer:_triangleLayer name:TriangleAnimation duration:animationDuration delegate:self];//开始右侧线条动画[self strokeEndAnimationFrom:1 to:0 onLayer:_rightLineLayer name:RightLineAnimation duration:animationDuration/4 delegate:self];//开始画弧动画[self strokeEndAnimationFrom:0 to:1 onLayer:_circleLayer name:nil duration:animationDuration/4 delegate:nil];//开始逆向画弧动画dispatch_after(dispatch_time(DISPATCH_TIME_NOW,  animationDuration*0.25 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){[self circleStartAnimationFrom:0 to:1];});//开始左侧线条缩短动画dispatch_after(dispatch_time(DISPATCH_TIME_NOW,  animationDuration*0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){[self strokeEndAnimationFrom:1 to:0 onLayer:_leftLineLayer name:nil duration:animationDuration/2 delegate:nil];});
}

逆向动画就是正向的逆过程,也就是strokeEnd属性0到1和1到0的关系罢了,只是执行时间不太一样,就不贴代码了。

衔接动画最终效果如下:

#延伸阅读:优酷播放按钮动画解析

#Github:https://github.com/mengxianliang/XLPlayButton

爱奇艺播放按钮动画解析相关推荐

  1. 让AI“读懂”短视频,爱奇艺内容标签技术解析

    前言 随着短视频的兴起,每天有大量的短视频被生产并上传到各大视频平台,面对海量的短视频,如何提升这些短视频的智能分发效率是各大短视频平台面临的重要课题. 视频的标签技术是内容理解的一种重要手段,已经在 ...

  2. 爱奇艺播放器使用更高倍速播放

    1. 原理 通过查看爱奇艺网页播放器的元素可以发现,爱奇艺播放器使用的是video标签播放的.那么就可以通过更改video标签的属性来改变播放速度. 2.操作 在控制台通过执行以下代码重新设置vide ...

  3. 爱奇艺播放器老板键试玩

    爱奇艺播放器有一个热键:老板键 这个快捷键默认不启用,需要我们自己绑定快捷键 效果就是按下快捷键,程序暂停并隐藏 意思是,正在播放的视频和音乐暂停,程序窗口关闭 但是打开任务管理器发现并没有结束进程 ...

  4. android高仿奇艺影视,android仿爱奇艺加载动画实例

    本篇文章介绍了android仿爱奇艺加载动画实例,具体代码如下: 效果图: 用到的知识点: Path ValueAnimator 如果对Path和ValueAnimator还不熟悉推荐去看这几个大神的 ...

  5. 爱奇艺播放视频声音和画面不同步解决办法

    描述:爱奇艺播放视频声音和画面不同步解决办法 步骤: 调低画面画质即可

  6. 模仿爱奇艺播放暂停按钮动画效果——swift

    先上效果图 实现思路: 重载init,画出左边线条.右边线条.三角形和圆弧图层,用layer.strokeEnd = 0隐藏三角形和弧线,初始化展示暂停按钮.圆弧作为过渡右边线和三角形使用. 暂停按钮 ...

  7. 爱奇艺播放技术——300ms背后的故事

    爱奇艺大播放内核运行在 Android Mobile.Android TV.Apple TV.iPhone.iPad.GPad.MAC.Windows PC 等不同业务平台,支持以"直播.点 ...

  8. 全面解析Android性能优化,含腾讯、阿里、百度、京东、美团、爱奇艺等大厂实战解析

    前言 安卓开发大军浩浩荡荡,经过十多年的发展.红利期已过,现在是增量有限,存量厮杀,从争夺用户到争夺时长.不管是用户也好.企业也好,都对 App 的用户体验和性能提出了更高的要求. 如果你已经有 2 ...

  9. 爱奇艺视频标签技术解析

    本文转载自:https://mp.weixin.qq.com/s/gTdVJs8PEzy-8fbYfXOrHA 前言 随着短视频的兴起,每天有大量的短视频被生产并上传到各大视频平台,面对海量的短视频, ...

最新文章

  1. 【Android View绘制之旅】主脉络
  2. java synchronized关键字_Java synchronized 关键字,你用的对吗?
  3. python扑克牌洗牌_python:面向对象基本知识(二)用类方法实现斗地主洗牌发牌...
  4. MySQL 事物隔离级别
  5. NLP《词汇表示方法(二)词嵌入表示》
  6. 百度地图智能语音助手用户量突破3亿:确实方便
  7. eclipse调试第一个java程序
  8. Docker优势以及与传统虚拟机对比(1)
  9. 关于清除丢失贴图与IES文件
  10. 群晖 VMM虚拟机安装openwrt软路由做单臂旁路由
  11. IT行业主要职业有什么?
  12. PyCharm运行问题:AssertionError: Torch not compiled with CUDA enabled
  13. shell小数点前不打0_shell十三问:关于${0##*/} 和${0%/*}
  14. 体感互动LED显示屏系统|体感互动屏幕|体感互动大屏软件
  15. idea中用git管理文件之后文件颜色的含义
  16. TextView设置SpannableString 添加图片和点击事件(仿微信点赞列表)
  17. 解决一个JAVA小问题
  18. 指针:const与指针
  19. 交换机的基本原理(特别是动态ARP、静态ARP、代理ARP)
  20. 【前后端结合】从 0 到 1 实现一个网站框架(一、注册 [1] )

热门文章

  1. latex小技巧—极限符号下方分成两行
  2. 【银河麒麟】终端安装微信代码
  3. python基础教程第三版豆瓣-数据结构与算法必读书单吐血整理推荐【附网盘链接】...
  4. 高并发核心技术Redis系列(七)--------Jedis操作Redis
  5. mysql(数据库)初级操作
  6. 【黄啊码】MySQL入门—5、数据库小技巧:单个列group by就会,多个列呢?
  7. TCP/IP详解(二)数据链路层
  8. Vue、ElementUI
  9. 成功实现NAS家庭服务器(流媒体播放、文件共享及下载机)
  10. 华为「天才少年」自制硬萌机器人,开源5小时,GitHub收获317星!