實例詳講iOS實現QQ粘性動畫效果
這幾天做了一些簡單iOS的效果圖,感覺蘋果官方已經幫我們做了很多了,我們只是站在巨人的肩膀上編程,這些也沒什么難的,最難的也就是用到了初中的三角函數,先讓大家看看這幾個動畫吧。




粒子圖
思路
- 添加控件
- 添加手勢畫線
- 重寫drawRect方法
- 添加Layer
- 添加動畫
HUD指示器
- 添加layer
- 添加動畫
- 添加復制圖層
緩沖跳動
- 添加控件
- 添加layer
- 添加動畫
- 添加復制層
QQ粘性效果
--思路
新建自定義UIButton
添加拖動手勢
計算圓的變化
計算圓之間矩形并且填充
回彈
爆炸
--步驟
先完成空間布局以及手勢添加
然后計算圓的變化,計算圓之間的矩形面積
效果微調
預覽代碼結構圖
詳細步驟
- 在一個storyBoard里面拖一個UIButton進去,然后新建一個自定義UIBUtton類 如:ETBUtton類與之對應,好了,一切新建工作完畢,下面,我們只需要在自定義的UIButton類里面做功夫就好了,簡單的代碼直接上好
#import "ETStickBtn.h" @interface ETStickBtn () @property (nonatomic, strong) UIView smalCirView; @property (nonatomic, assign) NSInteger oriRadius; @property (nonatomic, strong) CAShapeLayer shapeLayer; @end @implementation ETStickBtnpragma mark - 懶加載數據
- (UIView )smalCirView{ if (!_smalCirView) { // 新建一個圓 UIView smalCirView = [[UIView alloc] init]; // smalCirView.frame = self.frame; smalCirView.backgroundColor = self.backgroundColor; [self.superview insertSubview:smalCirView belowSubview:self]; _smalCirView = smalCirView; } return _smalCirView; }
- (CAShapeLayer )shapeLayer{ if (!_shapeLayer) { CAShapeLayer shapeLayer = [CAShapeLayer layer]; shapeLayer.fillColor = self.backgroundColor.CGColor; [self.superview.layer insertSublayer:shapeLayer below:self.layer]; _shapeLayer = shapeLayer; } return _shapeLayer; } #pragma mark - 系統初始化
- (id)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { [self setUp]; } return self; }
- (void)awakeFromNib{ [self setUp]; } #pragma mark - 初始化視圖
- (void)setUp{
CGFloat w = self.bounds.size.width;
self.layer.cornerRadius = w / 2;
self.smalCirView.layer.cornerRadius = w/2;
// 記錄半徑
_oriRadius = w/2;
_smalCirView.frame = self.frame;
UIPanGestureRecognizer pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:pan];
}</pre>
在添加完Pan手勢后當然我們要在自定義的Pan方法里面做功夫,先看看已經實現了拖動效果的Pan代碼塊吧。- (void)pan:(UIPanGestureRecognizer
)pan{ // 移動 CGPoint transPoint = [pan translationInView:self]; CGPoint center = self.center; center.x += transPoint.x; center.y += transPoint.y; self.center = center; [pan setTranslation:CGPointZero inView:self]; }</pre>
就這樣,可以拖動啦,接下來你想做什么呢?我就想你不斷拖動大圓的過程中,小圓的半徑一直減少直到為0, 具體思路是圓心距越大,小圓半徑越小 ,這是奉上此時的Pan代碼。- (void)pan:(UIPanGestureRecognizer )pan{ // 移動 CGPoint transPoint = [pan translationInView:self]; CGPoint center = self.center; center.x += transPoint.x; center.y += transPoint.y; self.center = center; [pan setTranslation:CGPointZero inView:self]; // 設置小圓變化的值 CGFloat cirDistance = [self distanceWithPointA:self.center andPointB:self.smalCirView.center]; CGFloat smallCirRadius = _oriRadius - cirDistance/10.0; if(smallCirRadius<0) smallCirRadius = 0; _smalCirView.bounds = CGRectMake(0, 0, smallCirRadius 2, smallCirRadius * 2); self.smalCirView.layer.cornerRadius = smallCirRadius; } #pragma mark - 獲取圓心距離
- (CGFloat)distanceWithPointA:(CGPoint)pointA andPointB:(CGPoint)pointB{
CGFloat offSetX = pointA.x - pointB.x;
CGFloat offSetY = pointA.y - pointB.y;
return sqrt(offSetXoffSetX + offSetYoffSetY);
}</pre>
- 好了,現在小圓終于可以隨著兩圓心距的變大而變小了,之后重頭戲來了,獲取貝塞爾曲線路徑,也就是繪制兩圓之間的曲線部分
- 這個也不難,只是用到初中的知識,先上個原型圖(手繪)
- 咳咳,由于用鉛筆畫的,有點模糊,那就奉上一個根據原型圖用電腦繪制的圖吧!
- 至此,重要的點都已經計算出來的,也不難吧,把算式換成代碼如下
- (UIBezierPath *)getBezierPathWithSmallCir:(UIView *)smallCir andBigCir:(UIView *)bigCir{ // 獲取最小的圓 if (bigCir.frame.size.width < smallCir.frame.size.width) { UIView *view = bigCir; bigCir = smallCir; smallCir = view; } // 獲取小圓的信息 CGFloat d = [self distanceWithPointA:smallCir.center andPointB:bigCir.center]; CGFloat x1 = smallCir.center.x; CGFloat y1 = smallCir.center.y; CGFloat r1 = smallCir.bounds.size.width/2; // 獲取大圓的信息 CGFloat x2 = bigCir.center.x; CGFloat y2 = bigCir.center.y; CGFloat r2 = bigCir.bounds.size.width/2; // 獲取三角函數 CGFloat sinA = (y2 - y1)/d; CGFloat cosA = (x2 - x1)/d; // 獲取矩形四個點 CGPoint pointA = CGPointMake(x1 - sinA*r1, y1 + cosA * r1); CGPoint pointB = CGPointMake(x1 + sinA*r1, y1 - cosA * r1); CGPoint pointC = CGPointMake(x2 + sinA*r2, y2 - cosA * r2); CGPoint pointD = CGPointMake(x2 - sinA*r2, y2 + cosA * r2); // 獲取控制點,以便畫出曲線 CGPoint pointO = CGPointMake(pointA.x + d / 2 * cosA , pointA.y + d / 2 * sinA); CGPoint pointP = CGPointMake(pointB.x + d / 2 * cosA , pointB.y + d / 2 * sinA); // 創建路徑 UIBezierPath *path =[UIBezierPath bezierPath]; [path moveToPoint:pointA]; [path addLineToPoint:pointB]; [path addQuadCurveToPoint:pointC controlPoint:pointP]; [path addLineToPoint:pointD]; [path addQuadCurveToPoint:pointA controlPoint:pointO]; return path; }
// 獲取最小的圓 if (bigCir.frame.size.width < smallCir.frame.size.width) {
}</pre>UIView *view = bigCir; bigCir = smallCir; smallCir = view;
以上這段代碼,由于我作圖是小圓的圓心為x1 y1,所以如果把大圓小圓的位置調過來會導致曲線面積的中間鼓起來,所以為了防止別人傳錯值,也不用他們那么糾結了,我代碼里面自己找出最小的圓就好了,所以一定要加上這一句,然后奉上此時的pan代碼。- (void)pan:(UIPanGestureRecognizer *)pan{ // 移動 CGPoint transPoint = [pan translationInView:self]; CGPoint center = self.center; center.x += transPoint.x; center.y += transPoint.y; self.center = center; [pan setTranslation:CGPointZero inView:self]; // 設置小圓變化的值 CGFloat cirDistance = [self distanceWithPointA:self.center andPointB:self.smalCirView.center]; CGFloat smallCirRadius = _oriRadius - cirDistance/10.0; if(smallCirRadius<0) smallCirRadius = 0; _smalCirView.bounds = CGRectMake(0, 0, smallCirRadius * 2, smallCirRadius * 2); self.smalCirView.layer.cornerRadius = smallCirRadius; self.shapeLayer.path = [self getBezierPathWithSmallCir:self andBigCir:self.smalCirView].CGPath; }
好了接下來就是收尾部分了,只是我想做在超過一定距離后,按鈕就完全被拖走了 依依不舍的走了。#pragma mark - 自定義方法 #pragma mark - 手勢觸發方法 #define MaxDistance 90
- 好了,現在小圓終于可以隨著兩圓心距的變大而變小了,之后重頭戲來了,獲取貝塞爾曲線路徑,也就是繪制兩圓之間的曲線部分
- (void)pan:(UIPanGestureRecognizer )pan{
// 移動
CGPoint transPoint = [pan translationInView:self];
CGPoint center = self.center;
center.x += transPoint.x;
center.y += transPoint.y;
self.center = center;
[pan setTranslation:CGPointZero inView:self];
// 設置小圓變化的值
CGFloat cirDistance = [self distanceWithPointA:self.center andPointB:self.smalCirView.center];
CGFloat smallCirRadius = _oriRadius - cirDistance/10.0;
if(smallCirRadius<0) smallCirRadius = 0;
_smalCirView.bounds = CGRectMake(0, 0, smallCirRadius 2, smallCirRadius * 2);
self.smalCirView.layer.cornerRadius = smallCirRadius;
// 畫圖
if (cirDistance > MaxDistance) {
self.smalCirView.hidden = YES;
[self.shapeLayer removeFromSuperlayer];
// self.smalCirView = nil;
self.shapeLayer = nil;
}else if(self.smalCirView.hidden == NO && cirDistance > 0){
self.shapeLayer.path = [self getBezierPathWithSmallCir:self andBigCir:self.smalCirView].CGPath;
}</pre>
最后,我們判斷在限定范圍內放手就回彈,超過就爆炸消失#pragma mark - 自定義方法 #pragma mark - 手勢觸發方法 #define MaxDistance 90
- (void)pan:(UIPanGestureRecognizer )pan{
// 移動
CGPoint transPoint = [pan translationInView:self];
CGPoint center = self.center;
center.x += transPoint.x;
center.y += transPoint.y;
self.center = center;
[pan setTranslation:CGPointZero inView:self];
// 設置小圓變化的值
CGFloat cirDistance = [self distanceWithPointA:self.center andPointB:self.smalCirView.center];
CGFloat smallCirRadius = _oriRadius - cirDistance/10.0;
if(smallCirRadius<0) smallCirRadius = 0;
_smalCirView.bounds = CGRectMake(0, 0, smallCirRadius 2, smallCirRadius * 2);
self.smalCirView.layer.cornerRadius = smallCirRadius;
// 畫圖
if (cirDistance > MaxDistance) {
self.smalCirView.hidden = YES;
[self.shapeLayer removeFromSuperlayer];
// self.smalCirView = nil;
self.shapeLayer = nil;
}else if(self.smalCirView.hidden == NO && cirDistance > 0){
self.shapeLayer.path = [self getBezierPathWithSmallCir:self andBigCir:self.smalCirView].CGPath;
}
// 爆炸或還原
if(pan.state == UIGestureRecognizerStateBegan){
NSLog(@"%@",NSStringFromCGRect(self.frame));
}
if (pan.state == UIGestureRecognizerStateEnded) {
if (cirDistance > MaxDistance){
// 這是動畫的爆炸效果
// 移除控件UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds]; NSLog(@"%@",NSStringFromCGRect(self.frame)); NSMutableArray *imageArr = [NSMutableArray array]; for (int i = 1 ; i < 9; i++) { UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"%d",i]]; [imageArr addObject:image]; } imageView.animationImages = imageArr; imageView.animationDuration = 0.5; imageView.animationRepeatCount = 1; [imageView startAnimating]; [self addSubview:imageView]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
}else{ // 回彈[self removeFromSuperview]; });
} } }</pre>[self.shapeLayer removeFromSuperlayer]; self.shapeLayer = nil; [UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:0.2 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{ self.center = self.smalCirView.center; } completion:^(BOOL finished) { self.smalCirView.hidden = NO; }];
這時基本上已經可以宣告完成的了,但是在爆炸效果釋放的時候你會發現,爆炸效果不在我們當前的位置爆炸,而是在初始位置爆炸,這是因為蘋果官方自動給 StoryBoard添加了自動布局約束,這是只需要把約束渲染設為NO就好了,就是在ViewController里面加上一句代碼- (void)viewDidLoad { [super viewDidLoad]; self.view.translatesAutoresizingMaskIntoConstraints = NO; }
最后奉上整個類的代碼
#import "ETStickBtn.h" @interface ETStickBtn () @property (nonatomic, strong) UIView smalCirView; @property (nonatomic, assign) NSInteger oriRadius; @property (nonatomic, strong) CAShapeLayer shapeLayer; @end @implementation ETStickBtn #pragma mark - 懶加載數據
- (UIView )smalCirView{ if (!_smalCirView) { // 新建一個圓 UIView smalCirView = [[UIView alloc] init]; // smalCirView.frame = self.frame; smalCirView.backgroundColor = self.backgroundColor; [self.superview insertSubview:smalCirView belowSubview:self]; _smalCirView = smalCirView; } return _smalCirView; }
- (CAShapeLayer )shapeLayer{ if (!_shapeLayer) { CAShapeLayer shapeLayer = [CAShapeLayer layer]; shapeLayer.fillColor = self.backgroundColor.CGColor; [self.superview.layer insertSublayer:shapeLayer below:self.layer]; _shapeLayer = shapeLayer; } return _shapeLayer; } #pragma mark - 系統初始化
- (id)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { [self setUp]; } return self; }
- (void)awakeFromNib{ [self setUp]; } #pragma mark - 初始化視圖
- (void)setUp{ CGFloat w = self.bounds.size.width; self.layer.cornerRadius = w / 2; self.smalCirView.layer.cornerRadius = w/2; // 記錄半徑 _oriRadius = w/2; _smalCirView.frame = self.frame; UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; [self addGestureRecognizer:pan]; } #pragma mark - 自定義方法 #pragma mark - 手勢觸發方法 #define MaxDistance 90
- (void)pan:(UIPanGestureRecognizer )pan{
// 移動
CGPoint transPoint = [pan translationInView:self];
CGPoint center = self.center;
center.x += transPoint.x;
center.y += transPoint.y;
self.center = center;
[pan setTranslation:CGPointZero inView:self];
// 設置小圓變化的值
CGFloat cirDistance = [self distanceWithPointA:self.center andPointB:self.smalCirView.center];
CGFloat smallCirRadius = _oriRadius - cirDistance/10.0;
if(smallCirRadius<0) smallCirRadius = 0;
_smalCirView.bounds = CGRectMake(0, 0, smallCirRadius 2, smallCirRadius * 2);
self.smalCirView.layer.cornerRadius = smallCirRadius;
// 畫圖
if (cirDistance > MaxDistance) {
self.smalCirView.hidden = YES;
[self.shapeLayer removeFromSuperlayer];
// self.smalCirView = nil;
self.shapeLayer = nil;
}else if(self.smalCirView.hidden == NO && cirDistance > 0){
self.shapeLayer.path = [self getBezierPathWithSmallCir:self andBigCir:self.smalCirView].CGPath;
}
// 爆炸或還原
if(pan.state == UIGestureRecognizerStateBegan){
NSLog(@"%@",NSStringFromCGRect(self.frame));
}
if (pan.state == UIGestureRecognizerStateEnded) {
if (cirDistance > MaxDistance){
// 這是動畫的爆炸效果
// 移除控件UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds]; NSLog(@"%@",NSStringFromCGRect(self.frame)); NSMutableArray *imageArr = [NSMutableArray array]; for (int i = 1 ; i < 9; i++) { UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"%d",i]]; [imageArr addObject:image]; } imageView.animationImages = imageArr; imageView.animationDuration = 0.5; imageView.animationRepeatCount = 1; [imageView startAnimating]; [self addSubview:imageView]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
}else{ // 回彈[self removeFromSuperview]; });
} } } #pragma mark - 獲取圓心距離[self.shapeLayer removeFromSuperlayer]; self.shapeLayer = nil; [UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:0.2 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{ self.center = self.smalCirView.center; } completion:^(BOOL finished) { self.smalCirView.hidden = NO; }];
- (CGFloat)distanceWithPointA:(CGPoint)pointA andPointB:(CGPoint)pointB{ CGFloat offSetX = pointA.x - pointB.x; CGFloat offSetY = pointA.y - pointB.y; return sqrt(offSetXoffSetX + offSetYoffSetY); } #pragma mark - 獲取貝塞爾曲線
- (UIBezierPath )getBezierPathWithSmallCir:(UIView )smallCir andBigCir:(UIView )bigCir{
// 獲取最小的圓
if (bigCir.frame.size.width < smallCir.frame.size.width) {
UIView view = bigCir;
bigCir = smallCir;
smallCir = view;
}
// 獲取小圓的信息
CGFloat d = [self distanceWithPointA:smallCir.center andPointB:bigCir.center];
CGFloat x1 = smallCir.center.x;
CGFloat y1 = smallCir.center.y;
CGFloat r1 = smallCir.bounds.size.width/2;
// 獲取大圓的信息
CGFloat x2 = bigCir.center.x;
CGFloat y2 = bigCir.center.y;
CGFloat r2 = bigCir.bounds.size.width/2;
// 獲取三角函數
CGFloat sinA = (y2 - y1)/d;
CGFloat cosA = (x2 - x1)/d;
// 獲取矩形四個點
CGPoint pointA = CGPointMake(x1 - sinAr1, y1 + cosA r1);
CGPoint pointB = CGPointMake(x1 + sinAr1, y1 - cosA r1);
CGPoint pointC = CGPointMake(x2 + sinAr2, y2 - cosA r2);
CGPoint pointD = CGPointMake(x2 - sinAr2, y2 + cosA r2);
// 獲取控制點,以便畫出曲線
CGPoint pointO = CGPointMake(pointA.x + d / 2 cosA , pointA.y + d / 2 sinA);
CGPoint pointP = CGPointMake(pointB.x + d / 2 cosA , pointB.y + d / 2 sinA);
// 創建路徑
UIBezierPath *path =[UIBezierPath bezierPath];
[path moveToPoint:pointA];
[path addLineToPoint:pointB];
[path addQuadCurveToPoint:pointC controlPoint:pointP];
[path addLineToPoint:pointD];
[path addQuadCurveToPoint:pointA controlPoint:pointO];
return path;
}
@end</pre>
好了,一個簡單的粘性效果完成了
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!