動畫-CAShapeLayer實現QQ訊息紅點拖拽效果

杭城小劉發表於2018-06-11

CAShapeLayer

一言以蔽之:CAShapeLayer 可以根據貝塞爾曲線描繪出的路徑而生成對應的圖形

綜合例子

  • 效果圖

QQ粘性動畫

  • 關鍵技術點剖析

  • 分析 QQ 粘性動畫的關鍵點就是當手勢拖動時候2個圓之間那個形狀怎麼繪製

答案:將2個圓的某一時刻之間形成的形狀用數學抽象來計算。

軌跡分解

  • 拖動到超過某個範圍的時候怎麼執行爆炸動畫

UIImageView 可以執行幀動畫,類似於 Flash 效果

關鍵程式碼

- (void)pan:(UIPanGestureRecognizer *)pan{
//當前移動的偏移量
CGPoint transP = [pan translationInView:self];
//改變紅點的位置
//transform並沒有修改自身的 center(center 是 layer 的position),只是修改了 frame
NSLog(@"偏移量:%@",NSStringFromCGPoint(transP));
CGPoint center = self.center;
center.x += transP.x;
center.y += transP.y;
self.center = center;
//self.transform = CGAffineTransformTranslate(self.transform, transP.x, transP.y);
//手勢復位:設定座標原點位上次的座標
[pan setTranslation:CGPointZero inView:self];
CGFloat distance = [self distanceWith:self.smallCircle bigCircle:self];
NSLog(@"%f",distance);
CGFloat smallCircleRadius = self.bounds.size.width * 0.5;
smallCircleRadius = smallCircleRadius - distance/10;
if (smallCircleRadius < 3) {
smallCircleRadius = 3;
}
self.smallCircle.bounds = CGRectMake(0, 0, smallCircleRadius*2, smallCircleRadius*2);
self.smallCircle.layer.cornerRadius = smallCircleRadius;
if (self.smallCircle.hidden == NO) {
//返回一個不規則的路徑
UIBezierPath *path = [self drawTracertWithSmallCircle:self.smallCircle bigCircle:self];
//將形狀轉換為一個形狀圖層
self.shapeLayer.path = path.CGPath;//根據路徑生成形狀
}
//建立形狀圖層
[self.superview.layer insertSublayer:self.shapeLayer atIndex:0];
if (distance > 60) {
self.smallCircle.hidden = YES;
[self.shapeLayer removeFromSuperlayer];
}
if (pan.state == UIGestureRecognizerStateEnded) {
//結束手勢
if (distance < 60) {
[self.shapeLayer removeFromSuperlayer];
self.center = self.smallCircle.center;
self.smallCircle.hidden = NO;
}
else{
//手勢拖拽超過60則播放一個動畫
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
NSMutableArray *images = [NSMutableArray array];
for (int i=0; i<8; i++) {
NSString *imageName = [NSString stringWithFormat:@"%d",i+1];
UIImage *image = [UIImage imageNamed:imageName];
[images addObject:image];
}
imageView.animationImages = images;
[imageView setAnimationDuration:1];
[imageView startAnimating];
[self addSubview:imageView];
//動畫結束移除本身
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self removeFromSuperview];
});
}
}
}
- (CGFloat )distanceWith:(UIView *)smallCircle bigCircle:(UIView *)bigScirle{
CGFloat offsetX = bigScirle.frame.origin.x - smallCircle.frame.origin.x;
CGFloat offsetY = bigScirle.frame.origin.y - smallCircle.frame.origin.y;
return sqrt( pow(offsetX, 2) + pow(offsetY, 2));
}
//將2個圓執行的變化軌跡用程式碼模擬
- (UIBezierPath *)drawTracertWithSmallCircle:(UIView *)smallCircle bigCircle:(UIView *)bigCircle{
CGFloat X1 = smallCircle.center.x;
CGFloat X2 = bigCircle.center.x;
CGFloat Y1 = smallCircle.center.y;
CGFloat Y2 = bigCircle.center.y;
CGFloat r1 = smallCircle.bounds.size.width/2;
CGFloat r2 = bigCircle.bounds.size.width/2;
CGFloat d = [self distanceWith:smallCircle bigCircle:bigCircle];
//Ø 代表角度
CGFloat SinØ = (X2 - X1)/d;
CGFloat CosØ = (Y2 - Y1)/d;
CGPoint pointA = CGPointMake(X1 - r1*CosØ, Y1 + r1*SinØ);
CGPoint pointB = CGPointMake(X1 + r1*CosØ, Y1 - r1*SinØ);
CGPoint pointC = CGPointMake(X2 + r2*CosØ, Y2 - r2*SinØ);
CGPoint pointD = CGPointMake(X2 - r2*CosØ, Y2 + r2*SinØ);
CGPoint pointO = CGPointMake(X1 + SinØ *d/2, Y1 + CosØ*d/2);
CGPoint pointP = CGPointMake(X1 + SinØ *d/2,Y1 + CosØ*d/2 );
//描述路徑
UIBezierPath *path = [UIBezierPath bezierPath];
//AB
[path moveToPoint:pointA];
[path addLineToPoint:pointB];
//BC(曲線)
[path addQuadCurveToPoint:pointC controlPoint:pointP];
//CD
[path addLineToPoint:pointD];
//DA(曲線)
[path addQuadCurveToPoint:pointA controlPoint:pointO];
return path;
}
複製程式碼

完整的程式碼,Github地址

相關文章