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 ...
随机推荐
- unity 分数的显示
通常 在完成 条件之后再增加分数 所以 一开始先增加 public int 得到分数; public Text 分数ui; 在完成条件后增加 得到分数++; 分数ui.text = 得到分数.ToSt ...
- 初学Ionic
官网 https://ionicframework.com/ 如连接所示,可跳转到该前端框架的官网,在这里提供了两种方式可供大家学习: Code with the CLI Design with lo ...
- 毕业回馈—89C51之GPIO使用
STC89C51系列单片机共有如下几类GPIO口: (1)P0.0-P0.7: 对应DIP40封装的39-32号引脚:P0口既可以作为输入/输出GPIO口,也可以作为地址/数据复用总线使用. a)P0 ...
- [uwp]MVVM之MVVMLight,一个登录注销过程的简单模拟
之前学MVVM,从ViewModelBase,RelayCommand都是自己瞎写,许多地方处理的不好,接触到MVVMLigth后,就感觉省事多了. 那么久我现在学习MVVMLight的收获,简单完成 ...
- RxJava / RxAndroid
RxJava 是什么 RxJava 是函数响应式编程框架,它用观察者设计模式. 常用来做异步数据处理,在安卓中用来代替传统的 AsyncTask + Handler 的组合结构. RxJava 架构简 ...
- CentOS7安装weblogic集群思路梳理
以前经常用weblogic集群,但是却没有仔细想过要实现它.这不,前两天成功安装了weblogic集群,现在将其思路整理下.防止日后自己忘掉了. 一.安装weblogic10.3.6 1. 在官网下载 ...
- NOIp2018提高组游记
Day1 T1 积木大赛 NOIp2013D2T1.....看到的时候我还以为我记错了,以为原题是一次可以随便加,这题只能加一,出考场后查了下发现一模一样. #include <iostream ...
- 动态代理方案性能对比 (CGLIB,ASSIT,JDK)
动态代理工具比较成熟的产品有: JDK自带的,ASM,CGLIB(基于ASM包装),JAVAASSIST, 使用的版本分别为: JDK-1.6.0_18-b07, ASM-3.3, CGLIB-2.2 ...
- 3.3.1 Validations
摘要: 出处:黑洞中的奇点 的博客 http://www.cnblogs.com/kelvin19840813/ 您的支持是对博主最大的鼓励,感谢您的认真阅读.本文版权归作者所有,欢迎转载,但请保留该 ...
- iOS 时区获取问题
时区缩写 UTC, CST, GMT, CEST 以及转换 UTC是协调世界时(Universal Time Coordinated)英文缩写,是由国际无线电咨询委员会规定和推荐,并由国际时间局(BI ...