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动效设计
  • 相关阅读:
    Spring IoC
    常见切入点表达式的例子(aop execution 表达式 )
    数据结构与算法(2)栈、中缀表达式、递归
    数据结构与算法(1)稀疏数组、队列、链表
    airflow实践
    head first 设计模式笔记13-与设计模式相处,剩下的模式,模式的分类
    head first 设计模式笔记12-复合模式
    head first 设计模式笔记11-代理模式
    head first 设计模式笔记10-状态模式
    WebDriver自动化测试常用处理方法
  • 原文地址:https://www.cnblogs.com/itlover2013/p/14327053.html
Copyright © 2011-2022 走看看