UIDynamic 物理引擎

概念介绍

  • UIDynamic从ios7才开始有的,其他2D仿真引擎:
    BOX2D:C语言框架,免费
    Chipmunk:C语言框架免费,其他版本收费(C#、Objective-C、Java)

  • 必须遵守了UIDynamicItem协议的控件才能应用这些行为,UIView遵守了,所以所有控件都可以使用

  • 使用步骤:创建一个动画者对象UIDynamicAnimator并设置坐标系,再添加一个动画行为对象(并设置动画作用的控件)

  • UIDynamic中的三个重要概念

  1. UIDynamicAnimator:动画者,为动力学元素提供物理学相关的能力及动画,同时为这些元素提供相关的上下文,是动力学元素与底层iOS物理引擎之间的中介,将Behavior对象添加到Animator即可实现动力仿真
  2. UIDynamicBehavior:仿真行为,是动力学行为的父类,基本的动力学行为类UIGravityBehavior、UICollisionBehavior、UIAttachmentBehavior、UISnapBehavior、UIPushBehavior以及UIDynamicItemBehavior均继承自该父类
  3. UIDynamicItem:动力学元素(动力项,就是应用行为的控件),是任何遵守了UIDynamicItem协议的对象,从iOS 7.0始,UIView和UICollectionViewLayoutAttributes默认实现该协议。如果自定义的对象实现了该协议,即可通过Dynamic Animator实现物理仿真

UIDynamicAnimator(动画者)

是动力行为(UIDynamicBehavior)的容器,添加到容器内的行为才会发挥作用
注意UIDynamicAnimator对象是否是强引用,可以创建一个强引用animator属性,进行懒加载

方法:

  1. 创建一个动画者并设置一个坐标系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,可以模拟多物体连接
注意:吸附行为重复添加的问题,建议懒加载行为对象

刚性附着和弹性附着

分为刚性附着和弹性附着
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(x2+y2) 求出
例子:

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;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 概述 最近群里有人私信我关于iOS物理引擎的知识,虽然UIDynamic在iOS7就引入了,但项目中还真没用到过,...
    杂雾无尘阅读 2,126评论 2 41
  • iOS 7增加了UIKit Dynamics库,其集成于UIKit框架中,将2D物理引擎引入了UIKit,提供了以...
    pro648阅读 2,759评论 2 14
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,490评论 18 139
  • 在看女生的篮球比赛时,我心中很是激动,因为很快就要开始进入高中的第一场比赛。曾经的我经历过很多场比赛也输给很多...
    80李萌阅读 238评论 1 4
  • 2017年2月5日,北京,万里晴空,从贵州寨子里带回来的烟熏腊肉正在阳台上享受阳光的洗礼,慢慢变干! 今年陪有盐先...
    有盐先生阅读 1,054评论 0 8