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

CurtCamaren 8年前發布 | 13K 次閱讀 iOS開發 Objective-C開發

來自: http://www.cnblogs.com/ziyi--caolu/p/5195615.html

qq的app中要是有新的聯系人發消息過來,相應聯系人的cell右邊會有一個紅色的圓圈表示消息條數。如果去觸碰那個圓圈,可以發現它竟然會跟著手指的移動而移動。

在一定范圍內,手指離開屏幕,會發現紅色圓圈會自動彈性的回到原來的位置。而如果超出一定距離,這個圓圈會做一個銷毀的動畫,從而從view上移除掉。

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

(由于mac制作gif圖片實在過于麻煩,所以效果只能是看看圖片。)

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

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

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

那么,不規則矩形怎么表示呢?可以利用Core Graphics在drawRect方法里面繪制不規則矩形的path,然后利用顏色fill就行。不規則矩形是隨著大圓的移動而不斷變化的,如果在drawRect方法里面繪制,那么在移動過程中不斷調用setNeedsDisplay方法進行重繪。這是種可行的方案,我所用的也大致是這種思路。

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

當然,難點并在在這里。而是不規則矩形的各個點的位置。要繪制這個不規則矩形,需要知道六個點的位置:

有了這些點的坐標,那么就可以用UIBezierPath來繪制相應的路徑,代碼如下:

- (UIBezierPath )pathWithBigCircleView:(UIView )bigCircleView smallCircleView:(UIView *)smallCircleView
{

CGPoint bigCenter = bigCircleView.center;
CGFloat x2 = bigCenter.x;
CGFloat y2 = bigCenter.y;
CGFloat r2 = bigCircleView.bounds.size.width / 2;

CGPoint smallCenter = smallCircleView.center;
CGFloat x1 = smallCenter.x;
CGFloat y1 = smallCenter.y;
CGFloat r1 = smallCircleView.bounds.size.width / 2;



// 獲取圓心距離
CGFloat d = [self distanceWithPointA:bigCenter pointB:smallCenter];

//Θ:(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];

// A
[path moveToPoint:pointA];

// AB
[path addLineToPoint:pointB];

// 繪制BC曲線
[path addQuadCurveToPoint:pointC controlPoint:pointP];


// CD
[path addLineToPoint:pointD];

// 繪制DA曲線
[path addQuadCurveToPoint:pointA controlPoint:pointO];

return path;

}</pre>

在實現過程中,我是自定義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 bigCenter = bigCircleView.center; CGFloat x2 = bigCenter.x; CGFloat y2 = bigCenter.y; CGFloat r2 = bigCircleView.bounds.size.width / 2;

    CGPoint smallCenter = smallCircleView.center; CGFloat x1 = smallCenter.x; CGFloat y1 = smallCenter.y; CGFloat r1 = smallCircleView.bounds.size.width / 2;

// 獲取圓心距離
CGFloat d = [self distanceWithPointA:bigCenter pointB:smallCenter];

//Θ:(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];

// A
[path moveToPoint:pointA];

// AB
[path addLineToPoint:pointB];

// 繪制BC曲線
[path addQuadCurveToPoint:pointC controlPoint:pointP];


// CD
[path addLineToPoint:pointD];

// 繪制DA曲線
[path addQuadCurveToPoint:pointA controlPoint:pointO];

return path;

}

@end</pre>

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

</div>

 本文由用戶 CurtCamaren 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!