CADisplayLink+弹簧动画实现果冻效果
项目中在Tabbar中间的按钮要从底部弹出视图并有果冻效果,在CocoaChina中找了一篇博客用 UIBezierPath 实现果冻效果,github,自己就按着上面的demo修改了一下( 之前也是想着自己一行一行动手敲代码,小伙伴总是说我不要重复造轮子),也正好通过这个学习一下CADisplayLink。
CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器。我们在应用中创建一个新的 CADisplayLink 对象,把它添加到一个runloop中,并给它提供一个 target 和selector 在屏幕刷新的时候调用。
一但 CADisplayLink 以特定的模式注册到runloop之后,每当屏幕需要刷新的时候,runloop就会调用CADisplayLink绑定的target上的selector,这时target可以读到 CADisplayLink 的每次调用的时间戳,用来准备下一帧显示需要的数据。例如一个视频应用使用时间戳来计算下一帧要显示的视频数据。在UI做动画的过程中,需要通过时间戳来计算UI对象在动画的下一帧要更新的大小等等。
在添加进runloop的时候我们应该选用高一些的优先级,来保证动画的平滑。可以设想一下,我们在动画的过程中,runloop被添加进来了一个高优先级的任务,那么,下一次的调用就会被暂停转而先去执行高优先级的任务,然后在接着执行CADisplayLink的调用,从而造成动画过程的卡顿,使动画不流畅。
duration属性提供了每帧之间的时间,也就是屏幕每次刷新之间的的时间。我们可以使用这个时间来计算出下一帧要显示的UI的数值。但是 duration只是个大概的时间,如果CPU忙于其它计算,就没法保证以相同的频率执行屏幕的绘制操作,这样会跳过几次调用回调方法的机会。frameInterval属性是可读可写的NSInteger型值,标识间隔多少帧调用一次selector 方法,默认值是1,即每帧都调用一次。如果每帧都调用一次的话,对于iOS设备来说那刷新频率就是60HZ也就是每秒60次,如果将 frameInterval 设为2 那么就会两帧调用一次,也就是变成了每秒刷新30次。
我们通过pause属性开控制CADisplayLink的运行。当我们想结束一个CADisplayLink的时候,应该调用-(void)invalidate
从runloop中删除并删除之前绑定的 target跟selector
另外CADisplayLink 不能被继承。
CADisplayLink 与 NSTimer 有什么不同
iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。NSTimer的精确度就显得低了点,比如NSTimer的触发时间到的时候,runloop如果在阻塞状态,触发时间就会推迟到下一个runloop周期。并且 NSTimer新增了tolerance属性,让用户可以设置可以容忍的触发的时间的延迟范围。CADisplayLink使用场合相对专一,适合做UI的不停重绘,比如自定义动画引擎或者视频播放的渲染。NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。在UI相关的动画或者显示内容使用 CADisplayLink比起用NSTimer的好处就是我们不需要在格外关心屏幕的刷新频率了,因为它本身就是跟屏幕刷新同步的。
废话写了这么多 ,下面是代码
1.定义弹出View
#import <UIKit/UIKit.h> @interface RYCuteView : UIView - (void)handlePanAction; @end
#import "RYCuteView.h"
#define SYS_DEVICE_WIDTH ([[UIScreen mainScreen] bounds].size.width) // 屏幕宽度
#define SYS_DEVICE_HEIGHT ([[UIScreen mainScreen] bounds].size.height) // 屏幕长度
@interface RYCuteView ()
@property (nonatomic, assign) CGFloat mHeight;
@property (nonatomic, assign) CGFloat curveX; // r5点x坐标
@property (nonatomic, assign) CGFloat curveY; // r5点y坐标
@property (nonatomic, strong) UIView *curveView; // r5红点
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, assign) BOOL isAnimating;
@end
@implementation RYCuteView
static NSString *kX = @"curveX";
static NSString *kY = @"curveY";
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self addObserver:self forKeyPath:kX options:NSKeyValueObservingOptionNew context:nil];
[self addObserver:self forKeyPath:kY options:NSKeyValueObservingOptionNew context:nil];
[self configShapeLayer];
[self configCurveView];
[self configAction];
}
return self;
}
- (void)dealloc {
[self removeObserver:self forKeyPath:kX];
[self removeObserver:self forKeyPath:kY];
}
- (void)drawRect:(CGRect)rect
{
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([keyPath isEqualToString:kX] || [keyPath isEqualToString:kY]) {
[self updateShapeLayerPath];
}
}
#pragma mark -
#pragma mark - Configuration
- (void)configAction
{
_isAnimating = NO; // 是否处于动效状态
// CADisplayLink默认每秒运行60次calculatePath是算出在运行期间_curveView的坐标,从而确定_shapeLayer的形状
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(calculatePath)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
_displayLink.paused = YES;
}
- (void)configShapeLayer
{
_shapeLayer = [CAShapeLayer layer];
_shapeLayer.fillColor = [UIColor colorWithRed:///255.0 alpha:1.0].CGColor;
[self.layer addSublayer:_shapeLayer];
}
- (void)configCurveView
{
// _curveView就是r5点
self.curveX = SYS_DEVICE_WIDTH/2.0; // r5点x坐标
self.curveY=-;
_curveView = [[UIView alloc] initWithFrame:CGRectMake(_curveX, _curveY, , )];
_curveView.backgroundColor = [UIColor clearColor];
[self addSubview:_curveView];
}
#pragma mark -
#pragma mark - Action
- (void)handlePanAction
{
if(!_isAnimating)
{
// _curveView.frame = CGRectMake(SYS_DEVICE_WIDTH/2.0, -20, 3, 3);
// 手势结束时,_shapeLayer返回原状并产生弹簧动效
_isAnimating = YES;
_displayLink.paused = NO; //开启displaylink,会执行方法calculatePath.
// 弹簧动效
[UIView animateWithDuration:
delay:
usingSpringWithDamping:0.3
initialSpringVelocity:0.01
options:UIViewAnimationOptionCurveEaseIn
animations:^{
// 曲线点(r5点)是一个view.所以在block中有弹簧效果.然后根据他的动效路径,在calculatePath中计算弹性图形的形状
_curveView.frame = CGRectMake(SYS_DEVICE_WIDTH/, , );
} completion:^(BOOL finished) {
if(finished)
{
_displayLink.paused = YES;
_isAnimating = NO;
}
}];
}
}
- (void)updateShapeLayerPath
{
// 更新_shapeLayer形状
UIBezierPath *tPath = [UIBezierPath bezierPath];
[tPath moveToPoint:CGPointMake(, )]; // r1点
[tPath addQuadCurveToPoint:CGPointMake(SYS_DEVICE_WIDTH, )
controlPoint:CGPointMake(_curveX, _curveY)]; // r3,r4,r5确定的一个弧线
[tPath addLineToPoint:CGPointMake(SYS_DEVICE_WIDTH, self.frame.size.height)];
[tPath addLineToPoint:CGPointMake(, self.frame.size.height)];
[tPath closePath];
_shapeLayer.path = tPath.CGPath;
}
- (void)calculatePath
{
// 由于手势结束时,r5执行了一个UIView的弹簧动画,把这个过程的坐标记录下来,并相应的画出_shapeLayer形状
CALayer *layer = _curveView.layer.presentationLayer;
self.curveX = layer.position.x;
self.curveY = layer.position.y;
}
@end
2.在ViewController中调用
#import "ViewController.h"
#import "RYCuteView.h"
@interface ViewController ()
@property (nonatomic,strong) RYCuteView *cuteView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *btn=[UIButton buttonWithType:UIButtonTypeSystem];
btn.frame=CGRectMake(, , , );
[btn setTitle:@"弹出" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
UIButton *btn1=[UIButton buttonWithType:UIButtonTypeSystem];
btn1.frame=CGRectMake(, , , );
[btn1 setTitle:@"落下" forState:UIControlStateNormal];
[btn1 addTarget:self action:@selector(btn1Click:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn1];
}
-(void)btn1Click:(id)sender
{
[UIView animateWithDuration:0.5 animations:^{
_cuteView.frame=CGRectMake(, self.view.frame.size.height, self.view.frame.size.width, );
} completion:^(BOOL finished) {
[_cuteView removeFromSuperview];
_cuteView=nil;
}];
}
-(void)btnClick:(id)sender
{
if (_cuteView!=nil) {
[_cuteView removeFromSuperview];
_cuteView=nil;
}
_cuteView = [[RYCuteView alloc] initWithFrame:CGRectMake(, self.view.frame.size.height, self.view.frame.size.width, )];
_cuteView.backgroundColor = [UIColor clearColor];
[self.view addSubview:_cuteView];
[UIView animateWithDuration:0.2 animations:^{
_cuteView.frame=CGRectMake(, self.view.frame.size.height-, self.view.frame.size.width, );
} completion:^(BOOL finished) {
[_cuteView handlePanAction];
}];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
3.效果图


链接: http://pan.baidu.com/s/1eRybw8i 密码: 65ft
https://github.com/ywcui/JellyAnimation
CADisplayLink+弹簧动画实现果冻效果的更多相关文章
- iOS开发——图形编程OC篇&粘性动画以及果冻效果
粘性动画以及果冻效果 在最近做个一个自定义PageControl——KYAnimatedPageControl中,我实现了CALayer的形变动画以及CALayer的弹性动画,效果先过目: 先做个提纲 ...
- 谈谈iOS中粘性动画以及果冻效果的实现
在最近做个一个自定义PageControl——KYAnimatedPageControl中,我实现了CALayer的形变动画以及CALayer的弹性动画,效果先过目: https://github.c ...
- 转:谈谈iOS中粘性动画以及果冻效果的实现
在最近做个一个自定义PageControl——KYAnimatedPageControl中,我实现了CALayer的形变动画以及CALayer的弹性动画,效果先过目: 先做个提纲: 第一个分享的主题是 ...
- iOS - 用 UIBezierPath 实现果冻效果
最近在网上看到一个很酷的下拉刷新效果(http://iostuts.io/2015/10/17/elastic-bounce-using-uibezierpath-and-pan-gesture/). ...
- pop弹簧动画实现
POP是一个在iOS与OS X上通用的极具扩展性的动画引擎.它在基本的静态动画的基础上增加的弹簧动画与衰减动画,使之能创造出更真实更具物理性的交互动画.POP的API可以快速的与现有的ObjC代码集成 ...
- [范例] Firemonkey 弹簧动画
弹簧动画效果: 不用写任何代码,只需设定下面动画属性: 参考动画曲线: http://monkeystyler.com/guide/Interpolation-and-AnimationType-Il ...
- 用POP动画引擎实现弹簧动画(POPSpringAnimation)
效果图: #import "ViewController.h" #import <POP.h> @interface ViewController () @proper ...
- Unity3D中暂停时的动画及粒子效果实现
暂停是游戏中经常出现的功能,而Unity3D中对于暂停的处理并不是很理想.一般的做法是将Time.timeScale设置为0.Unity的文档中对于这种情况有以下描述: The scale at wh ...
- iOS9 CASpringAnimation 弹簧动画详解
http://blog.csdn.net/zhao18933/article/details/47110469 1. CASpringAnimation iOS9才引入的动画类,它继承于CABaseA ...
随机推荐
- Kafka与.net core(二)zookeeper
1.zookeeper简单介绍 1.1作用 zookeeper的作用是存储kafka的服务器信息,topic信息,和cunsumer信息.如下图: 而zookeeper是个什么东西呢?简单来说就是一个 ...
- 总结目前为止学到的关键字(break,continue,private,static,this,super,final,abstract)
1.控制跳转语句:break(结束) 使用的场景: a.循环当中 b.switch break关键字需要注意的问题: 1.break关键字只能用于循环和switch语句当中,其本质就是结束整段语句的意 ...
- JAVA实现长连接(含心跳检测)Demo
实现原理: 长连接的维持,是要客户端程序,定时向服务端程序,发送一个维持连接包的. 如果,长时间未发送维持连接包,服务端程序将断开连接. 客户端: Client通过持有Sock ...
- Ubuntu add-apt-repository: command not found
在Ubuntu下,时不时会有这个错误的. add-apt-repository: command not found 这个是缺少程序,安装一下就可以了.只是不知道安装的名字. 按以下命令走一趟就可以的 ...
- Java50道经典习题-程序9 求完数
题目:一个数如果恰好等于它的因子之和,这个数就称为"完数".例如6=1+2+3.编程找出1000以内的所有完数. public class Prog9 { public stati ...
- 【bug】—— ios scroll 滚动穿透
BUG描述 在 ios 微信浏览器或原生浏览器中,主内容容器.content在文档流内,并且overflow-y: scroll.在其之上有一个 fixed 定位的弹出层.popUp,滚动.popUp ...
- solr安装教程
Solr Solr is the popular, blazing-fast, open source enterprise search platform built on Apache Lucen ...
- 初学Oracle
初学Oracle,遇到了很多的问题,下载的是Oracle11g,没有找到合适的管理工具,所以用sql plus 创建表,以下是本人总结的一些sql plus的命令行的命令,希望对大家有用 与sql p ...
- Laravel5.5 使用第三方Vendor添加注册验证码
Laravel5系列的验证码添加通用,使用第三方验证码即可完美实现.这里记录下具体步骤吧,以备不时之需. 第一步:使用composer 安装 验证码库 composer require mews/ca ...
- FlowPortal-BPM——文件目录功能
安装目录文件夹:Attachments 附件bin Bin目录:dll文件的引用DataSourceProviders 固定的数据库连接文件ExtServer 数据源服务FormService 表单服 ...