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中删除并删除之前绑定的 targetselector
另外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:57/255.0 green:67/255.0 blue:89/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=-80;
    _curveView = [[UIView alloc] initWithFrame:CGRectMake(_curveX, _curveY, 3, 3)];
    _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:1
                                  delay:0
                 usingSpringWithDamping:0.3
                  initialSpringVelocity:0.01
                                options:UIViewAnimationOptionCurveEaseIn
                             animations:^{

                                 // 曲线点(r5点)是一个view.所以在block中有弹簧效果.然后根据他的动效路径,在calculatePath中计算弹性图形的形状
                                 _curveView.frame = CGRectMake(SYS_DEVICE_WIDTH/2.0, 0, 3, 3);

                             } completion:^(BOOL finished) {

                                 if(finished)
                                 {
                                     _displayLink.paused = YES;
                                     _isAnimating = NO;
                                 }

                             }];

    }
}

- (void)updateShapeLayerPath
{
    // 更新_shapeLayer形状
    UIBezierPath *tPath = [UIBezierPath bezierPath];
    [tPath moveToPoint:CGPointMake(0, 0)];                              // r1点
    [tPath addQuadCurveToPoint:CGPointMake(SYS_DEVICE_WIDTH, 0)
                  controlPoint:CGPointMake(_curveX, _curveY)]; // r3,r4,r5确定的一个弧线
    [tPath addLineToPoint:CGPointMake(SYS_DEVICE_WIDTH, self.frame.size.height)];
    [tPath addLineToPoint:CGPointMake(0, 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(10, 100, 100, 80);
    [btn setTitle:@"弹出" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];

    UIButton *btn1=[UIButton buttonWithType:UIButtonTypeSystem];
    btn1.frame=CGRectMake(210, 100, 100, 80);
    [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(0, self.view.frame.size.height,  self.view.frame.size.width, 300);
    } completion:^(BOOL finished) {
        [_cuteView removeFromSuperview];
        _cuteView=nil;
    }];
}
-(void)btnClick:(id)sender
{
    if (_cuteView!=nil) {
        [_cuteView removeFromSuperview];
        _cuteView=nil;
    }
    _cuteView = [[RYCuteView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height, self.view.frame.size.width, 300)];
    _cuteView.backgroundColor = [UIColor clearColor];
    [self.view addSubview:_cuteView];
    [UIView animateWithDuration:0.2 animations:^{
        _cuteView.frame=CGRectMake(0, self.view.frame.size.height-300,  self.view.frame.size.width, 300);
    } 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

时间: 2024-10-06 23:51:59

CADisplayLink+弹簧动画实现果冻效果的相关文章

仿IOS效果 带弹簧动画的ListView_IOS

最近项目打算做一个界面,类似于dayone首页的界面效果,dayone 是一款付费应用,目前只有IOS端.作为一个资深懒惰的程序员,奉行的宗旨是绝对不重复造一个轮子.于是乎,去网上找一大堆开源项目,发现没有找到合适的,然后,只能硬着头皮自己来了.先看看效果: 效果图 其实写起来也比较简单,就是控制ListView的头部和底部的高度就可以了, 如果用RecycleView实现起来也是一样,只是RecycleView添加头和尾巴稍微麻烦一点,处理点击事件也不是很方便,所以就基于ListView去实现

果冻效果

果冻效果   说明 参考源码 https://github.com/Resory/RYCuteView   效果   源码 https://github.com/YouXianMing/Animations // // SpringEffectController.m // Animations // // Created by YouXianMing on 16/1/17. // Copyright 2016年 YouXianMing. All rights reserved. // #imp

Unity3D中暂停时的动画及粒子效果实现

暂停是游戏中经常出现的功能,而Unity3D中对于暂停的处理并不是很理想.一般的做法是将Time.timeScale设置为0.Unity的文档中对于这种情况有以下描述: The scale at which the time is passing. This can be used for slow motion effects-.When timeScale is set to zero the game is basically paused - timeScale表示游戏中时间流逝快慢的尺

JQuery插件Quicksand实现超炫的动画洗牌效果

  Quicksand这是一个非常不错的 jQuery 插件,用于实现动画洗牌效果,十分的实用,有需要的小伙伴可以参考下. Quicksand是一款基于jQuery的插件,能对页面上的元素进行重新排序及过滤,并且有非常不错的洗牌过渡动画效果,可以应用在很多项目中来增强用户体验.本文以实际项目应用来讲解Quicksand的使用. XHTML ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <div id="nav">    

jQuery层动画定位滑动效果的方法

  本文实例讲述了jQuery层动画定位滑动效果的方法.分享给大家供大家参考.具体实现方法如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 7

wpf 动画实现以下效果

问题描述 wpf 动画实现以下效果 想要实现上面链接的动画效果,有大神实现过吗? 解决方案 WPF实现射线效果动画WPF实现淡入淡出效果

Android中利用viewflipper动画切换屏幕效果_Android

整个项目的 package com.example.viewflipper; import android.R.integer; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.GestureDetector.OnDoubleTapListener; import android.view.Menu; import android.view.Me

js实现div拖动动画运行轨迹效果代码分享_javascript技巧

本文实例讲述了js div拖动动画运行轨迹效果.分享给大家供大家参考.具体如下: 这是一款基于js实现的div拖动动画运行轨迹效果源码,是一款原生js div拖动效果制作鼠标拖动div动画运行轨迹效果代码.可以选择[记住轨迹]与[不记住轨迹]两种拖动显示模式,从而显示出不同的拖动效果. 运行效果图:                                        -------------------查看效果 下载源码------------------- 小提示:浏览器中如果不能

jQuery层动画定位滑动效果的方法_jquery

本文实例讲述了jQuery层动画定位滑动效果的方法.分享给大家供大家参考.具体实现方法如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml">