zoukankan      html  css  js  c++  java
  • CAShapeLayer的strokeStart和strokeEnd属性

    https://blog.csdn.net/a787188834/article/details/78504848

    https://www.gameres.com/474139.html

    1 keyPath = strokeStart  

    动画的fromValue = 0,toValue = 1

        表示从路径的0位置画到1 怎么画是按照清除开始的位置也就是清除0 一直清除到1 效果就是一条路径慢慢的消失

         

         2 keyPath =strokeStart  动画的fromValue = 1,toValue = 0

        表示从路径的1位置画到0 怎么画是按照清除开始的位置也就是1 这样开始的路径是空的(即都被清除掉了)一直清除到0效果就是一条路径被反方向画出来

         

         3 keyPath =strokeEnd  动画的fromValue = 0,toValue = 1

         表示这里我们分3个点说明动画的顺序  strokeEnd从结尾开始清除首先整条路径先清除后2/3,接着清除1/3 效果就是正方向画出路径

         

         3 keyPath =strokeEnd  动画的fromValue = 1,toValue = 0

        效果就是反方向路径慢慢消失

    注释: 动画的0-1(fromValue= 0,toValue = 1) 或1-0 (fromValue= 1,toValue = 0) 表示执行的方向 和路径的范围。

       CABasicAnimation *pathAnimation =[CABasicAnimation animationWithKeyPath:@"strokeEnd"];

       pathAnimation.duration = 1.5;

       pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

       pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];

       pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];

       pathAnimation.autoreverses = NO;

       [_chartLineaddAnimation:pathAnimation forKey:@"strokeEndAnimation"];

       

       _chartLine.strokeEnd = 2.0;

    一款Loading动画的实现思路(二):stroke方案


    GameRes游资网授权发布 文 / 雪夜吐息的简书

    感谢大家对本系列第一篇的关注和肯定,让我更加有动力和信心与大家分享心得,本系列的第二篇现在开始了。

    有的同学是第一次来,没有看过之前的文章,我贴一下动画的整体效果(我实现的简单版本),原动效及前阶段动效的实现请戳本系列第一篇:一款Loading动画的实现思路(一):复杂任务的拆分


    先来回顾一下,第1阶段的最终效果是这样的:


    现在开始第2阶段,效果如下图(为了清晰,之前的阶段我设置为灰色正常速度,而当前阶段设置为蓝色慢速)


    看到这个运动的小蓝条,大家有什么感觉?

    没错,这是一段起点终点不停变化的弧。

    看过本系列第一篇的同学可能想到了,这完全可以用第1阶段重绘弧的方式来实现。

    大家可以比较下面两张图,前者是第1阶段,后者是第2阶段:



    蓝色部分是我们的弧,因为弧太短,我们用灰色的线来表示弧的运动轨迹,这样看的更清楚些;

    具体的节点数值我没有标出,能看出来,第2阶段可以用重绘弧的方式实现,看上去比第1阶段还要简单一些。

    如果是在项目中,第2阶段就会选用重绘弧方案了,减少方案数可以降低项目的维护成本,毕竟多一种方案就多一些Bug的可能性,项目中新人的学习成本也大一些。

    所以,第二篇就这么完事了吗?

    当然不是,我们现在是学习,是在玩,方案当然是越多越好。

    接下来,我们要换一种方式来实现第2阶段动画,至于重绘弧的方式,有兴趣的同学可以自己实现一下,加深一下印象。

    换一种思路

    大家请再次观察一下这张图:


    我们可不可以这样理解,灰色部分是一条弧,弧OD只是它的一部分,这部分涂上了蓝色。

    再进一步说,我们将灰色改成透明,那是不是可以这样认为,弧本身是透明的,我们将弧上O和D之间的部分涂上了蓝色。

    如果我们不停的改变O和D的位置,弧涂上蓝色部分的位置就在不断变化,看上去就像一段蓝色弧在移动。

    由这个思路,我们可以有一种新方案,stroke方案。

    stroke方案

    看到stroke,有过绘制经验的同学可能想起了strokeColor、lineWidth等词,没错,就是这个stroke,没用过的同学也不用着急,后文会慢慢讲。

    我们的弧可以认为是一条路径(后文我们用path这个词,例如第1阶段我们用的UIBezierPath),stroke就是沿着path涂色。

    那么重绘弧的方案可以理解为,我们不停的创建新的蓝色path,每条path的起点和终点不一样,来形成动画。

    而stroke方案则是,我们只创建一条透明的path,我们不断改变其涂蓝色的起点和终点,来形成动画。

    假设我们给涂蓝色的起点终点分别命名为SS(strokeStart)和SE(strokeEnd),

    再假设SS、SE不用具体数值表示,而用0~1之间的值表示,代表其在path的哪个位置(比如0.1就是距path起点的10%处),

    我们用一条直线path来示例,依然用灰色代表透明部分,用蓝色代表stroke部分,大家请看下图,看看SS、SE取值怎么影响path的样子:


    图中示意了4种情况下,SS、SE取值形成的path的样子,大家应该已经有感觉了,接下来我们示范一下SS、SE动态变化时path的样子。

    假设SS=0不变,SE从0变化到1,由于SS与SE之间的部分就是要涂色的部分,看上去就是涂色的部分从path的起点开始,越来越接近path终点,直到到达终点,效果就是path从无到有,如下图:


    再看一种情况,SE=1不变,SS从0变化1,

    详细点说,初始时SS=0,SE=1,此时path整体被涂色,动画过程中。SS从0变到1,一直到SS和SE重合为一个点,由于涂色的范围是SS到SE之间,两者重合那就亲密无间了,没处可涂色,效果就是path从有到无,如图:


    再示范一种情况,SS从0变到0.9,SE从0.1变到1,这样的话变化过程中SS和SE的相对位置始终没有变化,也就是涂色的长度没有变化,会是什么效果呢, 大家可能想到了,是这样的:


    然后大家再看下第2阶段的效果图:


    可以看出,两者本质是一样的,只不过前者是一条直线path的SS和SE在变化,后者是一段弧path的SS和SE在变化。

    我们假设第2阶段蓝色部分是弧长(path长度)的1/10,那么我们只需要找到弧的path,然后让它的SS从0变到0.9(9/10),SE从0.1(1/10)变到1就可以了,SS与SE始终相差0.1,也就是path长度的1/10。

    剩下的就是找到这段弧了。

    我们先复习下画弧的API,看看都需要什么:

    1. + (instancetype)bezierPathWithArcCenter:(CGPoint)center
    2.                              radius:(CGFloat)radius
    3.                          startAngle:(CGFloat)startAngle
    4.                            endAngle:(CGFloat)endAngle
    5.                           clockwise:(BOOL)clockwise
    复制代码

    前4个值是我们要找,分别是弧的圆心arcCenter,弧的半径arcRadius,开始角度(我们前文中用O点代表),结束角度(用D点代表)。

    为了计算简单,我做了简化,让弧的终点在圆正上方,和圆顶部的距离正好是圆的半径,请看下图,灰色的圆就是第1阶段动画形成的圆,蓝色的弧就是我们要找的弧:


    图中左下方有一个d,代表x轴上弧圆心到圆左侧的距离。

    由此我们可以得出变量的表达式,手写太长,我偷个懒,直接上一段代码,代码中的kRadius是小圆的半径:

    1. // 小圆圆心
    2. CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
    3. // d(x轴上弧圆心与小圆左边缘的距离)
    4. CGFloat d = ?; // 此时还不知道d的表达式
    5. // 弧圆心
    6. CGPoint arcCenter = CGPointMake(center.x - kRadius - d, center.y);
    7. // 弧半径
    8. CGFloat arcRadius = kRadius * 2 + d;
    9. // O(origin)
    10. CGFloat origin = M_PI * 2;
    11. // D(dest)
    12. CGFloat dest = M_PI * 2 - asin(kRadius * 2 / arcRadius); // 2π - 图中的θ角弧度
    复制代码

    可以看到,表达式中唯一未知的就是d,接下来我们思考d的表达式,

    图中红色的两条线都是弧的半径,所以长度是相等的,注意到左上的这条是一个直接三角形的斜边,因此可以得出下面的表达式:


    思路OK了,弧也找到了,现在可以写代码了。

    写代码

    这次我们要用到CAShapeLayer了,CAShapeLayer是CALayer的子类,请看它的三个属性,如图:


    Perfect!

    我们要的它全都有,上代码:

    1. self.moveArcLayer = [CAShapeLayer layer];
    2. [self.layer addSublayer:self.moveArcLayer];
    3. self.moveArcLayer.frame = self.layer.bounds;
    4. // 弧的path
    5. UIBezierPath *moveArcPath = [UIBezierPath bezierPath];
    6. // 小圆圆心
    7. CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
    8. // d(x轴上弧圆心与小圆左边缘的距离)
    9. CGFloat d = kRadius / 2;
    10. // 弧圆心
    11. CGPoint arcCenter = CGPointMake(center.x - kRadius - d, center.y);
    12. // 弧半径
    13. CGFloat arcRadius = kRadius * 2 + d;
    14. // O(origin)
    15. CGFloat origin = M_PI * 2;
    16. // D(dest)
    17. CGFloat dest = M_PI * 2 - asin(kRadius * 2 / arcRadius);
    18. [moveArcPath addArcWithCenter:arcCenter radius:arcRadius startAngle:origin endAngle:dest clockwise:NO];
    19. self.moveArcLayer.path = moveArcPath.CGPath;
    20. self.moveArcLayer.lineWidth = 3;
    21. self.moveArcLayer.strokeColor = [UIColor blueColor].CGColor;
    22. self.moveArcLayer.fillColor = nil;
    23. // SS(strokeStart)
    24. CGFloat SSFrom = 0;
    25. CGFloat SSTo = 0.9;
    26. // SE(strokeEnd)
    27. CGFloat SEFrom = 0.1;
    28. CGFloat SETo = 1;
    29. // end status
    30. self.moveArcLayer.strokeStart = SSTo;
    31. self.moveArcLayer.strokeEnd = SETo;
    32. // animation
    33. CABasicAnimation *startAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    34. startAnimation.fromValue = @(SSFrom);
    35. startAnimation.toValue = @(SSTo);
    36. CABasicAnimation *endAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    37. endAnimation.fromValue = @(SEFrom);
    38. endAnimation.toValue = @(SETo);
    39. CAAnimationGroup *step2 = [CAAnimationGroup animation];
    40. step2.animations = @[startAnimation, endAnimation];
    41. step2.duration = kStep2Duration;
    42. step2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    43. [self.moveArcLayer addAnimation:step2 forKey:nil];
    复制代码

    很清晰,而且代码也不多,所以说现在的系统库和第三方库已经很强大了,思路有了,实现起来一般不会太难。

    发散

    stroke方案很适合处理沿path变化的动效,比如前文中的这张图,

    其实就是自定义进度条的雏形:


    这里的path是直线,那就是直线的进度条,如果是圆,那就是圆形的进度条,如果是很个性的形状,那就是很个性的进度条。

    除此之外,还有很多CAShapeLayer的path和stroke实现的动效,因为path的无限可能性,也造就了很多stroke方案的炫动效,比如这个动画写字的,炫到飞起!


    相关阅读iOS开发:停止不必要的UI动效设计
  • 相关阅读:
    2020年. NET Core面试题
    java Context namespace element 'component-scan' and its parser class ComponentScanBeanDefinitionParser are only available on JDK 1.5 and higher 解决方法
    vue 淡入淡出组件
    java http的get、post、post json参数的方法
    vue 父子组件通讯案例
    Vue 生产环境解决跨域问题
    npm run ERR! code ELIFECYCLE
    Android Studio 生成apk 出现 :error_prone_annotations.jar (com.google.errorprone:error) 错误
    记忆解析者芜青【总集】
    LwIP应用开发笔记之十:LwIP带操作系统基本移植
  • 原文地址:https://www.cnblogs.com/itlover2013/p/14327053.html
Copyright © 2011-2022 走看看