美文网首页iOSiOS-CoreAnimationiOS UIKit Dynamics
IOS CADisplayLink结合UIBezierPath的

IOS CADisplayLink结合UIBezierPath的

作者: 887d1fc86fe6 | 来源:发表于2016-11-18 11:52 被阅读198次

    CADisplayLink结合UIBezierPath的神奇妙用

    IOS点滴

    做过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的案例,并附上了源代码地址。

    Github地址

    Github地址

    新建@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/

    相关文章

      网友评论

        本文标题:IOS CADisplayLink结合UIBezierPath的

        本文链接:https://www.haomeiwen.com/subject/hdgjpttx.html