概念介绍
-
UIDynamic从ios7才开始有的,其他2D仿真引擎:
BOX2D:C语言框架,免费
Chipmunk:C语言框架免费,其他版本收费(C#、Objective-C、Java) -
必须遵守了UIDynamicItem协议的控件才能应用这些行为,UIView遵守了,所以所有控件都可以使用
-
使用步骤:创建一个动画者对象UIDynamicAnimator并设置坐标系,再添加一个动画行为对象(并设置动画作用的控件)
-
UIDynamic中的三个重要概念
- UIDynamicAnimator:动画者,为动力学元素提供物理学相关的能力及动画,同时为这些元素提供相关的上下文,是动力学元素与底层iOS物理引擎之间的中介,将Behavior对象添加到Animator即可实现动力仿真
- UIDynamicBehavior:仿真行为,是动力学行为的父类,基本的动力学行为类UIGravityBehavior、UICollisionBehavior、UIAttachmentBehavior、UISnapBehavior、UIPushBehavior以及UIDynamicItemBehavior均继承自该父类
- UIDynamicItem:动力学元素(动力项,就是应用行为的控件),是任何遵守了UIDynamicItem协议的对象,从iOS 7.0始,UIView和UICollectionViewLayoutAttributes默认实现该协议。如果自定义的对象实现了该协议,即可通过Dynamic Animator实现物理仿真
UIDynamicAnimator(动画者)
是动力行为(UIDynamicBehavior)的容器,添加到容器内的行为才会发挥作用
注意UIDynamicAnimator对象是否是强引用,可以创建一个强引用animator属性,进行懒加载
方法:
- 创建一个动画者并设置一个坐标系view,参数:动画效果在哪个view的范围、坐标系之内
例子:-(instancetype)initWithReferenceView:(UIView *)view;
UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
2.添加行为
- (void)addBehavior:(UIDynamicBehavior *)behavior;
3.移除行为、移除所有行为
- (void)removeBehavior:(UIDynamicBehavior *)behavior;
- (void)removeAllBehaviors;
属性:
1.获取坐标系View
@property (nullable, nonatomic, readonly) UIView *referenceView;
2.获取所有行为对象
@property (nonatomic, readonly, copy) NSArray<__kindof UIDynamicBehavior*> *behaviors;
动力行为(UIDynamicBehavior)
所有的动力学行为可以独立作用也可以组合使用,注意:避免重复添加行为对象,可以用懒加载
UIDynamicBehavior (抽象类):
UIGravityBehavior:重力行为
UICollisionBehavior:碰撞行为
UISnapBehavior:甩行为
UIAttachmentBehavior:附着行为
UIPushBehavior:推行为
UIDynamicItemBehavior:动力学元素行为(执行动画物体自身的行为)
UIDynamicItem 协议,动力学元素、动力项协议
ios9新特性参考 http://www.cocoachina.com/ios/20150719/12613.html
@property (nonatomic, readwrite) CGPoint center;
@property (nonatomic, readonly) CGRect bounds;(只读)
@property (nonatomic, readwrite) CGAffineTransform transform;
一、抽象类 UIDynamicBehavior
属性:
1.获取添加到该动态行为中的子动态行为
@property (nonatomic, readonly, copy) NSArray<__kindof UIDynamicBehavior *> *childBehaviors;
2.action属性:行为执行期间一直调用这个action block,可以用来不断监听行为,如在block中重绘
@property (nullable, nonatomic,copy) void (^action)(void);
3.获取该动态行为相关联的dynamicAnimator
@property (nullable, nonatomic, readonly) UIDynamicAnimator *dynamicAnimator;
方法:
1、添加一个子动态行为
- (void)addChildBehavior:(UIDynamicBehavior *)behavior;
2、移除一个子动态行为
- (void)removeChildBehavior:(UIDynamicBehavior *)behavior;
3、 当该动态行为将要被添加到一个UIDynamicAnimator中时,这个方法会被调用
- (void)willMoveToAnimator:(nullable UIDynamicAnimator *)dynamicAnimator;
二、UIGravityBehavior(重力行为)
属性
1.获取该重力行为的所有动力项
@property (nonatomic, readonly, copy) NSArray<id <UIDynamicItem>> *items;
2.重力的方向,默认(0.0,1.0)向下移动
(1.0,1.0),是右下角移动,(1.0,0.0)是向右移动,坐标上非0的位置表示移动的方向
@property (readwrite, nonatomic) CGVector gravityDirection;
例子:
gravity.gravityDirection = CGVectorMake(1.0, 1.0);
3.方向的弧度,向哪个方向进行重力动画(和上面的效果差不多)
@property (readwrite, nonatomic) CGFloat angle;
例子:
gravity.angle = M_PI_2;
4.力度、力量(越大掉的越快,默认是1)
@property (readwrite, nonatomic) CGFloat magnitude;
方法
1.创建一个重力行为同时添加一组动力项
- (instancetype)initWithItems:(NSArray<id <UIDynamicItem>> *)items;
例子:
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]];
2.添加一个动力项
- (void)addItem:(id <UIDynamicItem>)item;
3.移除一个动力项
- (void)removeItem:(id <UIDynamicItem>)item;
4.设置重力方向和力度
- (void)setAngle:(CGFloat)angle magnitude:(CGFloat)magnitude;
三、UICollisionBehavior:碰撞行为
属性
1.获取该碰撞行为的所有动力项
@property (nonatomic, readonly, copy) NSArray<id <UIDynamicItem>> *items;
2.碰撞模式
@property (nonatomic, readwrite) UICollisionBehaviorMode collisionMode;
UICollisionBehaviorMode 枚举:
UICollisionBehaviorModeItems 仅仅和控件碰撞
UICollisionBehaviorModeBoundaries 仅仅和边界碰撞
UICollisionBehaviorModeEverything 可以和边界和控件碰撞
3.是否以参照视图的bounds为碰撞边界,设置为YES会设置当前view为边界
@property (nonatomic, readwrite) BOOL translatesReferenceBoundsIntoBoundary;
4.获取该碰撞所有的边界标识
@property (nullable, nonatomic, readonly, copy) NSArray<id <NSCopying>> *boundaryIdentifiers;
5.代理对象(可以监听动力项的碰撞过程)
@property (nullable, nonatomic, weak, readwrite) id <UICollisionBehaviorDelegate> collisionDelegate;
方法
1.创建一个碰撞行为同时添加给一组动力项
- (instancetype)initWithItems:(NSArray<id <UIDynamicItem>> *)items;
2.给该行为添加一个动力项
- (void)addItem:(id <UIDynamicItem>)item;
3.给该行为移除一个动力项
- (void)removeItem:(id <UIDynamicItem>)item;
4.设置参照视图的bounds为边界,并且设置视图的内边距
- (void)setTranslatesReferenceBoundsIntoBoundaryWithInsets:(UIEdgeInsets)insets;
5.设置边界线的两种方法,identifier参数是给这个边界随意取一个标识,碰到边界后会产生一些行为方法,所以要指定一个标识,用于以后引用
(1)设置一个贝塞尔曲线路径为边界
- (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier forPath:(UIBezierPath *)bezierPath;
例子:
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 320, 320)];
[collision addBoundaryWithIdentifier:@"circle" forPath:path];
(2)设置一条线为边界
- (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier fromPoint:(CGPoint)p1 toPoint:(CGPoint)p2;
6.移除该碰撞所有边界
- (void)removeAllBoundaries;
7.根据边界标识获取路径
- (nullable UIBezierPath *)boundaryWithIdentifier:(id <NSCopying>)identifier;
8.根据边界标识移除边界
- (void)removeBoundaryWithIdentifier:(id <NSCopying>)identifier;
UICollisionBehaviorDelegate 代理方法:
注意:碰撞代理是collisionDelegate,而不是delegate,注意与父类代理区分,如:
collision.collisionDelegate = self;
1.一个动力项碰到另一个动力项:
(1)一个动力项开始碰到另一个动力项时调用
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2 atPoint:(CGPoint)p;
(2)一个动力项结束碰到另一个动力项时调用
- (void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2;
2.动力项和边界的碰撞:
(1)动力项开始碰到某个边界
- (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(nullable id <NSCopying>)identifier atPoint:(CGPoint)p;
(2)动力项结束碰到某个边界
- (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(nullable id <NSCopying>)identifier;
例子:一个动力项碰撞到边界后改变不同颜色
// 碰撞行为的代理方法
- (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(nullable id <NSCopying>)identifier atPoint:(CGPoint)p {
//获取当前碰撞到边界的动力项(把item参数强转成使用的控件)
UIView *view = (UIView *)item;
//获取当前碰撞到的边界名称
NSString *ID = (NSString *)identifier;
//判断当前碰撞到的边界是否是“黄色view的边界”
if ([ID isEqualToString:@"yellow_boundary"]) {
//碰到后就变成蓝色
view.backgroundColor = [UIColor blueColor];
//0.3秒后变成红色
[UIView animateWithDuration:0.3 animations:^{
view.backgroundColor = [UIColor redColor];
}];
}
}
四、UISnapBehavior(甩行为)
效果:触摸哪个点这个红色块就跟随到哪里,并产生附着效果,甩行为可以将视图通过动画甩(吸附)到某个点上
属性
1.振幅,默认0.5,0振幅最大,1振幅最小
@property (nonatomic, assign) CGFloat damping;
2.设置附着点
@property (nonatomic, assign) CGPoint snapPoint;
方法
1.创建一个甩行为同时设置动力项和附着点
- (instancetype)initWithItem:(id <UIDynamicItem>)item snapToPoint:(CGPoint)point;
例子:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = touches.anyObject;
CGPoint loc = [touch locationInView:self];
// 1. 创建动画者
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self];
// 2. 创建物理行为
UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:self.redView snapToPoint:loc];
snap.damping = 0;
// 3. 把行为添加到animator中
[self.animator addBehavior:snap];
}
五、UIAttachmentBehavior(附着行为、吸附行为)
描述一个view和一个锚相连接的情况,也可以描述view和view之间的连接
在多个物体间设定多个UIAttachmentBehavior,可以模拟多物体连接
注意:吸附行为重复添加的问题,建议懒加载行为对象
![](https://i-blog.csdnimg.cn/blog_migrate/8895baef4c7739fdfb8b73cfcab7154e.webp?x-image-process=image/format,png)
分为刚性附着和弹性附着
1、刚性附着,固定了length就是刚性附着,红、蓝两点距离固定
attachment.length = 100;
2、弹性附着(设置了频率和振幅),红、蓝两色像皮筋一样距离不固定
attachment.damping = 0; attachment.frequency = 0.5;
属性
1.获取该吸附行为的所有动力项
当吸附行为类型是UIAttachmentBehaviorTypeItems
时有l两个动力项,当吸附行为类型是UIAttachmentBehaviorTypeAnchor
时只有一个动力项
@property (nonatomic, readonly, copy) NSArray<id <UIDynamicItem>> *items;
2.获取连接类型
@property (readonly, nonatomic) UIAttachmentBehaviorType attachedBehaviorType;
UIAttachmentBehaviorType 枚举:
UIAttachmentBehaviorTypeItems 连接到视图view(至少两个动力项)
UIAttachmentBehaviorTypeAnchor 连接到锚点(只有一个动力项)
3.设置动力项吸附的锚点
@property (readwrite, nonatomic) CGPoint anchorPoint;
4.视图点连接锚点的距离,两个吸附点之间的距离
@property (readwrite, nonatomic) CGFloat length;
5.只要设置了以下两个属性,即为弹性连接
(1)振幅大小, 吸附行为减弱的阻力大小
@property (readwrite, nonatomic) CGFloat damping;
(2)振幅频率
@property (readwrite, nonatomic) CGFloat frequency;
方法
注意: 吸附行为的创建不是直接 alloc + init, 而是 alloc + initWithItem
1.构造方法
(1)创建一个吸附行为,让一个动力项的中点和一个指定的锚点进行吸附,该初始化方法的吸附行为的类型是UIAttachmentBehaviorTypeAnchor
- (instancetype)initWithItem:(id <UIDynamicItem>)item attachedToAnchor:(CGPoint)point;
(2)创建一个吸附行为,让两个动力项的中点进行吸附, 该初始化方法的吸附行为的类型是UIAttachmentBehaviorTypeItems
- (instancetype)initWithItem:(id <UIDynamicItem>)item1 attachedToItem:(id <UIDynamicItem>)item2;
(3)创建一个吸附行为,让一个动力项的某一点和指定的锚点进行吸附,UIAttachmentBehaviorTypeAnchor
类型,offset相对于动力项center的偏移
- (instancetype)initWithItem:(id <UIDynamicItem>)item offsetFromCenter:(UIOffset)offset attachedToAnchor:(CGPoint)point;
(4)创建一个吸附行为,让一个动力项的某一点和另一个动力项的某一点进行吸附,UIAttachmentBehaviorTypeItems
类型,offset相对于动力项center的偏移
- (instancetype)initWithItem:(id <UIDynamicItem>)item1 offsetFromCenter:(UIOffset)offset1 attachedToItem:(id <UIDynamicItem>)item2 offsetFromCenter:(UIOffset)offset2;
把以子视图为基准的坐标转换为以父视图为基准的视图坐标方法:
注意:不能直接修改子控件的anchorPoint为0,0,因为以后使用center就会导致无法使用!!!
![]()
子视图为基准的坐标转换为以父视图为基准的视图坐标方法
UIPushBehavior(推行为)
注意要进行懒加载,多次添加会导致无效
1.获取该行为作用的动力项
@property (nonatomic, readonly, copy) NSArray<id <UIDynamicItem>> *items;
2.推力类型
@property (nonatomic, readonly) UIPushBehaviorMode mode;
UIPushBehaviorModeContinuous 持续推力
UIPushBehaviorModeInstantaneous 瞬时推力
3.是否激活,active表示推力是否还在作用,如果是瞬时推力,需要激活
@property (nonatomic, readwrite) BOOL active;
4、推动角度,可使用0~M_PI*2之间的值来定位所有方向
@property (readwrite, nonatomic) CGFloat angle;
5、重力加速度,推力大小,越远力量越大,默认为1.0,可取负值
@property (readwrite, nonatomic) CGFloat magnitude;
算法:取触摸点loc的x,y与动力项的中点center的x,y做差算出两条直线长度,再用勾股定理:sqrt(x^2+y^2) 求出
例子:
CGFloat offsetX = loc.x - self.redView.center.x; CGFloat offsetY = loc.y - self.redView.center.y; CGFloat distance = sqrtf(powf(offsetX, 2.0) + powf(offsetY, 2.0)); //powf 函数为浮点型的参数1的参数2次方
6.推动方向,CGVector 矢量
@property (readwrite, nonatomic) CGVector pushDirection;
例子:push.pushDirection = CGVectorMake(1, 1);
方法:
1.将行为添加到动力项当中
- (void)addItem:(id <UIDynamicItem>)item;
2.将行为从动力项当中移除
- (void)removeItem:(id <UIDynamicItem>)item;
3.创建一个推行为添加给一组动力项,并设置推力类型
- (instancetype)initWithItems:(NSArray<id<UIDynamicItem>> *)items mode:(UIPushBehaviorMode)mode;
4.设置推动角度和力度
- (void)setAngle:(CGFloat)angle magnitude:(CGFloat)magnitude;
5.获取推力作用点的偏移量,默认是center
- (UIOffset)targetOffsetFromCenterForItem:(id <UIDynamicItem>)item;
6.设置推力作用点的偏移量,默认是center
- (void)setTargetOffsetFromCenter:(UIOffset)o forItem:(id <UIDynamicItem>)item;
UIDynamicItemBehavior(动力项行为)
控件本身的行为:弹性系数、摩擦系数、密度、阻力、角阻力以及是否允许旋转等
属性
1.获取有该行为的所有动力项
@property (nonatomic, readonly, copy) NSArray<id <UIDynamicItem>> *items;
2.设置弹性系数,决定了碰撞的弹性程度,比如碰撞时物体的弹性,值从0—1,0为无弹力
@property (readwrite, nonatomic) CGFloat elasticity;
3.摩擦系数,决定了沿接触面滑动时的摩擦力大小,0为无摩擦,1最大
@property (readwrite, nonatomic) CGFloat friction;
4.密度,和size结合使用,计算物体的总质量。质量越大,物体加速或减速就越困难,默认为1
@property (readwrite, nonatomic) CGFloat density;
5.阻力,决定线性移动的阻力大小,与摩擦系数不同,摩擦系数只作用于滑动运动,0为无阻力
@property (readwrite, nonatomic) CGFloat resistance;
6.角阻力,决定旋转运动时的阻力大小
@property (readwrite, nonatomic) CGFloat angularResistance;
7.是否允许旋转,设置这个属性为 NO 无论施加多大的转动力物体都不会转动
@property (readwrite, nonatomic) BOOL allowsRotation;
8.charge 代表能够影响一个动力项在电磁场上如何移动的电荷
@property (readwrite, nonatomic) CGFloat charge;
9.anchored本质上是将图形变成了碰撞中的一个静态物体,但没有响应事件(如果有什么东西撞上了它,它会丝毫不动),所以可以完美地用来表示地板或墙壁。
@property (nonatomic, getter = isAnchored) BOOL anchored;
方法
1.创建一个动力项行为对象,并添加到一组动力项当中
- (instancetype)initWithItems:(NSArray<id <UIDynamicItem>> *)items;
2.将行为添加到动力项当中
- (void)addItem:(id <UIDynamicItem>)item;
3.将行为从动力项当中移除
- (void)removeItem:(id <UIDynamicItem>)item;
4、配置一些公用的属性,与其他的Dynamic Behavior共同配合
(1)添加线速度,滑行时候的速度
- (void)addLinearVelocity:(CGPoint)velocity forItem:(id <UIDynamicItem>)item;
(2)获取线速度
- (CGPoint)linearVelocityForItem:(id <UIDynamicItem>)item;
(3)添加角速度,旋转时候的速度
- (void)addAngularVelocity:(CGFloat)velocity forItem:(id <UIDynamicItem>)item;
(4)获取角速度,动力项自身旋转的速度
- (CGFloat)angularVelocityForItem:(id <UIDynamicItem>)item;