简介
UIDynamic
是iOS 7
之后新添加的一些物理仿真动画库,包含在UIKit
框架中。
UIDynamic中的三个重要概念:
- UIDynamicAnimator
动画引擎。为动力学元素提供物理学相关的能力及动画,同时为这些元素提供相关的上下文,是动力学元素与底层iOS物理引擎之间的中介。 - UIDynamicBehavior
仿真行为。创建时,需要附带动画将要作用的视图。 - UIDynamicItem
动力学元素。从iOS7开始,UIView
和UICollectionViewLayoutAttributes
默认实现协议,如果自定义对象实现了该协议,即可通过Dynamic Animator实现物理仿真。
UIDynamicAnimator
- 创建
view参数:是一个参照视图,表示物理仿真的范围。- (instancetype)initWithReferenceView:(UIView *)view;
- 添加一个物理仿真行为
- (void)addBehavior:(UIDynamicBehavior *)behavior;
- 移除一个物理仿真行为
- (void)removeBehavior:(UIDynamicBehavior *)behavior;
- 移除之前添加过的所有物理仿真行为
- (void)removeAllBehaviors;
- 更新我们已经修改的动态项目
- (void)updateItemUsingCurrentState:(id
)item; - 参照视图
@property (nonatomic, readonly) UIView* referenceView;
- 添加到物理仿真器中的所有物理仿真行为
@property (nonatomic, readonly, copy) NSArray* behaviors;
- 是否正在进行物理仿真
@property (nonatomic, readonly, getter = isRunning) BOOL running;
- 代理对象(能监听物理仿真器的仿真过程,比如开始和结束)
@property (nonatomic, assign) id
delegate; @optional // 当dynamicAnimator将要恢复调用 -(void)dynamicAnimatorWillResume:(UIDynamicAnimator *)animator; // 当dynamicAnimator已经暂停调用 -(void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator;
UIDynamicItem
- 中心
@property (nonatomic, readwrite) CGPoint center;
- 实时范围
@property (nonatomic, readonly) CGRect bounds;
- 旋转状态
@property (nonatomic, readwrite) CGAffineTransform transform;
UIDynamicBehavior
在通常情况下我们不会直接使用UIDynamicBehavior类,而是使用它的子类们。在开始介绍它的子类前,先来看看UIDynamicBehavior作为父类的一些属性和方法:
- 添加一个自定义行为子类
- (void)addChildBehavior:(UIDynamicBehavior *)behavior;
- 移除一个自定义行为子类
- (void)removeChildBehavior:(UIDynamicBehavior *)behavior;
- 当一个动态行为被添加或移除,会回调该函数
- (void)willMoveToAnimator:(nullable UIDynamicAnimator *)dynamicAnimator;
- 子行为 (只读)
@property (nonatomic, readonly, copy) NSArray<__kindof UIDynamicBehavior *> *childBehaviors;
- 在运行时调用的每一个动画步骤的block形式动作代码
@property (nullable, nonatomic,copy) void (^action)(void);
- 所属的
dynamicAnimator
@property (nullable, nonatomic, readonly) UIDynamicAnimator *dynamicAnimator;
1. UIGravityBehavior(重力行为)
- 构造
// 根据动力元素组构造 - (instancetype)initWithItems:(NSArray
> *)items; // 添加动力元素 - (void)addItem:(id )item; // 删除动力元素 - (void)removeItem:(id )item; // 获取说有动力元素 @property (nonatomic, readonly, copy) NSArray > *items; - 重力方向
// 这是二维坐标系中的方向,默认是(0.0, 1.0),表示垂直向下,数值越大;数值可以为负,如(0.0, -1.0)就表示重力方向是垂直向上。也可以利用x和y来表示二维坐标系中的任意方向。例如(1.0, 1.0)沿右下角45度方向,(1.0, 100000)极度接近竖直向下方向。 @property (readwrite, nonatomic) CGVector gravityDirection;
- 重力角度
// 是一个角度,x轴正方向为0°,顺时针正数,逆时针负数)。 @property (readwrite, nonatomic) CGFloat angle;
- 力的系数
// 正数时,沿`gravityDirection`方向,数值越大,加速度越大;负数时,`gravityDirection`的反方向,数值越小,加速度越大。 @property (readwrite, nonatomic) CGFloat magnitude;
实例
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]];
gravity.gravityDirection = CGVectorMake(0, 1);
//gravity.angle = M_PI*0.01;
[self.animator addBehavior:gravity];
效果:
2. UICollisionBehavior (碰撞行为)
- 构造
// 根据动力元素组构造 - (instancetype)initWithItems:(NSArray
> *)items // 添加动力元素 - (void)addItem:(id )item; // 删除动力元素 - (void)removeItem:(id )item; // 获取所有动力元素 @property (nonatomic, readonly, copy) NSArray > *items; - 碰撞模式
@property (nonatomic, readwrite) UICollisionBehaviorMode collisionMode; typedef NS_OPTIONS(NSUInteger, UICollisionBehaviorMode) { UICollisionBehaviorModeItems = 1 << 0, 元素碰撞 UICollisionBehaviorModeBoundaries = 1 << 1, 边界碰撞 UICollisionBehaviorModeEverything = NSUIntegerMax 全体碰撞 } NS_ENUM_AVAILABLE_IOS(7_0);
- 碰撞边界
// 是否检测碰撞的边界 @property (nonatomic, readwrite) BOOL translatesReferenceBoundsIntoBoundary; // 设置碰撞`ReferenceView`边界的内边距 - (void)setTranslatesReferenceBoundsIntoBoundaryWithInsets:(UIEdgeInsets)insets; // 通过添加贝塞尔曲线,添加碰撞边界 - (void)addBoundaryWithIdentifier:(id
)identifier forPath:(UIBezierPath *)bezierPath; // 通过添加由两点组成的线段,添加碰撞边界 - (void)addBoundaryWithIdentifier:(id )identifier fromPoint:(CGPoint)p1 toPoint:(CGPoint)p2; // 根据获取指定已命名的碰撞边界的贝塞尔曲线 - (nullable UIBezierPath *)boundaryWithIdentifier:(id )identifier; // 移除指定已命名的碰撞边界 - (void)removeBoundaryWithIdentifier:(id )identifier; // 获得所有命名 @property (nullable, nonatomic, readonly, copy) NSArray > *boundaryIdentifiers; // 移除所有添加的碰撞边界 - (void)removeAllBoundaries; - 代理
// 碰撞代理 @property (nullable, nonatomic, weak, readwrite) id
collisionDelegate; @optional // 当一个两个动态元素之间发生碰撞时调用 - (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id )item1 withItem:(id )item2 atPoint:(CGPoint)p; // 当一个两个动态元素之间碰撞结束时调用 - (void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id )item1 withItem:(id )item2; // 当一个动态元素与边界发生碰撞时调用 - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id )item withBoundaryIdentifier:(nullable id )identifier atPoint:(CGPoint)p; // 当一个动态元素与边界碰撞结束时调用 - (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id )item withBoundaryIdentifier:(nullable id )identifier;
实例一: 元素之间的碰撞
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]];
gravity.gravityDirection = CGVectorMake(1, 1);
[self.animator addBehavior:gravity];
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.redView, self.blueView]];
collision.collisionMode = UICollisionBehaviorModeItems;
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];
效果:
实例二: 边界碰撞
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]];
gravity.gravityDirection = CGVectorMake(1, 1);
[self.animator addBehavior:gravity];
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.redView, self.blueView]];
collision.collisionMode = UICollisionBehaviorModeBoundaries;
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];
效果:
实例三: 碰撞所有
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]];
gravity.gravityDirection = CGVectorMake(1, 1);
[self.animator addBehavior:gravity];
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.redView, self.blueView]];
collision.collisionMode = UICollisionBehaviorModeEverything;
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];
效果:
实例四: 碰撞边界内边距
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]];
gravity.gravityDirection = CGVectorMake(1, 1);
[self.animator addBehavior:gravity];
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.redView, self.blueView]];
collision.collisionMode = UICollisionBehaviorModeEverything;
[collision setTranslatesReferenceBoundsIntoBoundaryWithInsets:UIEdgeInsetsMake(50, 50, 50, 50)];
// 检测边距和内边距互相冲突,以最后设置为准
// collision.translatesReferenceBoundsIntoBoundary = NO;
[self.animator addBehavior:collision];
效果:
实例五: 贝塞尔曲线边界
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]];
gravity.gravityDirection = CGVectorMake(1, 1);
[self.animator addBehavior:gravity];
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.redView]];
collision.collisionMode = UICollisionBehaviorModeEverything;
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 70, 200, 400)];
[collision addBoundaryWithIdentifier:@"path" forPath:path];
[self.animator addBehavior:collision];
效果:
实例六: 两点组成的线段边界
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]];
gravity.gravityDirection = CGVectorMake(1, 1);
[self.animator addBehavior:gravity];
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.redView]];
collision.collisionMode = UICollisionBehaviorModeEverything;
[collision addBoundaryWithIdentifier:@"line" fromPoint:CGPointMake(200, 100) toPoint:CGPointMake(50, 400)];
[self.animator addBehavior:collision];
效果:
3. UIAttachmentBehavior (附着行为)
附着行为一般都是添加手势,让视图跟着手势移动,因为一般都是与手势搭配使用。
- 构造
// 元素和锚点之间的吸附 - (instancetype)initWithItem:(id
)item attachedToAnchor:(CGPoint)point; // 元素和锚点之间的吸附,offset参数设置元素吸附力作用点的偏移量 - (instancetype)initWithItem:(id )item offsetFromCenter:(UIOffset)offset attachedToAnchor:(CGPoint)point NS_DESIGNATED_INITIALIZER; // 元素和元素之间的吸附 - (instancetype)initWithItem:(id )item1 attachedToItem:(id )item2; // 元素和元素之间的吸附,offset1、offset2分别是两个元素吸附力作用点的偏移量 - (instancetype)initWithItem:(id )item1 offsetFromCenter:(UIOffset)offset1 attachedToItem:(id )item2 offsetFromCenter:(UIOffset)offset2 NS_DESIGNATED_INITIALIZER; - 锚点
当吸附发生在元素和锚点之间的时候,我们可以通过anchorPoint属性获得锚点位置,如果吸附发生在元素和元素之间的时候,该属性的值为(0, 0)。@property (readwrite, nonatomic) CGPoint anchorPoint;
- 元素吸附力作用点和锚点的距离
@property (readwrite, nonatomic) CGFloat length;
- 振动频率
除了刚性吸附之外弹性吸附也是很常用的类型,frequency属性就对弹性吸附有着直接的影响。// 值越大,弹性运动的频率越快。 @property (readwrite, nonatomic) CGFloat frequency;
- 弹性阻力
属性是设置元素在弹性吸附时震动所受到的阻力,值越大,阻力越大,弹性运动震动的幅度越小。@property (readwrite, nonatomic) CGFloat damping;
- 旋转阻力
旋转力矩,指围绕一点旋转所受的阻力,默认值0.0,值越大,阻力越大。@property (readwrite, nonatomic) CGFloat frictionTorque NS_AVAILABLE_IOS(9_0);
- 运动范围
@property (readwrite, nonatomic) UIFloatRange attachmentRange NS_AVAILABLE_IOS(9_0);
实例一: 元素和锚点
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]];
gravity.gravityDirection = CGVectorMake(1.0f, 1.0f);
[self.animator addBehavior:gravity];
UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:self.redView attachedToAnchor:CGPointMake(200, 300)];
attachment.length = 50;
[self.animator addBehavior:attachment];
效果:
实例二: 元素和锚点加偏移
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]];
gravity.gravityDirection = CGVectorMake(1.0f, 1.0f);
[self.animator addBehavior:gravity];
UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:self.redView offsetFromCenter:UIOffsetMake(25, 0) attachedToAnchor:CGPointMake(200, 300)];
attachment.length = 50;
[self.animator addBehavior:attachment];
效果:
实例三: 元素与元素
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.redView]];
gravity.gravityDirection = CGVectorMake(0, 1.0f);
[self.animator addBehavior:gravity];
UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:self.redView attachedToItem:self.blueView];
attachment.length = 100;
[self.animator addBehavior:attachment];
效果:
实例四: 手势依附
// 添加手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init];
[pan addTarget:self action:@selector(panDidHandler:)];
[self.view addGestureRecognizer:pan];
// 手势响应
- (void)panDidHandler:(UIPanGestureRecognizer *)pan {
CGPoint location = [pan locationInView:self.view];
if (pan.state == UIGestureRecognizerStateBegan) {
self.attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.redView attachedToAnchor:location];
[self.animator addBehavior:self.attachmentBehavior];
} else if (pan.state == UIGestureRecognizerStateChanged) {
self.attachmentBehavior.anchorPoint = location;
} else if (pan.state == UIGestureRecognizerStateEnded) {
[self.animator removeBehavior:self.attachmentBehavior];
}
}
效果:
4. UIPushBehavior(推动行为)
- 构造
// 根据动力元素组和推动类型构造 - (instancetype)initWithItems:(NSArray
> *)items mode:(UIPushBehaviorMode)mode NS_DESIGNATED_INITIALIZER; typedef NS_ENUM(NSInteger, UIPushBehaviorMode) { UIPushBehaviorModeContinuous, // 连续的推动 UIPushBehaviorModeInstantaneous // 瞬间的推动 } NS_ENUM_AVAILABLE_IOS(7_0); // 添加动力元素 - (void)addItem:(id )item; // 删除动力元素 - (void)removeItem:(id )item; // 获取所有动力元素 @property (nonatomic, readonly, copy) NSArray > *items; - 作用力中心偏移量
- (UIOffset)targetOffsetFromCenterForItem:(id
)item; - (void)setTargetOffsetFromCenter:(UIOffset)o forItem:(id )item; - 设置推力
// 推力方向 @property (readwrite, nonatomic) CGVector pushDirection; // 推力角度 @property (readwrite, nonatomic) CGFloat angle; // 推力矢量的大小 @property (readwrite, nonatomic) CGFloat magnitude; // 设置推力的角度和推力矢量的大小 -(void)setAngle:(CGFloat)angle magnitude:(CGFloat)magnitude;
- 推动行为是否处于活跃状态
在添加一个pushBehavior
到animator
时,使用这个属性来激活或禁用推力作用,而不是通过重新添加behavior
来实现。@property (nonatomic, readwrite) BOOL active;
实例一: 瞬间推动UIPushBehaviorModeInstantaneous
UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.redView] mode:UIPushBehaviorModeInstantaneous];
pushBehavior.pushDirection = CGVectorMake(0.5, 0.5);
[self.animator addBehavior:pushBehavior];
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.redView]];
collision.collisionMode = UICollisionBehaviorModeEverything;
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];
效果:
实例二: 连续推动
UIPushBehaviorModeContinuous
UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.redView] mode:UIPushBehaviorModeContinuous];
pushBehavior.pushDirection = CGVectorMake(0.5, 0.5);
[self.animator addBehavior:pushBehavior];
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.redView]];
collision.collisionMode = UICollisionBehaviorModeEverything;
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];
效果:
实例三: 作用力中心偏移
UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.redView] mode:UIPushBehaviorModeInstantaneous];
pushBehavior.pushDirection = CGVectorMake(0.5, 0.5);
[pushBehavior setTargetOffsetFromCenter:UIOffsetMake(-25, 0) forItem:self.redView];
[self.animator addBehavior:pushBehavior];
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.redView]];
collision.collisionMode = UICollisionBehaviorModeEverything;
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];
效果:
5. UIDynamicItemBehavior (动力行为)
因为可以设置摩擦力、弹力、密度、阻力等参数,在模拟视图运动的能量损失。
- 构造
// 根据动力元素组构造 - (instancetype)initWithItems:(NSArray
> *)items NS_DESIGNATED_INITIALIZER; // 添加动力元素 - (void)addItem:(id )item; // 删除动力元素 - (void)removeItem:(id )item; // 获取所有动力元素 @property (nonatomic, readonly, copy) NSArray > *items; - 弹性系数
用于碰撞行为的动态元素的弹性量。默认0,范围0.0 ~ 1.0。@property (readwrite, nonatomic) CGFloat elasticity;
- 摩擦系数
用于两个发生摩擦的动态元素。默认值0.0(没有摩擦),当值为1.0时,强烈摩擦。如果设置更高的摩擦,可以使用更高的数值。@property (readwrite, nonatomic) CGFloat friction;
- 相对质量密度
用于动态元素相对密度。其连同动态元素大小,决定动态元素的有效质量。其参与的动力学行为包括摩擦、碰撞、推动等...默认1。@property (readwrite, nonatomic) CGFloat density;
假设你有两个具有相同密度但大小不同的动态元素:元素一尺寸为100x100像素点,元素二尺寸为100x200像素点。
这个例子中,元素二的有效质量是元素一的两倍。
在一个弹性碰撞中,这些元素根据它们的相对质量表现出自然的动量守恒。元素一密度为1.0,当施加一个力(通过推动行为)1.0级时,加速度为100点/s²。 - 线速度阻尼
默认值是0.0。有效范围从0.0(没有速度阻尼)到CGFLOAT_MAX(最大速度阻尼)。当设置为1.0,动态元素会立马停止就像没有力量作用于它一样。@property (readwrite, nonatomic) CGFloat resistance;
- 角速度阻尼
有效范围从0.0到CGFLOAT_MAX,值越大,角速度阻尼越大,旋转减速越快,到停止。@property (readwrite, nonatomic) CGFloat angularResistance;
- 电荷
电荷数确定动态元素与电场和磁场相互作用的程度。这个属性值没有单位,电磁场强度由你调控的适当的值来决定。默认值0.0。@property (readwrite, nonatomic) CGFloat charge NS_AVAILABLE_IOS(9_0);
- 是否固定
当一个动态元素被设置为固定后,该元素参与碰撞,但不受碰撞影响,仿佛成为一个碰撞边界。默认值为NO。@property (nonatomic, getter = isAnchored) BOOL anchored NS_AVAILABLE_IOS(9_0);
- 添加一个动态元素,并设置它的角速度
默认值为0.0,单位弧度/秒。设置一个负值,减少一定角速度。-(void)addAngularVelocity:(CGFloat)velocity forItem:(id
)item; - 添加一个动态元素,并设置它的线速度
默认值为0.0,单位点/秒。设置一个负值,减少一定线速度。-(void)addLinearVelocity:(CGPoint)velocity forItem:(id
)item; - 获得动态元素的角速度
-(CGFloat)angularVelocityForItem:(id
)item; - 获得动态元素的线速度
-(CGPoint)linearVelocityForItem:(id
)item;
实例:
- (void)addDynamicItem {
UIDynamicItemBehavior *dynamicItem = [[UIDynamicItemBehavior alloc] initWithItems:@[self.redView]];
dynamicItem.elasticity = 0.5f; // 弹性系数
dynamicItem.friction = 1.0f; // 摩擦系数
dynamicItem.density = 10.0f; // 相对质量密度
dynamicItem.resistance = 10.0f; // 线性阻尼
dynamicItem.angularResistance = 10.0f; // 角度阻尼
[self.animator addBehavior:dynamicItem];
// 推动行为
UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.redView] mode:UIPushBehaviorModeInstantaneous];
pushBehavior.pushDirection = CGVectorMake(0.5, 0);
pushBehavior.magnitude = 20;
[pushBehavior setTargetOffsetFromCenter:UIOffsetMake(0, -25) forItem:self.redView];
[self.animator addBehavior:pushBehavior];
// 碰撞行为
UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.redView]];
collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collisionBehavior];
}
效果:
6. UISnapBehavior (捕获行为)
捕获行为是定义一个动态元素运动到指定点,运动过程伴随弹簧效果。
- 构造
- (instancetype)initWithItem:(id
)item snapToPoint:(CGPoint)point NS_DESIGNATED_INITIALIZER; - 捕获点
该属性的默认值为initWithItem:snapToPoint:
方法设置的值,当该属性的值发生改变时,捕获行为会更新,动态元素会向新的捕获点捕获。@property (nonatomic, assign) CGPoint snapPoint NS_AVAILABLE_IOS(9_0);
- 震动阻尼
阻尼的有效范围为0.0~1.0,0.0最大震荡、1.0最小震荡,默认值为0.5。@property (nonatomic, assign) CGFloat damping;
实例:
UISnapBehavior *snapBehavior = [[UISnapBehavior alloc] initWithItem:self.redView snapToPoint:CGPointMake(200, 300)];
snapBehavior.damping = 0.2f;
[self.animator addBehavior:snapBehavior];
效果: