• IOS中qq消息拖动移除的粘性效果


    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 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];
    }
    复制代码
    • 在添加完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];
          }
      复制代码
    • 就这样,可以拖动啦,接下来你想做什么呢?我就想你不断拖动大圆的过程中,小圆的半径一直减少直到为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(offSetX*offSetX + offSetY*offSetY);
      }
      复制代码
    • 好了,现在小圆终于可以随着两圆心距的变大而变小了,之后重头戏来了,获取贝塞尔曲线路径,也就是绘制两圆之间的曲线部分
    • 这个也不难,只是用到初中的知识,先上个原型图(手绘)
    • 咳咳,由于用铅笔画的,有点模糊,那就奉上一个根据原型图用电脑绘制的图吧!
    • 至此,重要的点都已经计算出来的,也不难吧,把算式换成代码如下
      复制代码
      - (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) {
              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;
          }
      复制代码
    • 最后,我们判断在限定范围内放手就回弹,超过就爆炸消失
      复制代码
      #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(), ^{
      //                移除控件
                      [self removeFromSuperview];
                  });
              }else{
      //            回弹
                  [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(), ^{
      //                移除控件
                      [self removeFromSuperview];
                  });
              }else{
      //            回弹
                  [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;
                  }];
              }
          }
      }
      
      #pragma mark - 获取圆心距离
      - (CGFloat)distanceWithPointA:(CGPoint)pointA  andPointB:(CGPoint)pointB{
          CGFloat offSetX = pointA.x - pointB.x;
          CGFloat offSetY = pointA.y - pointB.y;
          return sqrt(offSetX*offSetX + offSetY*offSetY);
      }
      
      #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 - 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;
      }
      @end
  • 相关阅读:
    构建自己的PHP框架--定义ORM的接口
    装X神器--Hacker Typer
    inno setup读取注册表遇到的一个坑
    一个简单的inno setup模板
    Android Studio导入项目非常慢的解决办法
    Android Studio快捷键每日一练(6)
    Android Studio快捷键每日一练(5)
    Android Studio快捷键每日一练(4)
    Android中的FragmentManager的问题
    Android Studio快捷键每日一练(3)
  • 原文地址:https://www.cnblogs.com/transcendent/p/4800759.html
Copyright © 2020-2023  润新知