QQ中的未读消息可以通过拖拽根据手指移动,在范围内拖拽会有一个粘性动画,当松开手指时还会有一个果冻效果。看到这个动画时,眨眼一看挺好看,仔细一想挺难,但深入分析后发现做起来不难。
一、整体思路
1.1. 构建UI
- 既然可以拖动,应该是一个view,使用
UIButton
或者UILabel
都可以,我们选择使用UIButton
; - 在按钮的原始位置创建一个尺寸小于按钮的view,作为‘吸盘’固定在该位置,吸盘和按钮是同一级,不是父子关系。
1
2
3
4self.placeView = [[UIView alloc] init];
self.placeView.frame = self.frame;
self.placeView.layer.cornerRadius = self.placeView.bounds.size.width * 0.5;
[self.superview insertSubview:self.placeView belowSubview:self];
1.2. 添加拖动事件
- 给
UIButton
添加拖动事件,让按钮能够跟随手指在屏幕上移动; - 拖动按钮时,让吸盘随着拖动距离按比例收缩;
- 拖动位移达到限定距离后让吸盘消失,按钮继续跟随手指位置;
- 取消拖动后,如果在限定距离内,让按钮回到原始位置,否则停留在当前触摸点。
1
2UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureHandle:)];
[self addGestureRecognizer:panGesture];
1.3. 计算路径(重点)
由于transform
不会修改center
,只会修改frame
,为了方便计算两个圆心之间的距离,所以不使用transform
让按钮发生位移动画。
1 | - (void)panGestureHandle:(UIPanGestureRecognizer *)gesture { |
圆心距离有了,只需要计算出两个圆的四个外切连线点坐标,然后把四个点连接起来并填充颜色就可以了。
四个点的坐标如何计算呢?
$已知 OA \perp AB { , } PB \perp AB { , } OA=PB= d \div 2 $
由上图可知,当动画改变时,两个圆距离和角度会发生变化,变换的值是距离d和角度θ。根据已知条件,用三角定理可以求出距离d,使用三角函数和相似三角原理可以求出其他各点的坐标(计算坐标时要用数学思维,不要把屏幕坐标系考虑在内)。
求距离d:
$ d = \sqrt{ (x_{1}-x_{2}) ^{2}+ (y_{1}-y_{2}) ^{2} } $
优先求出MN
和x2轴
的角θ正弦和余弦:
$ \sin \theta = \frac{ x_{2}-x_{1} }{d} $
$ \cos \theta = \frac{ y_{2}-y_{1} }{d} $
求A的坐标:
1 | AE = r1 * cosθ |
同理可得其他点坐标:
1 | B(x1 + r1 * cosθ, y1 - r1 * sinθ) |
最后只需要把所有点用贝塞尔曲线连接起来就行了。
1.4. 绘制形状(重点)
直接使用UIBezierPath
不能填充路径,而且超出路径范围后不显示,所以需要使用CAShapeLayer
根据路径生成一个形状,并形状添加到父视图上。
1 | UIBezierPath *path = [self getBezierPathWithView:self.placeView otherView:self]; |
二、实现细节
DBBadgeButton
1 | DBBadgeButton () |