手机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