QQ(iOS)客户端的粘性动画效果

时间 2016-02-17 16:50:00  博客园精华区
原文  http://www.cnblogs.com/ziyi--caolu/p/5195615.html
主题 iOS开发

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; }

在实现过程中,我是自定义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

转载于:https://www.cnblogs.com/diweinan/p/6230533.html

iOS开发 QQ粘性动画效果相关推荐

  1. iOS开发之各种动画各种页面切面效果

    转发:http://www.cocoachina.com/ios/20141226/10775.html 今天所介绍的主题是关于动画的,在之前的博客中也有用到动画的地方,今天就好好的总结一下iOS开发 ...

  2. iOS实现“下雨下雪”动画效果和“烟花”动画效果

    "下雨"的动画效果 一.效果展示 二.实现流程 设置背景 func setupUI() {self.imageView = UIImageView.init()self.image ...

  3. 语音聊天源码开发之常用动画效果的实现

    效果展示 下面是语音聊天源码开发中比较入门的豪华礼物动画--烟花. 一个复杂的礼物动画,首先是美术给出gif实现草图和素材,技术进行动画剖析和图片压缩,在语音聊天源码中加载图片和实现动画,其中要注意内 ...

  4. android手势控制动画,轻松实现Android,iOS的一个手势动画效果

    先来看效果 这是iOS下的效果,android下完全一致.通过do_GestureView组件和do_Animation组件,deviceone能很容易实现复杂的跨平台纯原生动画效果,这个示例就是通过 ...

  5. ios 自定义加载动画效果

    在开发过程中,可能会遇到各种不同的场景需要等待加载成功后才能显示数据.以下是自定义的一个动画加载view效果.      在UIViewController的中加载等到效果,如下 - (void)vi ...

  6. iOS 图标上下浮动的动画效果

    要实现某个图片上下浮动的动画效果很只需要三步: 1.把图标下拉 2.把图标上移 3.重复步骤1 2 - (void)ImageSpring {[UIView animateWithDuration:0 ...

  7. ios scrollView中增加动画效果,自动滚动UIScrollView,利用了NSTimer

    转载自:http://blog.csdn.net/z343929897/article/details/7974753 按照赵总要求,在首页上边需要加广告条,本来以为挺复杂的,原来挺简单 在类的申明文 ...

  8. iOS 开发——登录页面动画、转场动画

    DEMO下载地址:https://github.com/YYProgrammer/YYLoginTranslationDemo 技术点分析 如何生成一个动画让控件执行? 现流行的方式主要有三种: 1. ...

  9. iOS开发之实现毛玻璃效果及图片模糊效果

    毛玻璃效果的实现 App设计时往往会用到一些模糊效果或者毛玻璃效果,iOS目前已提供了一些模糊API可以让我们方便使用.苹果在iOS7.0之后,很多系统界面都使用了毛玻璃效果,增加了界面的美观性,比如 ...

最新文章

  1. fwrite ,fprintf的作用与区别
  2. 基于SLF4J MDC机制实现日志的链路追踪
  3. lgg8配置_LGG8XThinQ参数配置-LG G8X ThinQ详细性能评测
  4. oc和java_oc与java c++语法区别
  5. python_线程读写操作一
  6. 使用组策略禁止域用户运行特定软件名称的程序
  7. 【字符串】旋转字符串
  8. python 文件批量转换格式_python实现快速文件格式批量转换的方法
  9. openproj ubuntu安装及其输入中文变方块乱码解决
  10. Android 65536错误:Cannot fit requested classes in a single dex file
  11. 输入某辆小轿车三次的 耗油量(升)和行驶里程(公里),计算平均油耗(升/百公里)。
  12. Web标准概念--摘抄《CSS布局实录》
  13. Thinkphp6调用企业微信官方weworkapi配置接收消息服务器方法
  14. iOS—APP打包上线流程
  15. mysql 家谱树查询_族谱树算法
  16. 【论文精读】KD-MVS
  17. 软件质量管理-1-课程介绍
  18. 爱奇艺 2021秋招在线笔试
  19. ibatis源码学习(一)整体设计和核心流程
  20. 使用 Amazon SageMaker JumpStart 更轻松地在组织内共享 ML 模型和笔记本

热门文章

  1. Struts2 Result详解
  2. Java相对路径读取文件
  3. ACM 模板--邻接表 无向图 搜索算法
  4. golang避免XSS攻击
  5. 后台开发经典书籍--深入理解计算机系统
  6. 逻辑运算符''取某值
  7. Nginx-06:Nginx配置实例之负载均衡
  8. spring19:AspectJ的初步介绍
  9. python二十:内置函数
  10. 升级Win10后windows.old删除