收藏iosiOS点点滴滴

iOS 弹性动画解析与封装

2017-12-22  本文已影响806人  4335151e8554
QQ20171221-161056-HD.gif

有什么能让App界面变得更加生动呢,答案就是动画效果,其中带弹性的动画效果我尤其喜欢,iOS 给我们提供了一个api
+(void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
但是这个api有一个局限性,就是他只能控制一个view的整体变化,像上图那种只让矩形的底边做弹性运动,光靠这个api难以实现。


QQ20171221-164020.png 以上动画的实现思路其实也是现在网上比较流行的一种思路:红色区域是一个CAShapeLayer,通过不断的改变CAShapeLayer的path来改变其形状。在touchBegin方法中获得当前触摸点,生成路径赋值给shapeLayer,现在CE边就可以跟着手指动啦。 QQ20171222-171251.png 问题的关键就是不断的改变这个path来限制CAShapeLayer的轮廓。变化的地方就是C点到E点这条路径,其中D点是CE边的控制点,通过改变D点坐标,不断的生成新的path赋值给CAShapeLayer,这样CE边就会跟着手指不断变化,CAShapeLayer的形状也就相应的变化了。

等等,这里只是手指滑动的时候要做的事情,当手指松开后如何让控制点D动态地回到原处(即CE的中心位置)并且还要有弹性效果呢?这里就需要用到上面所说的弹性动画api了,其实我在D点这个位置放了一个view,就是动图上面的黑点,手指滑动的时候黑色view也跟着D点运动,当手指松开时对黑色view做一个弹性动画,并且运行一个CADisplayLink,并且在CADisplayLink触发的方法中不断获取黑色view.layer.presentationLayer.position的值,刷新D点的位置,这样D点也能跟着黑色view做弹性运动了,这样就实现了回弹和弹性效果。
是不是很麻烦!又是加黑色view,又是对黑色view做震荡动画,又是开CADisplayLink,又是监控黑色view的位置,其实就是为了获得一串带震荡效果的点,并连续的赋值给D点。所以这里我提供一个封装了的类,里面有一个方法就是产生连续的带震荡效果的值,并且不断的将值赋值给block,这样外界就能在block中直接获得这些值做动画了。我在这个类里面已经开了CADisplayLink,会不断的调用block,并且将新值付给block。
.h文件

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef void(^CallBackBlockValue)(CGFloat value);
typedef void(^CallBackBlockPoint)(CGPoint point);
@interface animationTools : NSObject
//单个值的震荡
-(void)animationWithFormValue:(CGFloat)formValue
                      toValue:(CGFloat)toValue
                      damping:(CGFloat)damping
                     velocity:(CGFloat)velocity
                     duration:(CGFloat)duration
                     callback:(CallBackBlockValue)callback;

//点的震荡
-(void)animationWithFormPoint:(CGPoint)formPoint
                      toPoint:(CGPoint)toPoint
                      damping:(CGFloat)damping
                     velocity:(CGFloat)velocity
                     duration:(CGFloat)duration
                     callback:(CallBackBlockPoint)callback;
@end

.m文件

#import "animationTools.h"
#import <UIKit/UIKit.h>

@interface animationTools()
@property (nonatomic,strong) CADisplayLink *displaylink;
@property (nonatomic,assign) CGFloat formValue;
@property (nonatomic,assign) CGFloat toValue;
@property (nonatomic,assign) CGPoint formPoint;
@property (nonatomic,assign) CGPoint toPoint;
@property (nonatomic,assign) CGFloat damping;
@property (nonatomic,assign) CGFloat velocity;
@property (nonatomic,assign) NSInteger numberOfnum;
@property (nonatomic,assign) NSInteger beginNum;
@property (nonatomic,copy)   CallBackBlockValue callbackValue;
@property (nonatomic,copy)   CallBackBlockPoint callbackPoint;
@end
@implementation animationTools


/*
 * damping = 5;     //越小 幅度越大
 * velocity = 30;   //越大 震动次数越多
 */
-(void)animationWithFormValue:(CGFloat)formValue
                      toValue:(CGFloat)toValue
                      damping:(CGFloat)damping
                     velocity:(CGFloat)velocity
                     duration:(CGFloat)duration
                     callback:(CallBackBlockValue)callback
{
    [self.displaylink invalidate];
    self.displaylink = nil;
    self.numberOfnum = duration * 60;   //总帧数
    self.formValue = formValue;
    self.toValue = toValue;
    self.damping = damping;
    self.velocity = velocity;
    self.beginNum = 0;
    self.callbackValue = callback;
    self.displaylink = [CADisplayLink displayLinkWithTarget:self selector:@selector(SEL_Displaylink_value)];
    [self.displaylink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}

-(void)SEL_Displaylink_value
{
    if (self.beginNum >= self.numberOfnum) {
        [self.displaylink invalidate];
        self.displaylink = nil;
    }
    //计算
    CGFloat i = self.beginNum * 1.0/self.numberOfnum;
    CGFloat value = self.toValue - (self.toValue - self.formValue) * (pow(M_E, -self.damping * i) * cos(self.velocity * i)); //1 y = 1-e^{-5x} * cos(30x)
    if (self.callbackValue != nil) {
        self.callbackValue(value);
    }
    self.beginNum ++;
}

-(void)animationWithFormPoint:(CGPoint)formPoint
                      toPoint:(CGPoint)toPoint
                      damping:(CGFloat)damping
                     velocity:(CGFloat)velocity
                     duration:(CGFloat)duration
                     callback:(CallBackBlockPoint)callback
{
    [self.displaylink invalidate];
    self.displaylink = nil;
    self.numberOfnum = duration * 60;   //总帧数
    self.formPoint = formPoint;
    self.toPoint = toPoint;
    self.damping = damping;
    self.velocity = velocity;
    self.beginNum = 0;
    self.callbackPoint = callback;
    self.displaylink = [CADisplayLink displayLinkWithTarget:self selector:@selector(SEL_Displaylink_Point)];
    [self.displaylink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}

-(void)SEL_Displaylink_Point
{
    if (self.beginNum >= self.numberOfnum) {
        [self.displaylink invalidate];
        self.displaylink = nil;
    }
    //计算
    CGFloat i = self.beginNum * 1.0/self.numberOfnum;
    CGFloat x = self.toPoint.x - (self.toPoint.x - self.formPoint.x) * (pow(M_E, -self.damping * i) * cos(self.velocity * i)); //1 y = 1-e^{-5x} * cos(30x)
    CGFloat y = self.toPoint.y - (self.toPoint.y - self.formPoint.y) * (pow(M_E, -self.damping * i) * cos(self.velocity * i));
    CGPoint point = CGPointMake(x, y);
    if (self.callbackPoint != nil) {
        self.callbackPoint(point);
    }
    self.beginNum ++;
}

@end

需要注意的是damping参数和duration参数,damping数值越小震动次数越多,可能duration设置的比较小,在duration时间结束后弹性效果还没结束,导致骤然停止。需要在使用的时候调试。

使用很简单!创建对象调用这个对象方法,告诉他起点和终点等信息,他就会不断的刷新point值并调用block,以后就不用加辅助view来监听位置变化了 QQ20171222-165241.png 效果图gif质量太低了。。。 QQ20171222-164555-HD.gif
下面是单个值的改变 QQ20171222-173235.png QQ20171222-173347-HD.gif 以下是打印出来的部分值,可以看出value在100上下震动,并最终趋于100 QQ20171222-173645.png
现在我只需要在touchesEnd方法中调用这个方法就可以达到同样的效果了! QQ20171222-180922.png 效果真的一样,甚至要更好,只是这个gif质量太差了。。 QQ20171222-181117-HD.gif
上一篇下一篇

猜你喜欢

热点阅读