先上一张效果图:
1.gif
首先分析一下动画, 是由哪些简单动画组成的, 分析完成之后, 把这些动画结合到一起就是复杂动画了:
一个拖动的圆, 一个在原位的圆
拖动的时候, 两个圆之前有封闭的, 中间逐渐变小的曲线连接
拖动的时候固定圆的大小会逐渐变小
拖动到一定距离会断开
松开时候回弹回到原位
那么针对前面分析的 5 步骤, 如何实现:
两个 view 画圆, 一个 view 的位置根据手势的位置进行移动
在两个圆上做切线相连, 中间缩小的程度可以根据拖拽的距离进行控制, 需要贝塞尔曲线中的控制点来实现
根据两个圆的圆心距离, 找出合适的百分比来实现固定圆的缩小的放大
当固定圆缩小到一定程度, 移除 layer 动画
记录固定圆的位置, 手势失败, 或者取消, 或者结束, 实现系统的弹性动画
第一步: 两个 view 画圆, 一个 view 的位置根据手势的位置进行移动
- // 全局两个 view
- @property (nonatomic,strong)UIView *view1;
- @property (nonatomic,strong)UIView *view2;
- // 添加 view 和 pan 手势
- - (void)viewDidLoad {
- [super viewDidLoad];
- UIView *view1 = [[UIView alloc]initWithFrame:CGRectMake(36, self.view.frame.size.height - 66, 40, 40)];
- view1.backgroundColor = [UIColor redColor];
- view1.layer.cornerRadius = 20;
- view1.layer.masksToBounds = YES;
- _view1 = view1;
- [self.view addSubview:view1];
- UIView *view2 = [[UIView alloc]initWithFrame:view1.frame];
- view2.backgroundColor = [UIColor redColor];
- view2.layer.cornerRadius = 20;
- view2.layer.masksToBounds = YES;
- _view2 = view2;
- UILabel *label = [[UILabel alloc]initWithFrame:_view2.bounds];
- label.text = @"99+";
- label.textAlignment = NSTextAlignmentCenter;
- label.textColor = [UIColor whiteColor];
- [_view2 addSubview:label];
- [self.view addSubview:view2];
- UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panAction:)];
- [_view2 addGestureRecognizer:pan];
- // Do any additional setup after loading the view, typically from a nib.
- }
第二步: 在两个圆上做切线相连, 中间缩小的程度可以根据拖拽的距离进行控制, 需要贝塞尔曲线中的控制点来实
由于本人电脑绘图水平为 0, 所以在小本本上画了一个图 (找不到圆规)
5C537A5183B286EC7658FB4FFF9A7BC2.jpg
分析:
两个圆的位置, 以及半径我们都可以获取的到, 然后就是求 A,B,C,D,P,O 点了
我又重新复习了一下初中的三角函数, 并且标出来了∠1 和∠2,∠1+∠2=90°,
∠1 的 sin 和 cos 我们都可以直接求出来的,
A:∠1 的对边不知道, 但是斜边知道就是 r1, 所以他的 x, 和 y 我们都可以求出来, 然后根据固定圆的中心点, 就知道绝对位置了
B: 同理∠1 的对边不知道, 但是斜边知道就是 r1, 所以他的 x, 和 y 我们都可以求出来, 然后根据固定圆的中心点, 就知道绝对位置了
C,D 点同理, 不再赘述, 我都把∠1 都标注出来了的, 根据拖动圆的圆心就可算出来绝对位置
现在是最关键的 P,O
P: 坐在两个圆的圆心的连线上另外做了一条辅助线平行于圆心的连线, p,d,o 在一条直线上, 那么 p 也就可以看出了,∠1 知道了, 斜边是 2/d
O: 同 p 点解法
那么就来撸代码算出这 6 个点:
- //1 求出 2 个中心点
- CGPoint center1 = self.view1.center;
- CGPoint center2 = self.view2.center;
- //2 求出 2 个中心点的距离
- CGFloat dis = sqrtf((center1.x - center2.x)*(center1.x - center2.x) + (center1.y - center2.y)*(center1.y - center2.y));
- //3 计算∠1 正弦余弦
- CGFloat sin = (center2.x - center1.x)/dis;
- CGFloat cos = (center2.y - center1.y)/dis;
- // 两个圆的半径
- CGFloat r1 = self.oldView1Frame.size.width*0.5;
- CGFloat r2 = self.view2.frame.size.width*0.5;
- // 计算 6 个点
- CGPoint pA = CGPointMake(center1.x - r1*cos,center1.y + r1*sin);
- CGPoint pB = CGPointMake(center1.x + r1*cos,center1.y - r1*sin);
- CGPoint pC = CGPointMake(center2.x + r2*cos, center2.y - r2*sin);
- CGPoint pD = CGPointMake(center2.x - r2*cos, center2.y + r2*sin);
- CGPoint pP = CGPointMake(pA.x + dis/2*sin, pA.y + dis/2*cos);
- CGPoint pO = CGPointMake(pB.x + dis/2*sin, pB.y + dis/2*cos);
- // 然后就用贝塞尔曲线链接, 那么我们需要用到 UIBezierPath 还有 CAShapeLayer
- @property (nonatomic,strong)CAShapeLayer *shapeLayer;
我们需要在初始化两个 view 的时候, 将 layer 添加到两个 view 上
- // 实例化 lshapeLayer
- CAShapeLayer *shapLayer = [CAShapeLayer layer];
- self.shapeLayer = shapLayer;
- self.shapeLayer.fillColor = [UIColor redColor].CGColor;
贝塞尔曲线链接 6 个点, 并添加到 layer 上:
- UIBezierPath *path = [UIBezierPath bezierPath];
- [path moveToPoint:pA];
- [path addQuadCurveToPoint:pD controlPoint:pP];
- [path addLineToPoint:pC];
- [path addQuadCurveToPoint:pB controlPoint:pO];
- [path closePath];
- self.shapeLayer.path = path.CGPath;
- [self.view.layer insertSublayer:self.shapeLayer below:self.view2.layer];
第三步: 根据两个圆的圆心距离, 找出合适的百分比来实现固定圆的缩小的放大
我这里也是试出来的, 我再拖动的时候, 让固定圆的半径根据两个圆心的距离来缩小, 我用固定圆最开始的半径减去圆心距离的 20 分之一, 所以要记录最开始的小圆的大小我又增加了几个全局变量:
- @property (nonatomic,assign)CGPoint oldView1Center;
- @property (nonatomic,assign)CGRect oldView1Frame;
- @property (nonatomic,assign)CGFloat view1R;
设置全局变量的值在初始化 UI 的时候:
- self.oldView1Frame = self.view1.frame;
- self.oldView1Center = self.view1.center;
- self.view1R = self.view1.frame.size.width*0.5;
所以上面设置 r1 修改为:(这个 1/20 可以修改为其他的值)
CGFloat r1 = self.oldView1Frame.size.width*0.5 - dis/20;
最后重新设置 view1 的 frame:
- // 重新设置 view 的大小
- self.view1R = r1;
- self.view1.bounds = CGRectMake(0, 0, r1*2, r1*2);
- self.view1.center = self.oldView1Center;
- self.view1.layer.cornerRadius = r1;
- self.view1.layer.masksToBounds = YES;
第四步: 当固定圆缩小到一定程度, 移除 layer 动画
我在手势动画里面判断, 当固定圆的半径小于我设置的 5 时, 就去移除动画:
- if (ges.state == UIGestureRecognizerStateChanged) {
- //view2 跟着移动
- _view2.center = [ges locationInView:self.view];
- // 计算出 6 个点 画出贝塞尔曲线
- if (self.view1R < 5) {
- self.view1.hidden = YES;
- [_shapeLayer removeFromSuperlayer];
- }else{
- [self calutePoint];
- }
- }
第五步: 记录固定圆的位置, 手势失败, 或者取消, 或者结束, 实现系统的弹性动画
- [_shapeLayer removeFromSuperlayer];
- self.view1.hidden = NO;
- [UIView animateWithDuration:.5 delay:0 usingSpringWithDamping:.3 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
- self.view2.center = self.oldView1Center;
- self.view1R = self.oldView1Frame.size.width/2;
- self.view1.frame = self.oldView1Frame;
- self.view1.layer.cornerRadius = self.view1R;
- } completion:^(BOOL finished) {
- }];
[最后附上 Git Demo] https://github.com/gnaw9258wp/qq-Drag
来源: http://www.jianshu.com/p/e2fc3bc58979