前言
之前在iOS开发干货 第1期 中提到过一个挺有意思的数字转变动画NumberMorphView , 如下图:
NumberMorphView
我将通过几篇文章对这个开源库做一些分析,当然,这篇文章不会对它做全面的解析,而是利用这个库的一些技术概念来做一些简单的示例,也算是一个引子,后面会抽时间再写一篇对这个库的代码分析,敬请期待。
要做些什么
我们将会使用CADisplayLink + CAShapeLayer + UIBezierPath结合制作一个毫秒级的画圆动画,不同的是,这个动画具有弹性效果,下面先来看看制作的效果:
效果预览
开始
准备工作
先新建一个Single View Application项目,在项目中添加类RRCircleAnimationView,继承于UIView。
打开Main.storyboard,将唯一的一个ViewController的view custom class修改为RRCircleAnimationView。
至此,准备工作已经完成。
动手来画个圆
先来个简单任务,我们来实现画圆动画。
第一步,为RRCircleAnimationView添加属性:
@implementationRRCircleAnimationView{CADisplayLink*_displayLink;// CADisplayLink可以确保系统渲染每一帧的时候我们的方法都被调用, 从而保证了动画的流畅性,毫秒级动画就靠他。UIBezierPath*_path;// 用于创建基于矢量的路径CGPoint_beginPoint;// 开始触摸位置CGPoint_endPoint;// 触摸结束的位置CAShapeLayer*_shapeLayer;// 可以结合UIBezierPath进行绘画}
接着初始化实例变量,由于我们用的是storyboard进行加载,所以可以在awakeFromNib方法里面初始化
// 注意这里我们是直接从xib加载当前view。
- (void)awakeFromNib{
_shapeLayer = [CAShapeLayerlayer];
[self.layeraddSublayer:_shapeLayer];
_shapeLayer.fillColor= [UIColorcolorWithRed:0.400green:0.400blue:1.000alpha:1.000].CGColor;
_displayLink = [CADisplayLinkdisplayLinkWithTarget:selfselector:@selector(updateFrame)];
[_displayLink addToRunLoop:[NSRunLoopcurrentRunLoop] forMode:NSRunLoopCommonModes];
}
接下来实现上面CADisplayLink要不停调用的updateFrame方法,我们在此方法内不断地画圆。
- (void)updateFrame {// 画圆
_path = [UIBezierPathbezierPathWithArcCenter:_beginPoint radius:
[selfgetRadius] startAngle:0endAngle:M_PI*2clockwise:YES];
_shapeLayer.path= _path.CGPath;
}
上面我们用开始触摸的点的位置作为圆心的位置,再根据特定的半径进行绘制一个圆,这个半径是根据我们触摸的开始点和结束点进行计算出来的,开始触摸点到结束点的距离就是这个圆的半径。
我们先把触摸的起始和结束点给找到:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event{
UITouch*touch = [touches anyObject];CGPointpoint = [touch locationInView:self];
_beginPoint = point;
_endPoint = point;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)event{
UITouch*touch = [touches anyObject];CGPointpoint = [touch locationInView:self];
_endPoint = point;
}
最后计算用上我们中学的数学知识,根据两点坐标距离公式
两点坐标距离公式
可以得到我们起始和结束两点的距离,也就是圆的半径是:
- (CGFloat)getRadius{ CGFloat result =sqrt(pow(_endPoint.x - _beginPoint.x,2) +pow(_endPoint.y - _beginPoint.y,2));returnresult;}
到这里画圆动画完成。
加入弹性效果
上面只是的画圆动画看起来是没什么问题了,不过总感觉缺少动感,接下来我们来帮他加入些活力!
添加一下成员变量到RRCircleAnimationView类中。
BOOL_isTouchEnd;// 触摸结束标志int_currentFrame;// 当前的帧数
在- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event方法内添加以下代码:
_isTouchEnd =NO;//重置触摸状态_currentFrame =1;//重置当前的帧数
添加以下方法:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event { _isTouchEnd =YES;//触摸结束,更新触摸状态}
将方法- (CGFloat)getRadius修改如下:
- (CGFloat)getRadius {
CGFloatresult = sqrt(pow(_endPoint.x-_beginPoint.x,2)+pow(_endPoint.y-_beginPoint.y,2));
if(_isTouchEnd) {
CGFloatanimationDuration =1.0;// 弹簧动画持续的时间
intmaxFrames = animationDuration / _displayLink.duration;
_currentFrame++;
if(_currentFrame <= maxFrames) {
CGFloatfactor = [selfgetSpringInterpolation:(CGFloat)(_currentFrame) / (CGFloat)(maxFrames)];//根据公式计算出弹簧因子
returnMAX_RADIUS + (result - MAX_RADIUS) * factor;// 根据弹簧因子计算当前帧的圆半径
}else{
return MAX_RADIUS;
}
}
return result;
}
最后加入神奇的公式:
- (CGFloat)getSpringInterpolation:(CGFloat)x {
CGFloat tension =0.3;// 张力系数
returnpow(2, -10* x) *sin((x - tension /4) * (2* M_PI) / tension);
}
这个公式用数学符号表达出来是:
Math
可以用Mac OS X自带的软件叫Grapher画出此函数的的图像,如下图:
Grapher
这个函数的作用其实就是通过x值,也就是当前帧数除以允许的最大帧数。
(CGFloat)(_currentFrame)/(CGFloat)(maxFrames)
因此,x的值的范围也就是(0, 1]。
我们所要的动画效果是把圆拉大到超过或者小于设定的目标半径MAX_RADIUS时,需要一个弹性动画逐渐回到设定好的目标半径。
回头再看一下实时计算动画半径的公式:
MAX_RADIUS +(result- MAX_RADIUS)* factor
为了让x = 1的时候,半径 = MAX_RADIUS,所以这时factor就应该为0,也就是f(1) = 0。
再看看刚才的函数图像,在x = 0到1之前振动,随着x的增加振幅逐渐减少,当x = 1的时候,y值为0。
最后
这篇文章讲述了如何自己实现具有弹性的帧动画,如果能理解好这种动画制作原理,对动画效果开发是很有帮助的,后面有时间会继续写其他的一些动画制作的方法,实现更多的动画效果。
差点忘了说了,目前这个动画已经放到github上面,传送门:RRongAnimation
RRongAnimation
The End
文/Mellong(作者)
原文链接:http://www.jianshu.com/p/cf9f600a5342#
长按关注:
QQ群:427763454
欢迎你的投稿,展示的你的技术文章:[email protected]