QQ(iOS)客戶端的粘性動畫效果

    qq的app中要是有新的聯繫人發消息過來,相應聯繫人的cell右邊會有一個紅色的圓圈表示消息條數。若是去觸碰那個圓圈,能夠發現它居然會跟着手指的移動而移動。git


    在必定範圍內,手指離開屏幕,會發現紅色圓圈會自動彈性的回到原來的位置。而若是超出必定距離,這個圓圈會作一個銷燬的動畫,從而從view上移除掉。github

    產品要求公司的App也要有效果,並花了些時間去學習它的實現過程,發現其實原理仍是比較簡單的。app

  (因爲mac製做gif圖片實在過於麻煩,因此效果只能是看看圖片。)學習

 

    Demo的github地址:https://github.com/wzpziyi1/QQ-Goo動畫

   這是實現過程當中的一些效果圖片:

atom

     通過分析,能夠發現,是兩個圓和一個不規則矩形位置、大小的變化。一開始,小的圓圈和大的圓圈的center是相同的,當移動大圓的時候,小圓的半徑隨着大圓離小圓的距離變遠而變小,當大圓距離小圓必定距離時,將小圓隱藏掉,中間的不規則矩形remove掉。spa

    那麼,不規則矩形怎麼表示呢?能夠利用Core Graphics在drawRect方法裏面繪製不規則矩形的path,而後利用顏色fill就行。不規則矩形是隨着大圓的移動而不斷變化的,若是在drawRect方法裏面繪製,那麼在移動過程當中不斷調用setNeedsDisplay方法進行重繪。這是種可行的方案,我所用的也大體是這種思路。3d

    不過,我沒有在drawRect方法裏面繪製,而是利用了CAShapeLayer,將不規則矩形的path繪製在shapeLayer裏面,這樣在移動大圓的過程當中不斷更新CAShapeLayer的path便可。

    固然,難點並在在這裏。而是不規則矩形的各個點的位置。要繪製這個不規則矩形,須要知道六個點的位置:
orm

     有了這些點的座標,那麼就能夠用UIBezierPath來繪製相應的路徑,代碼以下:blog

- (UIBezierPath *)pathWithBigCircleView:(UIView *)bigCircleView smallCircleView:(UIView *)smallCircleView
{
    CGPoint smallCircleCenter = smallCircleView.center;
    CGFloat x1 = smallCircleCenter.x;
    CGFloat y1 = smallCircleCenter.y;
    CGFloat r1 = smallCircleView.bounds.size.width / 2;
    
    CGPoint BigCircleViewCenter = bigCircleView.center;
    CGFloat x2 = BigCircleViewCenter.x;
    CGFloat y2 = BigCircleViewCenter.y;
    CGFloat r2 = bigCircleView.bounds.size.width / 2;
    
    CGFloat d = [self distanceWithPointA:BigCircleViewCenter pointB:smallCircleCenter];
    
    //Θ:(xita)
    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(pointA.x + d / 2 * sinθ , pointA.y + d / 2 * cosθ);
    CGPoint pointP =  CGPointMake(pointB.x + d / 2 * sinθ , pointB.y + d / 2 * cosθ);
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    // D
    [path moveToPoint:pointD];
    
    // DA
    [path addQuadCurveToPoint:pointA controlPoint:pointO];
    
    // AB
    [path addLineToPoint:pointB];
    
    // BC
    [path addQuadCurveToPoint:pointC controlPoint:pointP];
    
    // CD
    [path addLineToPoint:pointD];
    
    return path;
}

 

     在實現過程當中,我是自定義UIButton的,須要注意的是,在監聽button的拖動時,最好是給它添加UIPanGestureRecognizer手勢,而不要在touchesBegin方法裏面去判斷它的移動位置,由於Touches系列方法會屏蔽button的點擊。

    自定義的這個button默認就是大圓,包含一個小圓(UIView)屬性,可是這個小圓並非添加在自定義的這個button(也就是大圓)裏面,而是在button的superView上。由於小圓並不須要隨着大圓位置的改變而改變位置,相應的,shapeLayer也是添加在button(大圓)的父控件上。

    給大圓添加了pan手勢,在pan:方法裏面隨之改變小圓的大小和繪製shapeLayer的path。

    當pan手勢狀態爲End的時候,須要判斷大圓與小圓的距離有沒有超出最大距離,若是超過,那麼添加一個gif圖片,播放銷燬大圓的過程。若是沒有被銷燬,那麼大圓須要復位,相應代碼:

#import "ZYGooView.h"

#define kMaxDistance 100

@interface ZYGooView ()
@property (nonatomic, weak) UIView *smallCircleView;

@property (nonatomic, assign) CGFloat smallCircleR;

@property (nonatomic, weak) CAShapeLayer *shapeLayer;
@end

@implementation ZYGooView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        [self commitInit];
        
    }
    return self;
}

- (void)awakeFromNib
{
    [self commitInit];
}

- (void)commitInit
{
    self.layer.cornerRadius = self.frame.size.width * 0.5;
    self.layer.masksToBounds = YES;
    
    self.smallCircleR = self.frame.size.width * 0.5;
    self.smallCircleView.bounds = self.bounds;
    self.smallCircleView.center = self.center;
    self.smallCircleView.layer.cornerRadius = self.smallCircleView.frame.size.width * 0.5;
    
    [self addGesture];
}

#pragma mark ----懶加載方法

- (UIView *)smallCircleView
{
    if (_smallCircleView == nil) {
        UIView *view = [[UIView alloc] init];
        
        view.backgroundColor = self.backgroundColor;
        
        [self.superview addSubview:view];
        
        [self.superview insertSubview:view atIndex:0];
        
        _smallCircleView = view;
        
    }
    return _smallCircleView;
}

- (CAShapeLayer *)shapeLayer
{
    if (_shapeLayer == nil) {
        CAShapeLayer *shapeLayer = [CAShapeLayer layer];
        shapeLayer.path = [self pathWithBigCircleView:self smallCircleView:self.smallCircleView].CGPath;
        shapeLayer.fillColor = self.backgroundColor.CGColor;
        
        [self.superview.layer addSublayer:shapeLayer];
        
        [self.superview.layer insertSublayer:shapeLayer atIndex:0];
        
        _shapeLayer = shapeLayer;
    }
    return _shapeLayer;
}

#pragma mark ----其餘方法

- (void)addGesture
{
    UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self addGestureRecognizer:recognizer];
}



- (void)pan:(UIPanGestureRecognizer *)recognizer
{
    CGPoint point = [recognizer translationInView:self.superview];
    
    CGPoint center = self.center;
    center.x += point.x;
    center.y += point.y;
    self.center = center;
    //復位
    [recognizer setTranslation:CGPointZero inView:self];
    
    CGFloat distance = [self distanceWithPointA:self.smallCircleView.center pointB:self.center];
    
    if (distance == 0) return;
    
    CGFloat newR = self.smallCircleR - distance / 15.0;
    NSLog(@"%f", newR);
    self.smallCircleView.bounds = CGRectMake(0, 0, newR * 2, newR * 2);
    self.smallCircleView.layer.cornerRadius = newR;
    
    if (distance > kMaxDistance || newR <= 0) {
        self.smallCircleView.hidden = YES;
        [self.shapeLayer removeFromSuperlayer];
        self.shapeLayer = nil;
    }
    
    if (distance <= kMaxDistance && self.smallCircleView.hidden == NO) {
        self.shapeLayer.path = [self pathWithBigCircleView:self smallCircleView:self.smallCircleView].CGPath;
    }
    
    if (recognizer.state == UIGestureRecognizerStateEnded) {
        if (distance <= kMaxDistance) {
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self.shapeLayer removeFromSuperlayer];
                self.shapeLayer = nil;
            });
            
            [UIView animateWithDuration:0.4 delay:0 usingSpringWithDamping:0.6 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
                self.center = self.smallCircleView.center;
                
            } completion:^(BOOL finished) {
                self.smallCircleView.hidden = NO;
            }];
        }
        else {
            UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
            [self addSubview:imageView];
            
            NSMutableArray *images = [NSMutableArray array];
            
            for (int i = 1; i <= 8; i++) {
                NSString *imageName = [NSString stringWithFormat:@"%d", i];
                UIImage *image = [UIImage imageNamed:imageName];
                [images addObject:image];
            }
            
            imageView.animationImages = images;
            imageView.animationDuration = 0.6;
            imageView.animationRepeatCount = 1;
            [imageView startAnimating];
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self removeFromSuperview];
            });
        }
    }
}

- (CGFloat)distanceWithPointA:(CGPoint)pointA pointB:(CGPoint)pointB
{
    CGFloat dx = pointB.x - pointA.x;
    CGFloat dy = pointB.y - pointA.y;
    
    return sqrt(dx * dx + dy * dy);
}

- (UIBezierPath *)pathWithBigCircleView:(UIView *)bigCircleView smallCircleView:(UIView *)smallCircleView
{
    CGPoint smallCircleCenter = smallCircleView.center;
    CGFloat x1 = smallCircleCenter.x;
    CGFloat y1 = smallCircleCenter.y;
    CGFloat r1 = smallCircleView.bounds.size.width / 2;
    
    CGPoint BigCircleViewCenter = bigCircleView.center;
    CGFloat x2 = BigCircleViewCenter.x;
    CGFloat y2 = BigCircleViewCenter.y;
    CGFloat r2 = bigCircleView.bounds.size.width / 2;
    
    CGFloat d = [self distanceWithPointA:BigCircleViewCenter pointB:smallCircleCenter];
    
    //Θ:(xita)
    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(pointA.x + d / 2 * sinθ , pointA.y + d / 2 * cosθ);
    CGPoint pointP =  CGPointMake(pointB.x + d / 2 * sinθ , pointB.y + d / 2 * cosθ);
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    // D
    [path moveToPoint:pointD];
    
    // DA
    [path addQuadCurveToPoint:pointA controlPoint:pointO];
    
    // AB
    [path addLineToPoint:pointB];
    
    // BC
    [path addQuadCurveToPoint:pointC controlPoint:pointP];
    
    // CD
    [path addLineToPoint:pointD];
    
    return path;
}

@end

 

 

Demo的github地址:https://github.com/wzpziyi1/QQ-Goo

相關文章
相關標籤/搜索