Animations开源动效分析(二)POP-Stroke动画

554843030 8年前

来自: http://segmentfault.com/a/1190000004365988

  1. 本教程源码 Animations 作者 YouXianMing ,建议配合源码项目食用

  2. 非死book pop动画框架简易教程请移步 非死book Pop 使用指南

  3. CoreAnimation不简易教程

  4. 如果不想看第三条的教程,也要弄明白CALayer的隐式动画,否则看本文会疑惑,请移步 CALayer的隐式动画和显式动画

CAMediaTimingFunction

今天我们来看一下研究一下 CAMediaTimingFunction 类,它是一个动画的时间线控制类,他所控制的时间线,可以是是一条直线、曲线或者折线,如下:

这是用一个开源软件生成的 CAMediaTimingFunction ,软件地址是 keefo/CATweaker

可见,一般自定义的 CAMediaTimingFunction 通过调用

/* Creates a timing function modelled on a cubic Bezier curve. The end   * points of the curve are at (0,0) and (1,1), the two points 'c1' and   * 'c2' defined by the class instance are the control points. Thus the   * points defining the Bezier curve are: '[(0,0), c1, c2, (1,1)]' */    + (instancetype)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;    - (instancetype)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;

两个方法,传入四个位置点参数生成。 注:上图中XY轴区间都是[0,1];

这个类,在什么地方用到呢?

  1. CALayer的隐式和显式动画, CATransaction 有 animationTimingFunction 设置。

  2. CAKeyframeAnimation 有相关设置。

  3. CABasicAnimation 是线性的动画,一条直线。

  4. CASpringAnimation 弹簧动画是也是有一个特殊的走向,属于 CAMediaTimingFunction 的特殊封装。

  5. POP 也借用了 CAMediaTimingFunction 类实现非线性动画。

下面这个网站可以在线调试, cubic-bezier ,虽然是给CSS工程师用的,但是通用的。

上图中,蓝色的方块的运动就是线性的,红色方块是非线性的。

iOS7开始,iOS系统大量引入了非线性动画。

上图引用自 使用 iOS 8 Spring Animation API 创建动画

Spring动画

弹簧(Spring)动画是一种特殊曲线的非线性动画,因为用的地方太多,所以无论是CoreAnimation还是POP,都将其进行了封装 CASpringAnimation , POPSpringAnimation 。

两者有一点区别,参考源码中的 CASpringAnimation 和 POP-Spring动画参数详解

POP-Stroke动画

今天我们来分析一下 POP-Stroke动画 的源代码,首先interface中声明了一个 CAShapeLayer 是中心的圆, timer 是一个定时器。这个GCDTimer是作者对GCD进行的一层对象化封装。

@interface PopStrokeController ()    @property (nonatomic, strong) CAShapeLayer  *circleShape;  @property (nonatomic, strong) GCDTimer      *timer;    @end

实现的思路是,定时改变 CAShapeLayer 的startStoke和endStoke属性,改变圆的绘制弧度,使用POP的Spring动画控制其改变数值。

- (void)setup {            [super setup];            self.circleShape           = [CAShapeLayer layer];      self.circleShape.strokeEnd = 0.f;      self.circleShape.lineCap   = kCALineCapRound;            StrokeCircleLayerConfigure *config = [StrokeCircleLayerConfigure new];      config.lineWidth                   = 4.f;      config.startAngle                  = 0;      config.endAngle                    = M_PI * 2;      config.radius                      = 55.f;      config.circleCenter                = self.contentView.middlePoint;      config.strokeColor                 = [UIColor cyanColor];      [config configCAShapeLayer:self.circleShape];         [self.contentView.layer addSublayer:self.circleShape];            _timer = [[GCDTimer alloc] initInQueue:[GCDQueue mainQueue]];        [_timer event:^{                    CGFloat value1 = arc4random() % 101 / 100.f;          CGFloat value2 = arc4random() % 101 / 100.f;                    POPSpringAnimation *strokeAnimationEnd = [POPSpringAnimation animationWithPropertyNamed:kPOPShapeLayerStrokeEnd];          strokeAnimationEnd.toValue             = @(value1 > value2 ? value1 : value2);          strokeAnimationEnd.springBounciness    = 12.f;                    POPSpringAnimation *strokeAnimationStart = [POPSpringAnimation animationWithPropertyNamed:kPOPShapeLayerStrokeStart];          strokeAnimationStart.toValue             = @(value1 < value2 ? value1 : value2);          strokeAnimationStart.springBounciness    = 12.f;                    POPBasicAnimation *strokeAnimationColor  = [POPBasicAnimation animationWithPropertyNamed:kPOPShapeLayerStrokeColor];          strokeAnimationColor.toValue             = (__bridge id)([self randomColor].CGColor);                    [self.circleShape pop_addAnimation:strokeAnimationEnd forKey:@"layerStrokeAnimation"];          [self.circleShape pop_addAnimation:strokeAnimationStart forKey:@"layerStrokeAnimation1"];          [self.circleShape pop_addAnimation:strokeAnimationColor forKey:@"layerStrokeAnimation2"];                } timeIntervalWithSecs:1];            [_timer start];  }    - (UIColor *)randomColor {        return [UIColor colorWithRed:arc4random() % 101 / 100.f                             green:arc4random() % 101 / 100.f                              blue:arc4random() % 101 / 100.f                             alpha:1];  }

我们可以看到,POP支持了CALayer的所有动画属性,上面代码中用的

NSString * const kPOPShapeLayerStrokeStart = @"shapeLayer.strokeStart";  NSString * const kPOPShapeLayerStrokeEnd = @"shapeLayer.strokeEnd";  NSString * const kPOPShapeLayerStrokeColor = @"shapeLayer.strokeColor";

分别对应CAShapeLayer的绘制颜色,起始比例区间。

/* The color to fill the path's stroked outline, or nil for no stroking.   * Defaults to nil. Animatable. */    @property(nullable) CGColorRef strokeColor;    /* These values define the subregion of the path used to draw the   * stroked outline. The values must be in the range [0,1] with zero   * representing the start of the path and one the end. Values in   * between zero and one are interpolated linearly along the path   * length. strokeStart defaults to zero and strokeEnd to one. Both are   * animatable. */    @property CGFloat strokeStart;  @property CGFloat strokeEnd;

然后通过生成随机数的方式,用定时器定时改变,同时随机改变了颜色。所以总共使用了三个Spring动画。

相同原理的Demo还有 Easing-圆环动画

只是这例子中用了作者自己封装的 YXEasing 类。

总结

非线性动画对于要精益求精交互工程师来说,是一把剑利的刃。放在iOS5时代可能因为设备性能等问题,还是把双刃剑,但现在来说,已经完全是提升APP动效交互的利器了。

相关阅读: 缓动函数速查表