仿手机iPhone QQ消息小红点动画1
前言
偶然发现iPhone QQ 显示消息条数的小红点可以响应动作事件,也有人问我这样的动画该怎么做,这里就把实现的思路简单的描述一下。在实现的过程中,同样发现该功能并没有看到的那么简单,要做一个完备的动画效果需要有一定的功底。因此,本篇会先侧重于实现思路,并不会实现一个一模一样的效果。
下面是iPhone QQ小红点的动作交互效果:
分析
首先我们分析拖拽时候的表现:
- 原先的小红点顺着手指移动,并与原来所处位置通过一个小尾巴(即移动的轨迹)连接
- 与原先位置在一定范围内时,小尾巴出现;超过一定范围时,小尾巴不出现
- 释放手指,小红点回到原先位置,并有弹簧动画效果
- 释放手指时离原先位置超过一定范围则不返回原点,而是有消失的泡沫动画
抛开细节,抓住要点,我归纳了几个要点:
- 小原点随手指移动
- 小尾巴分情况出现
- 手指释放后,小红点弹回原先位置
除此之外,红点上的文字,消失等情形的处理不是主要问题,我们先缓一缓。
实现
红点的移动
首先实现一个圆形的view,并且可以随手指移动。在一定移动范围内,手指离开后,view返回原处并带有弹簧效果;超出范围,view则停留在手指离开处。
我们通过drawRect:来画一个圆;设置一个CGPoint的对象来记录开始触摸时的位置;接着就是实现相关的touchEvent:。因为都是很基本的内容,直接上代码。
//头文件
@interface ZZSpringView : UIView
- (instancetype)initWithSquareLength:(CGFloat)length originPoint:(CGPoint)oPoint;
@end
//类文件
const CGFloat kOffset = 100.0;//拖拽的范围限制
@interface ZZSpringView ()
{
CGPoint pointOriginCenter;
}
@end @implementation ZZSpringView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
pointOriginCenter = self.center;
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (instancetype)initWithSquareLength:(CGFloat)length originPoint:(CGPoint)oPoint
{
if (self = [self initWithFrame:CGRectMake(oPoint.x, oPoint.y, length, length)])
{ }
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(context, true);
CGContextSetShouldAntialias(context, true); CGContextAddEllipseInRect(context, rect);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillPath(context);
} - (BOOL)_isDistanceEnough:(CGPoint)point
{ CGFloat distance = (point.x - pointOriginCenter.x)*(point.x - pointOriginCenter.x) + (point.y - pointOriginCenter.y)*(point.y - pointOriginCenter.y);
if (distance > kOffset * kOffset)
{
return YES;
}
return NO;
} //touch event
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
pointOriginCenter = [touch locationInView:self.superview]; [UIView animateWithDuration:. animations:^{
self.center = pointOriginCenter;
}];
} - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{ }
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{ UITouch *touch = [touches anyObject];
CGPoint pointMove = [touch locationInView:self.superview];
self.center = pointMove;
} - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint pointEnd = [touch locationInView:self.superview];
CGFloat distance = (pointEnd.x - pointOriginCenter.x)*(pointEnd.x - pointOriginCenter.x) + (pointEnd.y - pointOriginCenter.y)*(pointEnd.y - pointOriginCenter.y);
if ([self _isDistanceEnough:pointEnd])
{
//may be destory self animation
self.center = pointEnd;
pointOriginCenter = self.center;
}
else
{
[UIView animateWithDuration:1.0
delay:.
usingSpringWithDamping:0.1
initialSpringVelocity:.
options:
animations:^{
self.center = pointOriginCenter;
}
completion:^(BOOL finished) { }]; }
}
ZZSrpingView
在touchBegin事件中,因为点击小红点的位置与中心会有偏移,通过UIView animation做一个平滑的过度。而在touchEnd事件中,返回弹簧震荡的效果是使用UIView的Spring animation。
添加小尾巴(轨迹)
我画了一张简化的模拟拖拽过程的图:
虚线圆是view原来的位置,P0是其圆点;实线圆是移动的位置,P1是圆点。设置两圆的切线(红色),把封闭的部分都填充为同一个颜色的话,就能大致模拟出相似的效果。这里隐含了几个前提:
- 实际的轨迹是带有弧度的曲线,这里使用了切线来代替(红色的切线)
- 拖拽的时候,原先位置的圆形view会随拖拽距离变小,这里设置为一个固定大小的圆(半径为原来的一半)
鉴于此,我们需要求出的是两对切点的位置,使之成为一个封闭图形进行填充。同时,虚线位置的小圆也进行填充。这样,就基本完成类似的功能。
首先我们需要扩展当前context的范围,为了简便,通过添加尾巴的子view来实线,这样可以利用原先的红点view。现在我们已知P0,P1,以及各自的半径,然后求外围矩形的位置和长度。因为可以按任意方向拖拽,按当前的计算方式,需要分四种情况讨论。按笛卡尔坐标系的划分,图例是第一象限的情形。同理还有二三四的可能。为了迅速验证方案的可行性,这里只对第一象限进行讨论和模拟。
定义新view:
typedef enum : NSUInteger {
ZZLineDirection1=,//northease
ZZLineDirection2,//northwest
ZZLineDirection3,//southwest
ZZLineDirection4//southeast
} ZZLineDirection; @interface ZZSpringTailView : UIView
@property (nonatomic, assign) ZZLineDirection lineDirection;
@property (nonatomic, assign) CGFloat radius;//centerradius
@property (nonatomic, assign) CGFloat moveRadius;
@end
ZZSpringTailView
ZZLineDirection代表的是某象限,radius是P0的半径,moveRadius为P1半径。 我们在touchMove事件中添加一个view,在此之前,我们会在ZZSpringView中添加一个ZZSpringTailView实例,用于内部访问。touchMove的实现更新为:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{ UITouch *touch = [touches anyObject];
CGPoint pointMove = [touch locationInView:self.superview]; if ([self _isDistanceEnough:pointMove])
{
//beyond the offset, hide the view
if (tailView)
{
tailView.hidden = YES;
}
}
else
{
//redraw the view
self.center = pointMove;
if (!tailView)
{
tailView = [[ZZSpringTailView alloc] init];
[self addSubview:tailView];
}
CGFloat widthHalf = self.bounds.size.width/2.0; CGFloat minX = ;//= MIN(pointMove.x, pointOriginCenter.x);
CGFloat minY = ;//= MIN(pointMove.y, pointOriginCenter.y);
CGFloat radius = widthHalf; //the width: the distance betweent two points and the origin size's width/2
CGRect frameInSuper = CGRectMake(minX, minY, fabsf(pointMove.x - pointOriginCenter.x) + widthHalf + radius, fabsf(pointMove.y - pointOriginCenter.y) + widthHalf + radius); tailView.radius = radius/;
tailView.moveRadius = radius; if (pointMove.x >= pointOriginCenter.x && pointMove.y <= pointOriginCenter.y)
{
NSLog(@"direnction1");
tailView.lineDirection = ZZLineDirection1;
frameInSuper.origin.x = pointOriginCenter.x - radius;
frameInSuper.origin.y = pointMove.y - radius; }
else if (pointMove.x <= pointOriginCenter.x && pointMove.y <= pointOriginCenter.y)
{
NSLog(@"direnction2");
tailView.lineDirection = ZZLineDirection2;
frameInSuper.origin.x = pointMove.x ;
frameInSuper.origin.y = pointMove.y;
}
else if (pointMove.x <= pointOriginCenter.x && pointMove.y >= pointOriginCenter.y)
{
NSLog(@"direnction3");
tailView.lineDirection = ZZLineDirection3;
frameInSuper.origin.x = pointMove.x - radius;
frameInSuper.origin.y = pointOriginCenter.y;
}
else
{
NSLog(@"direnction4");
tailView.lineDirection = ZZLineDirection4;
frameInSuper.origin.x = pointOriginCenter.x - radius;
frameInSuper.origin.y = pointOriginCenter.y - radius;
}
tailView.frame = [self convertRect:frameInSuper fromView:self.superview];
[tailView setNeedsDisplay];
} }
touchMoveEvent:
这里的实现是把tailview添加到springview之上,通常情况下,clipToBouds默认是NO的,因此这种添加超出父view bound 的子view方案是可行的。需要注意的是,上述的两个point是在spring view的父view内的,因此,在最后确定tailView frame的时候需要转换到springView的坐标系。
接下来就是tailView的drawRect实现。这里主要需要做2件事情:
- 绘制P0为圆心的圆
- 绘制2对切点构成的封闭图形
drawRect的部分实现:
- (void)drawRect:(CGRect)rect
{ CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(context, true);
CGContextSetShouldAntialias(context, true); CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, ); CGPoint pointStart, pointEnd;//center CGPoint movePoint1, movePoint2;//移动圆的2个切点
CGPoint centerPoint1, centerPoint2;//原有圆的2个切点
CGFloat moveRadius = _moveRadius;//移动圆 弧的半径 CGFloat sinval = , csinval = ;
CGFloat distance = ;
switch (_lineDirection) {
case ZZLineDirection1:
{
pointStart = CGPointMake(rect.size.width - moveRadius, + moveRadius);
pointEnd = CGPointMake( + _radius, rect.size.height - _radius); distance = CGRectGetHeight(rect) * CGRectGetHeight(rect) + CGRectGetWidth(rect) * CGRectGetWidth(rect); sinval = CGRectGetHeight(rect) * CGRectGetHeight(rect)/distance;
csinval = CGRectGetWidth(rect) * CGRectGetWidth(rect)/distance; movePoint2 = CGPointMake(pointStart.x - moveRadius * sinval, pointStart.y - moveRadius*csinval);
movePoint1 = CGPointMake(pointStart.x + moveRadius*sinval, pointStart.y + moveRadius*csinval); centerPoint2 = CGPointMake(pointEnd.x + _radius*sinval, pointEnd.y + _radius*csinval);
centerPoint1 = CGPointMake(pointEnd.x - _radius * sinval, pointEnd.y - _radius*csinval);
break;
}
case ZZLineDirection2:
{
break;
}
case ZZLineDirection3:
{ break;
}
case ZZLineDirection4:
{
break;
} } CGContextMoveToPoint(context, movePoint1.x, movePoint1.y);
CGContextAddLineToPoint(context, movePoint2.x, movePoint2.y);
CGContextAddLineToPoint(context, centerPoint1.x, centerPoint1.y);
CGContextAddLineToPoint(context, centerPoint2.x, centerPoint2.y);
CGContextClosePath(context); CGContextFillPath(context);
CGContextStrokePath(context); CGContextAddArc(context, pointEnd.x, pointEnd.y, _radius, , *M_PI, );
CGContextFillPath(context); }
ZZSpringTailView
计算过程就不详细描述了,初中数学的知识就够了。接着运行下,看看效果。
从运行效果看,还是差强人意的。这显示了方案的可行性。
那么相应二三四象限的情况也能做类似的处理,这里就不贴代码了。
由于时间的关系,暂时研究到此,下一篇会把功能逐步完善。主要会包含添加文字的情形等内容,敬请期待。
如果有更好的实现方式,也请大家赐教!
-->
仿手机iPhone QQ消息小红点动画1的更多相关文章
- 仿手机QQ消息小红点动画2
前言 上一篇把动画的实现步骤大致理清,需要确认小尾巴的绘制区域,关键就是确定4个顶点的位置.大家可以根据需要,选择不同的计算方式. 今天,要实现: 文字的添加 尾巴拉长用弧形代替直线 下面是现在的效果 ...
- 高仿精仿手机版QQ空间应用源码
说明:本次QQ空间更新了以前非常基础的代码 更新内容一 更新了登陆界面二 增加了输入时密码时和登陆成功后播放音频的效果三 增加了导航条渐隐的效果(和真实QQ空间的导航条一样,首先透明,当tablev ...
- 类似QQ消息左滑删除的Demo
最近在网上学到一篇类似QQ消息左滑删除的demo,完善了下代码,感觉还不错,特此分享一波: CustomSwipeListView.java 是个继承自ListView的类,里面调用了自定义View ...
- vue-miniQQ——基于Vue2实现的仿手机QQ单页面应用(接入了聊天机器人,能够进行正常对话)
使用Vue2进行的仿手机QQ的webapp的制作,作品由个人独立开发,源码中进行了详细的注释. 由于自己也是初学Vue2,所以注释写的不够精简,请见谅. 项目地址 https://github.com ...
- wpf实现仿qq消息提示框
原文:wpf实现仿qq消息提示框 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/huangli321456/article/details/5052 ...
- js高仿QQ消息列表左滑功能
该组件,主要功能类似于QQ消息列表左滑出现删除.标为已读等按钮的功能:现在的版本用的是纯javaScript编写:后续会跟进 angularJs 开发的类似组件以及jquery的; 下面,就让我们来认 ...
- Android端IM应用中的@人功能实现:仿微博、QQ、微信,零入侵、高可扩展
本文由“猫爸iYao”原创分享,感谢作者. 1.引言 最近有个需求:评论@人(没错,就是IM聊天或者微博APP里的@人功能),就像下图这样: ▲ 微信群聊界面里的@人功能 ▲ QQ群聊界面里 ...
- HTML5仿手机微信聊天界面
HTML5仿手机微信聊天界面 这篇文章主要为大家详细介绍了HTML5仿手机微信聊天界面的关键代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 给大家带来的是HTML5仿手机微信聊天界面, ...
- 基于Vue2、WebSocket的仿腾讯QQ
概述 本项目基于Vue2进行高仿手机QQ的webapp,UI上使用的是museUI,使用springMVC搭建的后台.聊天方面,使用WebSocket实现浏览器与服务器全双工通信,允许服务器主动发送信 ...
随机推荐
- Linux(Ubuntu16.04)下添加新用户
某些情况下,Ubuntu 使用useradd 新用户名,在home 文件夹下面看不到新创建的用户文件夹,例如: 发现找不到,spark的文件夹,因此将采用下面方式重新建立首先删除spark用户 若想给 ...
- Android 二次打包(封装)AAR实用指南
前言 上次文章Android SDK开发与使用的那些事儿说到如何在aar里集成导入的aar,也就是二次封装aar的问题,一带而过,难免不过瘾.在封装这条路上也遇到了不少坑,现在将此方法详细记录下来. ...
- Android开发如何定制framework层服务
刚刚跨完年,新年第一篇文章,那么今天将对Android开发framework中间层的服务定制使用作个总结.首先我们先导入Android平台源码framework层的代码到开发工具eclipse中,代码 ...
- MUI框架-09-MUI 与后台数据交互
MUI框架-09-MUI 与后台数据交互 本篇介绍使用 art-template 和原生 MUI 的数据交互 mui.ajax 来实现 我们大家都知道,想要数据交互就要有数据,每次当我们发送请求,我们 ...
- AndroidStudio项目打包成jar
AndroidStudio项目打包成jar 前言:在eclipse中我们知道如何将一个项目导出为jar包,现在普遍AndroidStuido开发,这里一步一步详加介绍AS项目打包成jar,jar和ar ...
- Aspose.Pdf v8.4.1 发布
.NET v8.4.1: 修复的错误及漏洞: PDF到JPEG时,内容从最终JPEG文件中丢失. 书签缩放识别错误. Java v4.2: 新特性: 搜索分离超过两行的文本. 修复的异常: PDF到 ...
- springMVC入门-01
这一系列是在看完网上SpringMVC(基于spring3.0)入门视频之后的个人总结,仅供参考,其中会添加一些个人的见解. 1.搭建SpringMVC所需jar包: org.springframew ...
- strncpy()函数【转】
C/C++中的strncpy()函数功能为将第source串的前n个字符拷贝到destination串,原型为: char * strncpy ( char * destination, const ...
- December 20th 2016 Week 52nd Tuesday
With the wonder of your love, the sun above always shines. 拥有你美丽的爱情,太阳就永远明媚. To accept the love from ...
- 高可用web框架
nginx nginx简介 Nginx是一个自由.开源.高性能及轻量级的HTTP服务器及反转代理服务器.Nginx以其高性能.稳定.功能丰富.配置简单及占用系统资源少而著称. Nginx 超越 Apa ...