CADisplayLink结合UIBezierPath的神奇妙用
做过iOS动画的朋友都知道,动画中一大头疼之处就是弹性、形变之类扭曲的效果。iOS7开始,我们开始可以直接使用UiView的渲染动画API实现简单的弹性效果。
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
dampingRatio是阻尼系数,取值范围0~1,决定弹性效果的明显程度;
velocity是初速度。
除此之外,iOS7又出现了一个重量级的家伙:UIKit Dynamics,可以用很简单的代码实现非常逼真的物理效果。
当然,更强大的是Facebook开源的Pop这个介于CAAnimation 和 UIDynamics之间的动画引擎,使用习惯和CAAnimation基本别无二致,很方便上手,而且动画效果非常出色,帧频非常高,所以看上去的动画会很连贯顺滑。
但是以上要实现那种很Q弹、形变的效果还是有点困难。知道我同时遇到了CADisplayLink和贝塞尔曲线UIBezierPath。下面就是一些结合CADisplayLink和UIBezierPath的案例,并附上了源代码地址。
新建@interface JellyView : UIView
JellyView.m:
- (void)drawRect:(CGRect)rect {
CGFloat yOffset = 30.0;
CGFloat width = CGRectGetWidth(rect);
CGFloat height = CGRectGetHeight(rect);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0.0, yOffset)]; //去设置初始线段的起点
CGPoint controlPoint = CGPointMake(width / 2, yOffset + self.sideToCenterDelta);
[path addQuadCurveToPoint:CGPointMake(width, yOffset) controlPoint:controlPoint];
[path addLineToPoint:CGPointMake(width, height)];
[path addLineToPoint:CGPointMake(0.0, height)];
[path closePath];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, path.CGPath);
[fillColor set];
CGContextFillPath(context);
}
上面代码绘制了一个封闭的贝塞尔曲线,初始时刻封闭曲线是一个四方的矩形,因为[path addQuadCurveToPoint:CGPointMake(width, yOffset) controlPoint:controlPoint];中的controlPoint的纵坐标和左右两个定点纵坐标相等。但这其实是个动点。注意到CGPoint controlPoint = CGPointMake(width / 2, yOffset + self.sideToCenterDelta);,我们可以看到动点的纵坐标是由yOffset + self.sideToCenterDelta决定的。yOffset是固定的值。self.sideToCenterDelta等于两个辅助视图的高度差。最后通过CADisplayLink的实时绘制,我们可以就可以看到屏幕上出现的形变效果了。
ViewController.m:
先创建一个实例displayLink.
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkAction:)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
实现刷新器绑定的方法:
-(void)displayLinkAction:(CADisplayLink *)dis{
CALayer *sideHelperPresentationLayer = (CALayer *)[self.sideHelperView.layer presentationLayer];
CALayer *centerHelperPresentationLayer = (CALayer *)[self.centerHelperView.layer presentationLayer];
CGPoint position = [[centerHelperPresentationLayer valueForKeyPath:@"position"]CGPointValue];
CGRect centerRect = [[centerHelperPresentationLayer valueForKeyPath:@"frame"]CGRectValue];
CGRect sideRect = [[sideHelperPresentationLayer valueForKeyPath:@"frame"]CGRectValue];
NSLog(@"Center:%@",NSStringFromCGRect(centerRect));
NSLog(@"Side:%@",NSStringFromCGRect(sideRect));
CGFloat newJellyViewTopConstraint = position.y - CGRectGetMaxY(self.view.frame);
self.jellyViewTopConstraint.constant = newJellyViewTopConstraint;
[self.jellyView layoutIfNeeded];
self.jellyView.sideToCenterDelta = centerRect.origin.y - sideRect.origin.y;
}
这里有个地方花了我好长时间,就是我们不能直接通过self.sideHelperView.layer和self.centerHelperView.layer获取两个辅助视图动画过程中的变化的坐标,得到的是一个恒定的终点状态的坐标。要想获得动画过程中的每个状态的坐标,我们需要使用layer的presentationLayer,并且通过valueForKeyPath:@"position"的方式实时获取动态坐标。
!!最后千万别忘了调用[self.jellyView setNeedsDisplay];,否则- (void)drawRect:(CGRect)rect不会called.
第二个gif的实现思路:
接下来的思路完全大同小异,只不过实时刷新的定时器从CADisplayLink换成了同样具有实时调用功能的手势:UIGestureRecognizerStateChanged。
新建@interface BounceView : UIView
BounceView.m:
先准备好一个CAShapeLayer,并且填充颜色用来显示形变的图形。
- (void) createLine {
self.verticalLineLayer = [CAShapeLayer layer];
self.verticalLineLayer.strokeColor = [[UIColor whiteColor] CGColor];
self.verticalLineLayer.lineWidth = 1.0;
self.verticalLineLayer.fillColor = [[UIColor whiteColor] CGColor];
[self.layer addSublayer:self.verticalLineLayer];
}
当手势开始变化的时候,我们让self.verticalLineLayer.path等于变化中的贝塞尔曲线的CGPath,并且把手指的偏移程度的变量CGFloat amountX = [gr translationInView:self].x传过去;
self.verticalLineLayer.path = [self getLeftLinePathWithAmount:amountX];
贝塞尔曲线的变化代码如下:
//左边曲线
- (CGPathRef) getLeftLinePathWithAmount:(CGFloat)amount {
UIBezierPath *verticalLine = [UIBezierPath bezierPath];
CGPoint topPoint = CGPointMake(0, 0);
CGPoint midControlPoint = CGPointMake(amount, self.bounds.size.height/2);
CGPoint bottomPoint = CGPointMake(0, self.bounds.size.height);
[verticalLine moveToPoint:topPoint];
[verticalLine addQuadCurveToPoint:bottomPoint controlPoint:midControlPoint];
[verticalLine closePath];
return [verticalLine CGPath];
}
代码还是大同小异,无非就是改变控制点midControlPoint,只不过这里是改变它的横坐标而已。
3、总结
归根结底,要实现这个形变的Q弹效果无非就是一个实时调用一个绘制贝塞尔曲线的方法,并且这个贝塞尔曲线的控制点是一个动点。那个实时调用就有很多实现的办法了。各种原生的代理方法,当然还包括文中提到了毫秒级刷新器CADisplayLink。期待你能做出更加动感的动画:)
资料参考:
CADisplayLinkhttp://www.jianshu.com/p/c35a81c3b9eb
原文地址:http://www.kittenyang.com/cadisplaylinkanduibezierpath/
网友评论