本来只是想写个可以拖动的小红点的,但写出乐趣来了,所以就多研究了研究,最后干脆封装了一下,可以实现给所有 View 添加黏贴效果。
GitHub 链接
效果如图:
首先需要了解贝塞尔曲线,我觉得下面这个动图,看一遍就知道贝塞尔曲线怎么回事了。
这是一个控制点(P1)的贝塞尔曲线,如果是两个控制点,则如下图
一个控制点的取了两次点,才绘制出贝塞尔曲线,所以称为二次曲线,两个控制点的取了三次点,所以称为三次曲线。
绘制贝塞尔曲线有一个数字需要记住,3.6。
即 AB 和 CD 都垂直于 AD,AB 和 CD 的距离为 AB 距离的 1/3.6,绘制出的弧线是一个非常接近 1/4 圆的弧线。
实现思路:
需要绘制黏贴效果
拖动的 View 需要盖住其它所有其它的 View
根据拖动的距离判断松手时是需要爆炸动画还是回弹动画。
效果如下
转换成模型图如下
小圆是小红点一开始的位置,大的圆是小红点拖过去的位置,小圆随着拖动距离增大而变大,两个圆的连接其实就是两贝塞尔曲线,临时点 point_temp 根据两个圆距离的增加从 p2 向 p4 移动,连接临时点和 P1 连线中点 P5 就是控制点了,即可绘制出这条贝塞尔曲线,p3 和 p5 之间的贝塞尔曲线同理。
接下来就是计算各个点的坐标了,捡起高中的数学,还是很简单的。得出各个点的坐标,然后绘制一个大圆一个小圆,中间绘制两条贝塞尔曲线,给上填充颜色。黏贴效果就出来了。每次拖动 View 的时候再重新绘制一遍。
需要让拖动的 View 覆盖在所有 View 上方,这个还是很重要的,要不然在 tableViewCell 里进行拖动的时候,会被其它的 Cell 盖住,我的实现方式是声明一个全屏大小的 RedDotView 添加到 [[[UIApplication sharedApplication].delegate window] 上,然后将拖动的 View 拷贝一份,放置到 RedDotView 上方,隐藏旧 View。这样拖动的 View 就能盖住所有其它的 View ,包括导航栏。
拷贝我取了个巧,直接将旧 View 绘制成一张 image,放置到新 View 上显示,
- (UIImage *)getImageFromView:(UIView *)view {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, UIScreen.mainScreen.scale);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
爆炸效果则是给 imageView 的 animationImages 属性添加六张图片,在超出最大距离之后播放。
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 1; i < 6; i++) {
UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"red_dot_image_%d", i]];
[array addObject:image];
}
UIImageView *imageView = [[UIImageView alloc] init];
imageView.frame = CGRectMake(0, 0, radius*2, radius*2);
imageView.center = explosionPoint;
imageView.animationImages = array;
[imageView setAnimationDuration:0.25];
[imageView setAnimationRepeatCount:1];
[imageView startAnimating];
[self addSubview:imageView];
```
回弹效果是给 View 一个弹簧动画,让 View 弹回旧 View 中心的位置
```objc
[UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:0.2 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
view.center = point;
} completion:^(BOOL finished) {
......
}];
```
总结:还是有些遗憾,做完之后才注意到 QQ 的实现上,p1 和 p3 连线并不经过圆心,所以效果和 QQ 的还是有些差别,另外为了让中间的贝塞尔曲线不会和大圆重合,中间的贝塞尔曲线我用的是两条贝塞尔曲线实现,这样更圆滑一些,但也留了个坑,中间的弧度还是得再调整一下。
参考资料:
[QQ手机版 5.0“一键下班”设计小结](https://isux.tencent.com/qq-mobile-off-duty.html)