iOS Animation学习笔记-贝塞尔曲线

结合《A Guide To iOS Animation》该书学习iOS动画。

贝塞尔曲线扫盲
该文章通熟易懂,解释了“贝塞尔曲线”这个词。

iOS Animation学习笔记-贝塞尔曲线_第1张图片
屏幕快照 2017-12-04 上午8.57.58.png

“小球由 弧AB,弧BC,弧CD,弧DA 组成。
其中 弧AB 是一条由 A,B 加两个控制点 C1,C2 一共四个点绘制的三次贝塞尔曲线。其他弧线段同理。”

“代码中的 (CGContextRef)ctx 字面意思是指上下文,你可以理解为一块全局的画布。也就是说,一旦在某个地方改了画布的一些属性,之后任何地方使用该画布属性都是改了之后的。比如上面在 //1 中把线条样式改成了虚线,那么在下文 //2 中如果不恢复成连续的直线,那么画出来的依然是虚线样式。”

CGFloat dash[] = {5.0, 5.0};
CGContextSetLineDash(ctx, 0.0, dash, 2); //1,设置边框正方形虚线
.....
CGContextSetLineDash(ctx, 0, NULL, 0); //2,设置圆形边框为实线
...
CGFloat dash2[] = {2.0, 2.0};
CGContextSetLineDash(ctx, 0.0, dash2, 2);//设置Ac1c2Bc3c4Cc5c6Dc7c8这个多边形的边框虚线

绘制部分

-(void)drawInContext:(CGContextRef)ctx{
    
    //A-C1、B-C2... 的距离,当设置为正方形边长的1/3.6倍时,画出来的圆弧完美贴合圆形
    CGFloat offset = self.outsideRect.size.width / 3.6;
    
    //A.B.C.D实际需要移动的距离.系数为滑块偏离中点0.5的绝对值再乘以2.当滑到两端的时候,movedDistance为最大值:「外接矩形宽度的1/6」.
    CGFloat movedDistance = (self.outsideRect.size.width * 1 / 6) * fabs(self.progress-0.5)*2;
    
    //方便下方计算各点坐标,先算出外接矩形的中心点坐标
    CGPoint rectCenter = CGPointMake(self.outsideRect.origin.x + self.outsideRect.size.width/2 , self.outsideRect.origin.y + self.outsideRect.size.height/2);
    
    
    CGPoint pointA = CGPointMake(rectCenter.x ,self.outsideRect.origin.y + movedDistance);
    CGPoint pointB = CGPointMake(self.movePoint == POINT_D ? rectCenter.x + self.outsideRect.size.width/2 : rectCenter.x + self.outsideRect.size.width/2 + movedDistance*2 ,rectCenter.y);
    CGPoint pointC = CGPointMake(rectCenter.x ,rectCenter.y + self.outsideRect.size.height/2 - movedDistance);
    CGPoint pointD = CGPointMake(self.movePoint == POINT_D ? self.outsideRect.origin.x - movedDistance*2 : self.outsideRect.origin.x, rectCenter.y);
    //.......
}

“offset 指的是 A-C1,B-C2... 的距离,当该值设置为正方形边长的 1/3.6 倍时,画出来的圆弧近似贴合 1/4 圆。为什么是 3.6 ?这里 有一篇文章。文章里三阶贝塞尔曲线拟合 1/4 圆的时候最佳参数 h=0.552, 表示的意义是:当正方形边长的 1/2 为 1 ( 即正方形边长为 2) 时, offset 等于 0.552 就能使圆弧近似贴近 1/4 圆。所以比例系数为 1/0.552 ,即正方形边长和 offset 的比例系数为:2/0.552 = 3.623。近似于 3.6。其实还有种更直观的近似方法:如果圆心为 O,OC1, OC2 就一定是三等分点,也就是夹角为 30°,那么 AC1 (也就是 offset )就等于 1/2的边长 * tan30° 。

顺便解释两个地方.“1.为什么要*2? 因为为了让 D 点区别于 A 点,让 D 移动距离比 A 多,你完全可以 3 ,2.5,但是不能移动太远。2.为什么要是1/6?这个 1/6 也是自己定的。你可以让 A 移动 1/7 ,1/10 都可以,但是最好不要太靠近 1/2,这时 A 点就移到中点了,形变的样子就不好看了。”

animatedCircle.gif

http://kittenyang.com/drawablebubble/
摘录来自: 杨骑滔(KittenYang). “A GUIDE TO IOS ANIMATION”。 iBooks.

未读气泡拖拽消失效果

“这个交互中,难点在于如何绘制贝塞尔曲线。而线又是由点组成的。所以最终归根结底我们还是要找个关键点的坐标。一图胜千言。下面,我绘制了一幅分析图,这样一来问题就转化成了一个高中数学求点坐标的题目了。”


iOS Animation学习笔记-贝塞尔曲线_第2张图片
路径分析.png

“在panGesture 中的UIPanGestureStateChanged中去实时重绘贝塞尔曲线”

- (void)displayLinkAction {
    x1 = backView.center.x;
    y1 = backView.center.y;
    x2 = self.frontView.center.x;
    y2 = self.frontView.center.y;
    
    centerDistance = sqrtf((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
    if (centerDistance == 0) {
        cosDigree = 1;
        sinDigree = 0;
    }else {
        cosDigree = (y2 - y1) / centerDistance;
        sinDigree = (x2 - x1) / centerDistance;
    }
    //centerDistance / self.viscosity 拖拽过程中,原先圆球半径渐渐变小,
    //这里减去一个两球圆心距离与系数viscosity的比
    r1 = oldBackViewFrame.size.width / 2 - centerDistance / self.viscosity;

    pointA = CGPointMake(x1 - r1 * cosDigree, y1 + r1 * sinDigree); // A
    pointB = CGPointMake(x1 + r1 * cosDigree, y1 - r1 * sinDigree); // B
    pointD = CGPointMake(x2 - r2 * cosDigree, y2 + r2 * sinDigree); // D
    pointC = CGPointMake(x2 + r2 * cosDigree, y2 - r2 * sinDigree); // C
    pointO = CGPointMake(pointA.x + (centerDistance / 2) * sinDigree,
                         pointA.y + (centerDistance / 2) * cosDigree);
    pointP = CGPointMake(pointB.x + (centerDistance / 2) * sinDigree,
                         pointB.y + (centerDistance / 2) * cosDigree);
    [self drawRect];
}

在拖拽过程中,等移动完成后,会移除上一次 layer [shapeLayer removeFromSuperlayer];手势每次开始移动,在displayLinkAction方法中重新绘制贝塞尔曲线。

- (void)handleDragGesture:(UIPanGestureRecognizer *)ges {
    CGPoint dragPoint = [ges locationInView:self.containerView];
    if (ges.state == UIGestureRecognizerStateBegan) {
        backView.hidden = NO;
        fillColorForCute = self.bubbleColor;
        [self RemoveAniamtionLikeGameCenterBubble];
    }else if (ges.state == UIGestureRecognizerStateChanged) {
        self.frontView.center = dragPoint;
        if (r1 <= 6) {
            fillColorForCute = [UIColor clearColor];
            backView.hidden = YES;
            [shapeLayer removeFromSuperlayer];
        }
        [self displayLinkAction];
    }else if (ges.state == UIGestureRecognizerStateEnded ||
              ges.state == UIGestureRecognizerStateCancelled ||
              ges.state == UIGestureRecognizerStateFailed) {
        if (r1 <= 6) {
            [UIView animateWithDuration:3.0f
                             animations:^{
                                 if (self.backgroundColor == [UIColor whiteColor]) {
                                     self.backgroundColor = [UIColor blackColor];
                                 }
                                 else {
                                     self.backgroundColor = [UIColor whiteColor];
                                 }
                             }
             completion:^(BOOL finished) {
                 if (finished) {
                     self.frontView.layer.opacity = 1;
                     backView.hidden = YES;
                     fillColorForCute = [UIColor clearColor];
                     [shapeLayer removeFromSuperlayer];
                     
                     self.frontView.center = oldBackViewCenter;
                     [self AddAniamtionLikeGameCenterBubble];
                 }
             }
             ];
        }else {
            backView.hidden = YES;
            fillColorForCute = [UIColor clearColor];
            [shapeLayer removeFromSuperlayer];//移除
            [UIView animateWithDuration:0.5
                                  delay:0.0f
                 usingSpringWithDamping:0.4f
                  initialSpringVelocity:0.0f
                                options:UIViewAnimationOptionCurveEaseInOut
                             animations:^{
                                 self.frontView.center = oldBackViewCenter;
                             } completion:^(BOOL finished) {
                                 if (finished) {
                                     [self AddAniamtionLikeGameCenterBubble];
                                 }
                             }];
        }
    }
}

效果


iOS Animation学习笔记-贝塞尔曲线_第3张图片
cuteBubble.gif

QQ中未读气泡拖拽消失的实现分析

你可能感兴趣的:(iOS Animation学习笔记-贝塞尔曲线)