iOS Core Animation Advanced Techniques-缓冲
上九章节:
这篇随笔主要介绍有关图层缓冲。
Core Animation使用缓冲来使动画移动更平滑更自然。
- 动画速度:
- 速率由以下公式计算而来:
- velocity = change / time
- /*
- 假设了速度在整个动画过程中都是恒定不变的,即线性步调动画,完全不真实的一种效果
- */
- 速率由以下公式计算而来:
- 缓冲函数:
- 使用物理引擎来对运动物体的摩擦和动量来建模的函数
- CAMediaTimingFunction:
- 一个通过控制动画缓冲来模拟物理效果例如加速或者减速来增强现实感的东西.
- CAAnimation的timingFunction属性(CAMediaTimingFunction类型):
- 创建CAMediaTimingFunction类型实例的方法:
- +timingFunctionWithName:
- 接收参数是如下几个常量之一:
- kCAMediaTimingFunctionLinear
- 创建一个线性的计时函数(例如子弹出膛),CAAnimation的timingFunction属性默认函数
- kCAMediaTimingFunctionEaseIn
- 创建了一个慢慢加速然后突然停止的方法(例如自由落体)
- kCAMediaTimingFunctionEaseOut
- 创建了一个全速开始,然后慢慢减速停止(例如球跌落地面反弹上升)
- kCAMediaTimingFunctionEaseInEaseOut
- 创建了一个慢慢加速然后再慢慢减速的过程(UIView动画的默认方式)
- kCAMediaTimingFunctionDefault
- 创建一个类似kCAMediaTimingFunctionEaseInEaseOut的方法,但加速和减速的过程都稍微有些慢
- 显式的CAAnimation它并不是默认选项
- kCAMediaTimingFunctionLinear
- 示范例子:
- //create a red layer
- self.colorLayer = [CALayer layer];
- self.colorLayer.frame = CGRectMake(0, 0, 100, 100);
- self.colorLayer.position = CGPointMake(self.view.bounds.size.width/2.0, self.view.bounds.size.height/2.0);
- self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
- [self.view.layer addSublayer:self.colorLayer];
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
- //configure the transaction
- [CATransaction begin];
- [CATransaction setAnimationDuration:1.0];
- [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
- //set the position
- self.colorLayer.position = [[touches anyObject] locationInView:self.view];
- //commit transaction
- [CATransaction commit];
- }
- 创建CAMediaTimingFunction类型实例的方法:
- UIView的动画缓冲:
- 为了改变UIView动画的缓冲选项,给options参数添加如下常量之一:
- UIViewAnimationOptionCurveEaseInOut
- UIViewAnimationOptionCurveEaseIn
- UIViewAnimationOptionCurveEaseOut
- UIViewAnimationOptionCurveLinear
- 示范例子:
- self.colorView = [[UIView alloc] init];
- self.colorView.bounds = CGRectMake(0, 0, 100, 100);
- self.colorView.center = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
- self.colorView.backgroundColor = [UIColor redColor];
- [self.view addSubview:self.colorView];
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
- //perform the animation
- [UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
- //set the position
- self.colorView.center = [[touches anyObject] locationInView:self.view];
- } completion:NULL];
- }
- 缓冲和关键帧动画:
- 不给整个动画过程应用一个计时函数,而是对每个动画的过程重复这样的缓冲:
- 具体做法:
- 设置关键帧动画CAKeyframeAnimation的timingFunctions属性(NSArray类型)对每次动画的步骤指定不同的计时函数。
- 注意:
- 指定函数的个数一定要等于keyframes数组的元素个数减一,因为它是描述每一帧之间动画速度的函数
- 示范例子:
- //create sublayer
- self.colorLayer = [CALayer layer];
- self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
- self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
- //add it to our view
- [self.layerView.layer addSublayer:self.colorLayer];
- - (IBAction)changeColor{
- //create a keyframe animation
- CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
- animation.keyPath = @"backgroundColor";
- animation.duration = 2.0;
- animation.values = @[
- (__bridge id)[UIColor blueColor].CGColor,
- (__bridge id)[UIColor redColor].CGColor,
- (__bridge id)[UIColor greenColor].CGColor,
- (__bridge id)[UIColor blueColor].CGColor ];
- //add timing function
- CAMediaTimingFunction *fn = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn];
- animation.timingFunctions = @[fn, fn, fn];
- //apply animation to layer
- [self.colorLayer addAnimation:animation forKey:nil];
- }
- CAMediaTimingFunction缓冲函数是如何工作的:
- 缓冲函数效果反应在一个简单的坐标图上,横轴代表时间,纵轴代表改变的量
- 也就是说:
- 线性的缓冲就是一条从起点开始的简单的斜线,该斜线代表了速度
- 但是CAMediaTimingFunction使用了一个叫做三次贝塞尔曲线的函数,
- 也就是说,CAMediaTimingFunction缓冲函数在坐标图上画出的速度线是一条贝塞尔曲线,切角(切线与时间轴形成的角)越大,速度越快
- 而一条三次贝塞尔曲线需要通过四个点来定义:
- 第一个和最后一个点代表了曲线的起点和终点
- 剩下中间两个点叫做控制点,因为它们控制了曲线的形状
- 自定义缓冲函数:
- CAMediaTimingFunction的另一个构造函数
- +functionWithControlPoints::::
- 提供创建一个自定义的缓冲函数
- CAMediaTimingFunction的一个方法:
- -getControlPointAtIndex:values:
- 用来检索曲线的点,可以找到标准缓冲函数的点,然后用UIBezierPath和CAShapeLayer来把它画出来。
- 示范例子:
- /*
- 标准CAMediaTimingFunction缓冲曲线,曲线的起始和终点始终是{0, 0}和{1, 1},于是我们只需要检索曲线的第二个和第三个点(控制点)
- */
- //create timing function
- CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
- //get control points
- CGPoint controlPoint1, controlPoint2;
- [function getControlPointAtIndex:1 values:(float *)&controlPoint1];
- [function getControlPointAtIndex:2 values:(float *)&controlPoint2];
- //create curve
- UIBezierPath *path = [[UIBezierPath alloc] init];
- [path moveToPoint:CGPointZero];
- [path addCurveToPoint:CGPointMake(1, 1)
- controlPoint1:controlPoint1 controlPoint2:controlPoint2];
- //scale the path up to a reasonable size for display
- [path applyTransform:CGAffineTransformMakeScale(200, 200)];
- //create shape layer
- CAShapeLayer *shapeLayer = [CAShapeLayer layer];
- shapeLayer.strokeColor = [UIColor redColor].CGColor;
- shapeLayer.fillColor = [UIColor clearColor].CGColor;
- shapeLayer.lineWidth = 4.0f;
- shapeLayer.path = path.CGPath;
- [self.layerView.layer addSublayer:shapeLayer];
- //flip geometry so that 0,0 is in the bottom-left
- self.layerView.layer.geometryFlipped = YES;
- 示范例子:
- /*
- 应用自定义缓冲函数
- */
- - (void)setAngle:(CGFloat)angle forHand:(UIView *)handView animated:(BOOL)animated{
- //generate transform
- CATransform3D transform = CATransform3DMakeRotation(angle, 0, 0, 1);
- if (animated) {
- //create transform animation
- CABasicAnimation *animation = [CABasicAnimation animation];
- animation.keyPath = @"transform";
- animation.fromValue = [handView.layer.presentationLayer valueForKey:@"transform"];
- animation.toValue = [NSValue valueWithCATransform3D:transform];
- animation.duration = 0.5;
- animation.delegate = self;
- animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:1 :0 :0.75 :1];
- //apply animation
- handView.layer.transform = transform;
- [handView.layer addAnimation:animation forKey:nil];
- } else {
- //set transform directly
- handView.layer.transform = transform;
- }
- }
- CAMediaTimingFunction的另一个构造函数
- 更复杂的动画曲线:
- 例如一个橡胶球掉落到坚硬的地面的场景,当开始下落的时候,它会持续加速知道落到地面,然后经过几次反弹,最后停下来
- 这种效果没法用一个简单的三次贝塞尔曲线表示,于是不能用CAMediaTimingFunction来完成,可以通过以下方法实现:
1.用CAKeyframeAnimation创建一个动画,然后分割成几个步骤,每个小步骤使用自己的计时函数
-
- 或
2.使用定时器逐帧更新实现动画
-
- 这里先讲解1:
- 基于关键帧的缓冲:
- 使用关键帧实现反弹动画:
- 1.需要在缓冲曲线中对每一个显著的点创建一个关键帧(关键点也就是每次反弹的峰值)
- 2.应用缓冲函数把每段曲线连接起来
- /*
- 同时,我们也需要通过keyTimes来指定每个关键帧的时间偏移,
- 由于每次反弹的时间都会减少,于是关键帧并不会均匀分布
- */
- 示范例子:
- UIImage *ballImage = [UIImage imageNamed:@"Ball.png"];
- self.ballView = [[UIImageView alloc] initWithImage:ballImage];
- [self.containerView addSubview:self.ballView];
- //reset ball to top of screen
- self.ballView.center = CGPointMake(150, 32);
- //create keyframe animation
- CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
- animation.keyPath = @"position";
- animation.duration = 1.0;
- animation.delegate = self;
- animation.values = @[
- [NSValue valueWithCGPoint:CGPointMake(150, 32)],
- [NSValue valueWithCGPoint:CGPointMake(150, 268)],
- [NSValue valueWithCGPoint:CGPointMake(150, 140)],
- [NSValue valueWithCGPoint:CGPointMake(150, 268)],
- [NSValue valueWithCGPoint:CGPointMake(150, 220)],
- [NSValue valueWithCGPoint:CGPointMake(150, 268)],
- [NSValue valueWithCGPoint:CGPointMake(150, 250)],
- [NSValue valueWithCGPoint:CGPointMake(150, 268)]
- ];
- animation.timingFunctions = @[
- [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn],
- [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut],
- [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn],
- [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut],
- [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn],
- [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut],
- [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn]
- ];
- animation.keyTimes = @[@0.0, @0.3, @0.5, @0.7, @0.8, @0.9, @0.95, @1.0];
- //apply animation
- self.ballView.layer.position = CGPointMake(150, 268);
- [self.ballView.layer addAnimation:animation forKey:nil];
- 这里先讲解1:
-
-
-
- 流程自动化:
- 实现一个方法,用缓冲函数来把任何简单的属性动画转换成关键帧动画:
- 为了实现自动化,我们需要知道如何做如下两件事情:
- 流程自动化:
-
-
1.自动把任意属性动画分割成多个关键帧
2.用一个数学函数表示弹性动画,使得可以对帧做便宜
-
-
-
-
-
- 解决问题1:
- 复制Core Animation的插值机制
- 这是一个传入起点和终点,然后在这两个点之间指定时间点产出一个新点的机制。
- 对于简单的浮点起始值,公式如下(假设时间从0到1):
- value = (endValue – startValue) × time + startValue;
- 对于类似于CGPoint,CGColorRef或者CATransform3D这种更加复杂类型的值:
- 可以简单地对每个独立的元素应用这个方法(也就CGPoint中的x和y值,CGColorRef中的红,蓝,绿,透明值,或者是CATransform3D中独立矩阵的坐标)
- 一旦我们可以用代码获取属性动画的起始值之间的任意插值,我们就可以把动画分割成许多独立的关键帧,然后产出一个线性的关键帧动画。
- 解决问题1:
- 示范例子:// 使用插入的值创建一个关键帧动画
- float interpolate(float from, float to, float time){
- return (to - from) * time + from;
- }
- - (id)interpolateFromValue:(id)fromValue toValue:(id)toValue time:(float)time{
- if ([fromValue isKindOfClass:[NSValue class]]) {
- //get type
- const char *type = [fromValue objCType];
- if (strcmp(type, @encode(CGPoint)) == 0) {
- CGPoint from = [fromValue CGPointValue];
- CGPoint to = [toValue CGPointValue];
- CGPoint result = CGPointMake(interpolate(from.x, to.x, time), interpolate(from.y, to.y, time));
- return [NSValue valueWithCGPoint:result];
- }
- }
- //provide safe default implementation
- return (time < 0.5)? fromValue: toValue;
- }
- - (void)animate{
- //reset ball to top of screen
- self.ballView.center = CGPointMake(150, 32);
- //set up animation parameters
- NSValue *fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)];
- NSValue *toValue = [NSValue valueWithCGPoint:CGPointMake(150, 268)];
- CFTimeInterval duration = 1.0;
- //generate keyframes
- NSInteger numFrames = duration * 60;//总帧数,每秒60帧
- NSMutableArray *frames = [NSMutableArray array];
- //获取每帧的插入点
- for (int i = 0; i < numFrames; i++) {
- float time = 1 / (float)numFrames * i;
- [frames addObject:[self interpolateFromValue:fromValue toValue:toValue time:time]];
- }
- //create keyframe animation
- CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
- animation.keyPath = @"position";
- animation.duration = 1.0;
- animation.delegate = self;
- animation.values = frames;
- //apply animation
- [self.ballView.layer addAnimation:animation forKey:nil];
- }
- /*
- 到目前为止我们所完成的只是一个非常复杂的方式来使用线性缓冲复制CABasicAnimation的行为
- 这种方式的好处在于我们可以更加精确地控制缓冲,这也意味着我们可以应用一个完全定制的缓冲函数
- */
- 示范例子:// 用关键帧实现自定义的缓冲函数
- float bounceEaseOut(float t){
- if (t < 4/11.0) {
- return (121 * t * t)/16.0;
- } else if (t < 8/11.0) {
- return (363/40.0 * t * t) - (99/10.0 * t) + 17/5.0;
- } else if (t < 9/10.0) {
- return (4356/361.0 * t * t) - (35442/1805.0 * t) + 16061/1805.0;
- }
- return (54/5.0 * t * t) - (513/25.0 * t) + 268/25.0;
- }
- - (id)interpolateFromValue:(id)fromValue toValue:(id)toValue time:(float)time{
- if ([fromValue isKindOfClass:[NSValue class]]) {
- //get type
- const char *type = [fromValue objCType];
- if (strcmp(type, @encode(CGPoint)) == 0) {
- CGPoint from = [fromValue CGPointValue];
- CGPoint to = [toValue CGPointValue];
- CGPoint result = CGPointMake(interpolate(from.x, to.x, time), interpolate(from.y, to.y, time));
- return [NSValue valueWithCGPoint:result];
- }
- }
- //provide safe default implementation
- return (time < 0.5)? fromValue: toValue;
- }
- - (void)animate{
- //reset ball to top of screen
- self.ballView.center = CGPointMake(150, 32);
- //set up animation parameters
- NSValue *fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)];
- NSValue *toValue = [NSValue valueWithCGPoint:CGPointMake(150, 268)];
- CFTimeInterval duration = 1.0;
- //generate keyframes
- NSInteger numFrames = duration * 60;
- NSMutableArray *frames = [NSMutableArray array];
- for (int i = 0; i < numFrames; i++) {
- float time = 1/(float)numFrames * i;
- //apply easing
- time = bounceEaseOut(time);
- //add keyframe
- [frames addObject:[self interpolateFromValue:fromValue toValue:toValue time:time]];
- }
- //create keyframe animation
- CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
- animation.keyPath = @"position";
- animation.duration = 1.0;
- animation.delegate = self;
- animation.values = frames;
- //apply animation
- [self.ballView.layer addAnimation:animation forKey:nil];
- }
-
-
-
-