代码实现效果 gitHub地址:https://github.com/BearRan/FlowMenuAnimation
原图效果
http://www.tuyiyi.com/v/46575.html
前言:
注意!由于collisionBoundsType是iOS9.0之后才有的,所以运行本demo时一定要是9.0之后的系统,不然会出现异常。而且屏幕适配暂时没调,所以iPhone6S运行最佳。
本来一味着想用CAAnimation来实现,却发现CABasicAnimation和CAKeyFrameAnimation完全不够用。有一位网友提醒我用有物理效果的UIDynamic这个试试,但是之前没有接触过这一块,也就只能现学现卖了。
本章只说明滚珠的难点和动效实现,其他部分的动效就简单的带过了。关于UIDynamic如何使用本文也不做讲解,文章结尾会放置一些比较好的链接。
难点:
- 滚珠路径如何绘制
- 滚珠动画该分解成几个步骤
- 如何分解滚珠的物理效果
###难点解析:
####1.滚珠路径如何绘制
使用BezierPath是毋庸置疑的,但是如何勾勒出来呢?用代码一点一点地调太费时间了。建议使用Sketch和CodePaint共同完成。
dmg资源:http://blog.youkuaiyun.com/xiongbaoxr/article/details/50989283
如何使用可以参考这篇博客:http://blog.youkuaiyun.com/xiongbaoxr/article/details/50890565
Sketch:可以方便地勾勒出路径,并且生成SVG文件。
CodePaint:可以将SVG的路径文件转换成代码
大致步骤如下:
把效果图截取一张,拖到Sketch做背景
新建图层,使用钢笔工具临摹背景图勾勒出路径
注意:勾勒时可以选择非镜像的控制点,这样方便调整曲线
接着点击Sketch右下角的倒出按钮,格式选为SVG格式
将生成的SVG文件拖入到codePaint中,拖入成功后会直接看到如下界面
看到代码路径后可以整段copy出来,当然,我建议把所有的点抽离出来,方便做适配
代码绘制凹槽动画
demo的AppDelegate.h中有以下这些开关,可以自己调配,方便观察
单个滚珠动效演示,这样看应该比较容易理解。其实我是盖了一个新的图层专门用来做滚珠动效的。
控制点就是用普通的UIAniamtion实现的,在动画之行的过程中,通过CADisplayLink实时观察Point的PresentLayer的position来不停的重绘贝塞尔曲线。
####2.滚珠动画该分解成几个步骤
主要分为以下步骤
阶段一:滚珠一起滚落下来时到最高点的阶段;
阶段二:滚珠滚落到最高点后回流的的阶段;
阶段三:滚珠消失阶段;
####3.如何分解滚珠的物理效果
UIDynamic有以下物理效果
- UIGravityBehavior:重力行为
- UICollisionBehavior:碰撞行为
- UISnapBehavior:捕捉行为
- UIPushBehavior:推动行为
- UIAttachmentBehavior:附着行为
- UIDynamicItemBehavior:动力元素行为
###UIGravityBehavior:重力行为
不再解释
#pragma mark 重力行为
- (UIGravityBehavior *)addGravityBehavior:(id <UIDynamicItem>)item
{
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] init];
[gravityBehavior addItem:item];
[_animator addBehavior:gravityBehavior];
return gravityBehavior;
}
###UICollisionBehavior:碰撞行为
适用于:UIView和父类view的边界碰撞,以及和其他UIView碰撞
这里可以配合重力行为来设定小球的运动路径
#pragma mark 碰撞行为
- (UICollisionBehavior *)addCollisionBehavior:(id <UIDynamicItem>)item
{
UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] init];
[collisionBehavior addItem:item];
[collisionBehavior addBoundaryWithIdentifier:@"path" forPath:_beizerPath];
[_animator addBehavior:collisionBehavior];
return collisionBehavior;
}
###UISnapBehavior:捕捉行为
顾名思义,不解释
UIPushBehavior:推动行为
使用瞬间或持续的力并按照某一方向作用于某个UIView
用于开始或结束时的小球推动
#pragma mark 显现动画,第一个球向右的推力
- (UIPushBehavior *)addPushBehavior_inFirstBtn
{
UIButton *tempBtn = _btnArray[0];
UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[tempBtn] mode:UIPushBehaviorModeInstantaneous];
pushBehavior.pushDirection = CGVectorMake(1, 0.3);
pushBehavior.magnitude = 1.6;
[_animator addBehavior:pushBehavior];
return pushBehavior;
}
###UIAttachmentBehavior:附着行为
UIView和某个UIView的相互吸附行为
此处用于小球见的相互吸附作用
#pragma mark 添加球与球之间的附着行为
- (UIAttachmentBehavior *)addAttachmentBehavior_item:(id <UIDynamicItem>)item attachToItem:(id <UIDynamicItem>)attachToItem
{
SpecialBtn *tempBtn = (SpecialBtn *)item;
UIAttachmentBehavior *attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:item attachedToItem:attachToItem];
[attachmentBehavior setLength:tempBtn.width + 20];
[attachmentBehavior setDamping:10.01];
[attachmentBehavior setFrequency:1];
[_animator addBehavior:attachmentBehavior];
return attachmentBehavior;
}
###UIDynamicItemBehavior:动力元素行为
一些其他的物理元素,比如摩擦力,线速度阻力,角速度阻力等
#pragma mark 动力元素行为
- (UIDynamicItemBehavior *)addDynamicItemBehavior:(id <UIDynamicItem>)item
{
UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[item]];
itemBehavior.resistance = 0;
itemBehavior.allowsRotation = YES;
itemBehavior.angularResistance = 4.0;
itemBehavior.friction = 0.8;
[_animator addBehavior:itemBehavior];
return itemBehavior;
}
###阶段一:滚珠一起滚落下来时到最高点的阶段
####球与球之间的附着行为 + 重力行为+碰撞行为+ 动力元素行为+ 第一个球向右的推力
#pragma mark 显现动画
- (void)showBtnsAnimation
{
_animatorStatus = kAnimatorStatus_open;
[_animator removeAllBehaviors];
if (showPath) {
_pathLayer.path = _beizerPath.CGPath;
_pathLayer.fillColor = [UIColor clearColor].CGColor;
_pathLayer.strokeColor = [UIColor orangeColor].CGColor;
_pathLayer.lineWidth = 2.0;
[self.layer addSublayer:_pathLayer];
}
CGFloat btn_gap = [self setXX:16];
for (int i = 0; i < [_btnArray count]; i++) {
SpecialBtn *tempBtn = _btnArray[i];
tempBtn.tag = i;
[self addSubview:tempBtn];
// 设定初始位置
[tempBtn setX:(tempBtn.width + btn_gap) * ([_btnArray count] - 1 - i) + btn_gap];
[tempBtn setY:-tempBtn.height];
// 添加球与球之间的附着行为
if (i > 0) {
[self addAttachmentBehavior_item:_btnArray[i] attachToItem:_btnArray[i - 1]];
}
// 重力行为
UIGravityBehavior *gravityBehavior = [self addGravityBehavior:tempBtn];
if (i == [_btnArray count] - 1) {
// 最后一个球处理重力行为
[self dealLastBtnGravityBehavior:gravityBehavior tempBtn:tempBtn];
}
// 碰撞行为
[self addCollisionBehavior:tempBtn];
// 动力元素行为
UIDynamicItemBehavior *itemBehavior = [self addDynamicItemBehavior:tempBtn];
if (i == [_btnArray count] - 1) {
// 最后一个球增加密度,以防止出现的时候,由于惯性的原因导致飞起来
itemBehavior.density = 1.8;
}
}
// 第一个球向右的推力
[self addPushBehavior_inFirstBtn];
}
###阶段二:滚珠滚落到最高点后回流的的阶段
####球与球之间的附着行为 + 重力行为+碰撞行为+ 动力元素行为+ 最后一个球向左push
for (int i = 0; i < [_btnArray count]; i++) {
// 添加球与球之间的附着行为
if (i > 0) {
UIAttachmentBehavior *attachmentBehavior = [self addAttachmentBehavior_item:_btnArray[i] attachToItem:_btnArray[i - 1]];
[attachmentBehavior setFrequency:5];
[attachmentBehavior setLength:tempBtn.width + 5];
}
// 重力行为
[self addGravityBehavior:_btnArray[i]];
// 碰撞行为
[self addCollisionBehavior:_btnArray[i]];
// 动力元素行为
UIDynamicItemBehavior *itemBehavior = [self addDynamicItemBehavior:_btnArray[i]];
itemBehavior.angularResistance = 15.0;
}
// 最后一个球向左push
UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[tempBtn] mode:UIPushBehaviorModeContinuous];
pushBehavior.pushDirection = CGVectorMake(-1, -0.5);
pushBehavior.magnitude = 2.3;
[_animator addBehavior:pushBehavior];
###阶段三:滚珠消失阶段
####球与球之间的附着行为 + 重力行为+碰撞行为+ 动力元素行为+ 最后一个球向左push(和阶段二几本一致,只是推动的力大了一些)
#pragma mark 消退动画
- (void)closeBtnsAniamtion
{
NSLog(@"-- closeBtnsAniamtion");
_animatorStatus = kAnimatorStatus_close;
SpecialBtn *lastBtn = (SpecialBtn *)[_btnArray lastObject];
[_animator removeAllBehaviors];
for (int i = 0; i < [_btnArray count]; i++) {
// 添加球与球之间的附着行为
if (i > 0) {
UIAttachmentBehavior *attachmentBehavior = [self addAttachmentBehavior_item:_btnArray[i] attachToItem:_btnArray[i - 1]];
[attachmentBehavior setFrequency:5];
[attachmentBehavior setLength:lastBtn.width + 5];
}
// 重力行为
UIGravityBehavior *gravityBehavior = [self addGravityBehavior:_btnArray[i]];
if (i == 0) {
[self dealFirstDisappearBtnGravityBehavior:gravityBehavior tempBtn:_btnArray[i]];
}
// 碰撞行为
[self addCollisionBehavior:_btnArray[i]];
// 动力元素行为
[self addDynamicItemBehavior:_btnArray[i]];
}
// 最后一个球向左push
UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[lastBtn] mode:UIPushBehaviorModeContinuous];
pushBehavior.pushDirection = CGVectorMake(-1, -0.5);
pushBehavior.magnitude = 10.0;
[_animator addBehavior:pushBehavior];
}
结束语:
以上即是滚珠动画的核心步骤和讲解。
###UIDynamic资料相关网址:
Core Animation详解(三)-UIDynamic Animation
http://www.th7.cn/Program/IOS/201502/389463.shtml
WWDC 2013 Session笔记 - UIKit Dynamics入门
https://onevcat.com/2013/06/uikit-dynamics-started/
iOS动画——DynamicAnimate 力学动画
http://www.cnblogs.com/madpanda/p/4742563.html