iOS粘性拖拽红点动画研究

 手机QQ有个针对强迫症很好的功能,就是未读消息红点的拖拽效果。下面我就来讲解要如何来实现这种效果。

            

一、预备知识

1.CAShapeLayer

      CAShapeLayer是CALayer的子类,它的特别之处在于它的形状可以通过贝塞尔曲线任意定制。

ex:

    cutePath = [UIBezierPath bezierPath];
    shapeLayer.path = [cutePath CGPath];

2.UIBezierpath

       为了实现拖拽的效果,我们需要用到UIBezierPath类来绘制曲线。UIBezierPath类可以用于创建基于矢量的路径,这个类在UIKit中。此类是Core Graphics框架关于path的一个封装。使用它可以定义简单的形状,如椭圆或者矩形,或者有多个直线和曲线段组成的形状。

ex:

    cutePath = [UIBezierPath bezierPath];
    [cutePath moveToPoint:pointA];
    [cutePath addCurveToPoint:pointD controlPoint1:pointO1 controlPoint2:pointO2];
    [cutePath addLineToPoint:pointC];
    [cutePath addCurveToPoint:pointB controlPoint1:pointP2 controlPoint2:pointP1];
    [cutePath moveToPoint:pointA];

      在UIBezierPath中有两个绘制曲线(curve)的API:

- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2; //三阶贝塞尔曲线
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint; //二阶贝塞尔曲线

      这两个方法的区别在于控制点的个数不同,控制点是用来描述曲线弯曲方向的点,如下图的P1。具体的数学解释可以阅读这篇博客。 

                                

       下图左侧是两个控制点的效果,右侧是单个控制点的。当然为了让形如右侧的曲线表现的更加平滑自然,使用多个控制点绘制一段单弧曲线也未尝不可。

  

二、总体实现方法

1.CAShapeLayer方法

       这种方法的不同之处在于 中间绿色的部分是使用CAShapeLayer来完成,蓝色的圆点和红色的圆点都是两个独立的view

      下面我将针对CAShapeLayer这种实现方法来进行解释。

二、形变的计算

      使用CAShapeLayer实现起来整体并没有太大的障碍,难点仅在于贝塞尔曲线上各个点的计算。

1.二阶贝塞尔曲线

       如下图所示,使OA ⊥ AB, PB ⊥ AB ,且 OA=PB=d/2.(你也可以自己定制角DAB的角度与OA、PB的长度,不过这样做最方便也比较美观) 这就转化为了一道高中数学题,已知的是S点、T点的坐标,r1 , r2; 进而能够得到ST的长度,与θ的sin与cos值。向右延长y1T,可知角ATy1=θ,其它同理。有了这些我们就可以轻松求得点A、B、C、D、O、P的坐标。

                                                                                                       (图片来自网络)

转化为代码:

    cosDigree = (y1-y2)/centerDistance;
    sinDigree = (x2-x1)/centerDistance;
    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);

    cutePath = [UIBezierPath bezierPath];
    //二次贝塞尔曲线
    [cutePath moveToPoint:pointA];
    [cutePath addQuadCurveToPoint:pointD controlPoint:pointO];
    [cutePath addLineToPoint:pointC];
    [cutePath addQuadCurveToPoint:pointB controlPoint:pointP];
    [cutePath moveToPoint:pointA];

    shapeLayer.path = [cutePath CGPath];
    shapeLayer.fillColor = [UIColor greenColor].CGColor;

2.三阶贝塞尔曲线

       如果二阶曲线并不能满足红点的“弹性”感,我们不妨用上面的方式,来计算出三阶贝塞尔曲线两个控制点的坐标。为了看清各个角度的关系,我画了下面这张图

                                      (上图说明:QA⊥AT  , O1T1⊥AT)

       首先,我们要给出一些控制点的设置,我们设∠QAO1为α,这个α可以代表曲线在第一个控制点的弯曲程度;设 O1T1 = ST/4,O2T1 = ST/2,1/4和1/2这两个分数可以抽象的想成是曲线弯曲的位置。(ST即为手指触点与小圆圆心之间的距离)

  好了,现在我们就可以开始计算O1点的坐标了。通过上文的方法,我们已经可以得出A点的坐标。现在列出我们的已知的条件:

                              A.x,A.y;

            O1T1 = ST/4;

            ∠AO1T1 = ∠QAO1 = α;

            α已知;ST已知;

     那么就可以得出:

                   AO1 = O1T1 */cos(α) = ST/4/cos(α)

                   O1.x = A.x + AO1 * sin(α+θ);

                   O1.y = A.y - AO1 * cos(α+θ);(这里是减号,注意iOS上的坐标系与我画的不同)

      再将(α+θ)的三角函数展开,我们就得到了点O1的坐标。

转化为代码:

    cosDigree = (y1-y2)/centerDistance;
    sinDigree = (x2-x1)/centerDistance;        
    cosAO1T1 = cos(BezierAngle * M_PI/180);
    sinAO1T1 = sin(BezierAngle * M_PI/180);        
    cosO1 = cosDigree * cosAO1T1 - sinDigree * sinAO1T1;
    sinO1 = sinDigree * cosAO1T1 + cosDigree * sinAO1T1;
    pointO1 = CGPointMake(pointA.x + centerDistance*sinO1/(4*cosAO1T1), pointA.y-centerDistance*cosO1/(4*cosAO1T1));

 

      下面计算点O2的坐标。

       貌似∠O2AT1不太好搞,那么我们先设它为β。有了上面求O1点的经验,可以知道只要知道∠A O2 Xo2的三角函数就能够得到O2的坐标了。

已知条件:          O2T1, AT1;(上文通过α计算过了)

                          O2T1 ⊥ AT1;

                          ∠θ的三角函数已知;

那么可以得到:

                    O2T1 = ST/2;

                    O2A = √ ̄((AT1)² +(O2T1)²);(勾股定理)

                     O2.x = A.x + O2A * sin(β+θ);

                     O2.y = A.y - O2A * cos(β+θ);

代码:

         //角β
        AO2 = sqrt(pow(O2T1, 2) + pow(AT1, 2));
        cosAO2T1 = O2T1/AO2;
        sinAO2T1 = AT1/AO2;
        cosO2 = cosAO2T1 * cosDigree - sinAO2T1 * sinDigree;
        sinO2 = sinDigree * cosAO2T1 + cosDigree * sinAO2T1;
        pointO2 = CGPointMake(pointA.x + AO2 * sinO2, pointA.y - AO2*cosO2);

   这样我们就得到了O2的坐标~

这样我们就可以绘制曲线了:

    [cutePath addCurveToPoint:pointD controlPoint1:pointO1 controlPoint2:pointO2];

 

       至此,革命尚未成功,因为我们只画了一边的曲线。同理我们也可以计算出另一边的控制点,而且由于对称性,左边计算好的β的三角函数值是可以直接拿来用的,只不过右侧的角度不是β+θ,而是θ-β

四、总结

      类似的粘性动画还有很多,红点拖拽效果只是其中一种。二阶贝塞尔曲线的计算是学习自github的大神,通过他的思路我实现了三阶贝塞尔曲线的效果。

      如有不足与错误还请指出。demo的github链接:https://github.com/MeowWang/RedDotDrag,欢迎大家下载。

      原项目的github链接:https://github.com/KittenYang/KYCuteView

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值