iOS开发 QQ粘性动画效果
QQ(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
iOS开发 QQ粘性动画效果的更多相关文章
- iOS开发之吸附动画效果
步骤:1.使用singleviewapplication创建新的项目 2.在.h文件中创建两张图片的实例对象,并与相关的图片进行相连:创建一个UIDynamicAnimator实例对象 3.在.m文件 ...
- ios开发之--简单动画效果的添加
记录一个简单的动画效果,自己写的,很简单,仅做记录. 附一个demo的下载地址: https://github.com/hgl753951/hglTest.git 代码如下: 1,准备 BOOL _i ...
- iOS 开发之推力动画效果
步骤: 1.使用single view application 创建新的项目 2.在.h文件中需要遵守两个协议<UICollisionBehaviorDelegate,UIGestureReco ...
- iOS开发QQ空间半透明效果的实现
//1.首先我们可以确定的是cell的半透明, /* white The grayscale value of the color object, specified as a value from ...
- iOS 开发之重力动画效果
步骤:1.使用single view application创建新的项目 2.在viewcontroller.h文件中创建一个图片实例并与相关图片相连,然后创建一个UIDynamicAnimator ...
- IOS开发系列 --- 核心动画
原始地址:http://www.cnblogs.com/kenshincui/p/3972100.html 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥i ...
- iOS CAReplicatorLayer 实现脉冲动画效果
iOS CAReplicatorLayer 实现脉冲动画效果 效果图 脉冲数量.速度.半径.透明度.渐变颜色.方向等都可以设置.可以用于地图标注(Annotation).按钮长按动画效果(例如录音按钮 ...
- iOS开发之各种动画各种页面切面效果
因工作原因,有段时间没发表博客了,今天就发表篇博客给大家带来一些干货,切勿错过哦.今天所介绍的主题是关于动画的,在之前的博客中也有用到动画的地方,今天就好好的总结一下iOS开发中常用的动画.说道动画其 ...
- 【转】iOS开发之各种动画各种页面切面效果
原文: http://www.cnblogs.com/ludashi/p/4160208.html?utm_source=tuicool 因工作原因,有段时间没发表博客了,今天就发表篇博客给大家带来一 ...
随机推荐
- 纳尼,java可以在接口中实现非抽象方法了?
纳尼,接口中可以定义实例方法了?! 纳尼,接口中还可以定义静态方法了?! 没错,在Java8中新增了很多新的特性,其中就包括可以在接口中添加方法和变量. 首先我们来看下代码 public interf ...
- 《zw版·Halcon-delphi系列原创教程》 Halcon分类函数017·point点函数
<zw版·Halcon-delphi系列原创教程> Halcon分类函数017·point点函数 为方便阅读,在不影响说明的前提下,笔者对函数进行了简化: :: 用符号“**”,替换:“p ...
- javascript的类型、值和变量
js的类型有多种分类,原始类型(数值,字符串,布尔值,null,undefined)和对象类型(object,String,Number,RgbExp等),或者是拥有方法的类型(object,Stri ...
- tornado_mongodb 连接和使用
tornado,mongodb 连接和使用,开始使用tornado3.2,mongodb2.6,pymongo-3.03遇到不少的麻烦.因为新版tornado与老版本的代码有很多变化,mongodb根 ...
- clock()、time()、clock_gettime()和gettimeofday()函数的用法和区别【转】
转自:http://www.cnblogs.com/krythur/archive/2013/02/25/2932647.html 转自http://blog.sina.com.cn/s/blog_7 ...
- spring中用到哪些设计模式
1.工厂模式,这个很明显,在各种BeanFactory以及ApplicationContext创建中都用到了: 2.模版模式,这个也很明显,在各种BeanFactory以及ApplicationCon ...
- jdbc连接mysql
package june25jdbcTest; import java.sql.Connection;import java.sql.DriverManager;import java.sql.Res ...
- Uva 1629 切蛋糕
题目链接:https://vjudge.net/contest/146179#problem/B 题意:一个矩形蛋糕上有好多个樱桃,现在要做的就是切割最少的距离,切出矩形形状的小蛋糕,让每个蛋糕上都有 ...
- Apache开启状态查看页面(原创贴-转载请注明出处)
=================写在前面的话================== 场景描述:有时候我们需要查看apache的运行状态,只需要开启apache的status功能就可以实现,但是stat ...
- Docker 学习笔记(CentOS 7.1)
基本概念 Docker 包括三个基本概念 镜像(Image) 容器(Container) 仓库(Repository)理解了这三个概念,就理解了 Docker 的整个生命周期. Docker 镜像 D ...