首页 iOS开发:物理引擎UIKit Dynamics
文章
取消

iOS开发:物理引擎UIKit Dynamics

UIKit Dynamics 的核心是物理引擎,它使用力学公式来模拟真实世界中的物体运动和相互作用。在 iOS 应用中,这些力学公式被用于控制视图的位置、速度、加速度和旋转等属性,从而实现更加逼真的用户交互体验。

UIKit Dynamics 提供了多种类型的动画效果,包括重力、碰撞、吸附、推动和捕捉等。这些动画效果通常会被组合在一起,以创建一个复杂的物理场景,其中不同的元素可以相互作用和影响。

基础知识

在开始使用UIKit Dynamics之前,我们需要了解一些基础概念:

  • 动画区域(Animation Regions):用于定义一个区域,可以用于进行碰撞检测、触摸事件响应等操作。
  • 动态解释器(Dynamic Animators):是指用于管理动态行为的对象,可以添加、移除或修改动态行为。
  • 物理对象(Dynamic Items):UIDynamicItem是一个协议,表示任何可以被物理引擎处理的对象。我们可以把它看作是物理世界里的物体。每个Dynamic item都必须符合UIDynamicItem协议,并且像一个UIView一样有一个frame属性。
  • 动态行为(Dynamic Behaviors):UIDynamicBehavior是描述物理特性的抽象类。我们可以把它看作是一种力,比如重力、碰撞、吸附等。用于指导dynamic animator 如何处理 dynamic items 的行为。
  • 行为属性(Behavior Properties):是指通过设置不同的属性值,可以调整动态行为的表现方式,比如重力方向、阻尼系数等。

UIDynamicAnimator

UIDynamicAnimator 是所有动态解释器的基类,用于创建和管理动态行为。它提供了一个全局的物理环境,并支持多个 UIDynamicBehavior 的组合。我们可以通过添加行为来控制动画的运动轨迹和速度。创建动态解释器时,我们需要将其关联到一个包含物理对象的容器视图上,并指定代理对象。

以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
// 创建一个UIDynamicAnimator对象
UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

// 创建一个重力行为
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[myView]];

// 将重力行为添加到animator中
[animator addBehavior:gravityBehavior];

Dynamic Items

Dynamic Items 用于描述动态对象的属性和行为。它由三个类组成:UIDynamicItem、UIDynamicItemBehavior、UIDynamicItemGroup。

UIDynamicItem

UIDynamicItem是一个协议,表示任何可以被物理引擎处理的对象。我们可以把它看作是物理世界里的物体。每个Dynamic item都必须符合UIDynamicItem协议,并且像一个UIView一样有一个frame属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface MyDynamicItem : NSObject <UIDynamicItem>

@property (nonatomic) CGRect bounds;
@property (nonatomic) CGPoint center;

@end

MyDynamicItem *item = [[MyDynamicItem alloc] init];
item.bounds = CGRectMake(0, 0, 50, 50);
item.center = CGPointMake(100, 100);

UIView *view = [[UIView alloc] initWithFrame:item.bounds];
view.center = item.center;
view.backgroundColor = [UIColor redColor];
[self.view addSubview:view];

UIDynamicItemBehavior

UIDynamicItemBehavior 用于控制动态项目的物理行为。它可以给Dynamic item施加各种物理效应,设置动态项目的质量、阻力、弹性等属性。

1
2
3
4
5
6
7
UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[view]];
itemBehavior.elasticity = 0.5;
itemBehavior.resistance = 0.2;
itemBehavior.friction = 0.5;
itemBehavior.angularResistance = 0.5;
[itemBehavior addAngularVelocity:1 forItem:view];
[self.animator addBehavior:itemBehavior];

UIDynamicItemGroup

UIDynamicItemGroup 可以将多个Dynamic item打包成一个整体进行处理。如果我们想在同一时间对多个Dynamic item施加相同的物理效应,就可以使用UIDynamicItemGroup

1
2
3
UIDynamicItemGroup *group = [[UIDynamicItemGroup alloc] initWithItems:@[view1, view2]];
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[group]];
[self.animator addBehavior:gravityBehavior];

Behaviors

Behaviors 是 UIKit Dynamics 的另一个重要组件,用于描述动态对象之间的相互作用和约束。它由6个类组成:UICollisionBehavior、UIAttachmentBehavior、UIFieldBehavior、UIGravityBehavior、UISnapBehavior和UIPushBehavior。

UICollisionBehavior

UICollisionBehavior 用于模拟动态对象之间的碰撞效果。它可以设置碰撞范围、是否启用边缘检测、弹性系数、碰撞模式等属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 64 , self.view.frame.size.width, self.view.frame.size.width) cornerRadius:self.view.frame.size.width * 0.5];
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.fillColor = [UIColor yellowColor].CGColor;
shapeLayer.path = bezierPath.CGPath;
[self.view.layer addSublayer:shapeLayer];

    
UIImageView *img1 = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.frame.size.width - 50) * 0.5 - 25, 100, 50, 50)];
img1.backgroundColor = [UIColor redColor];
[self.view addSubview:img1];
    
UIImageView *img2 = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.frame.size.width - 50) * 0.5 + 25, 100, 50, 50)];
img2.backgroundColor = [UIColor blueColor];
[self.view addSubview:img2];
    
UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[img1,img2]];
    
UICollisionBehavior *collosionBehavior = [[UICollisionBehavior alloc] initWithItems:@[img1,img2]];
collosionBehavior.collisionMode = UICollisionBehaviorModeEverything;
    
//使用贝塞尔曲线 设置碰撞边界
[collosionBehavior addBoundaryWithIdentifier:@"bouns1" forPath:bezierPath];
    
// 设置碰撞行为的代理
collosionBehavior.collisionDelegate = self;
    
[animator addBehavior:gravityBehavior];
[animator addBehavior:collosionBehavior];

UIAttachmentBehavior

UIAttachmentBehavior 可以在Dynamic items之间创建弹簧连接。我们可以指定弹簧的长度、阻尼、振幅等属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
UIImageView *img1 = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.frame.size.width - 50) * 0.5 - 50, 100, 50, 50)];
img1.backgroundColor = [UIColor redColor];
[self.view addSubview:img1];

UIImageView *img2 = [[UIImageView alloc] initWithFrame:CGRectMake((self.view.frame.size.width - 50) * 0.5 + 25, 100+100, 50, 50)];
img2.backgroundColor = [UIColor blueColor];
[self.view addSubview:img2];

UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

UICollisionBehavior *collosionBehavior = [[UICollisionBehavior alloc] initWithItems:@[img1,img2]];
collosionBehavior.collisionMode = UICollisionBehaviorModeEverything;
collosionBehavior.translatesReferenceBoundsIntoBoundary = YES;

UIGravityBehavior *gravityBehavior1 = [[UIGravityBehavior alloc] initWithItems:@[img1]];
[gravityBehavior1 setGravityDirection:CGVectorMake(1, 1)];
UIGravityBehavior *gravityBehavior2 = [[UIGravityBehavior alloc] initWithItems:@[img2]];
[gravityBehavior2 setAngle:M_PI_2 magnitude:0.9];

UIAttachmentBehavior *attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:img1 offsetFromCenter:UIOffsetMake(25, 0) attachedToItem:img2 offsetFromCenter:UIOffsetMake(-25, 0)];
attachmentBehavior.length = 150;

[animator addBehavior:gravityBehavior1];
[animator addBehavior:gravityBehavior2];

[animator addBehavior:attachmentBehavior];
[animator addBehavior:collosionBehavior];

UIFieldBehavior

UIFieldBehavior 用于模拟物理场和力。它可以为动态项目提供各种类型的力,如引力、斥力等,我们可以指定物理场的类型、大小、方向等属性。

1
2
3
4
5
UIFieldBehavior *field = [[UIFieldBehavior alloc] initWithItems:@[view1]];
[field setStrength:1.0];
[field setFalloff:0.5];
[field setMinimumRadius:50];
[self.animator addBehavior:field];

UIGravityBehavior

UIGravityBehavior 用于模拟重力效果。它可以为动态项目提供向下的力,并可以控制重力的强度和方向。

1
2
3
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[view1]];
[gravity setMagnitude:1.0];
[self.animator addBehavior:gravity];

在上面的示例中,我们创建了一个 UIGravityBehavior 对象,并将其应用于一个 UIView。我们设置了重力的强度,并将 UIGravityBehavior 对象添加到 UIDynamicAnimator 中。

UIPushBehavior

UIPushBehavior 可以模拟物体受到推力(如手指推动物体)的情况,从而产生运动效果。我们可以指定推力的方向、大小等属性。例如:

1
2
3
4
UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[view] mode:UIPushBehaviorModeInstantaneous];
pushBehavior.pushDirection = CGVectorMake(1.0, 0.0);
self.pushBehavior.magnitude = 0.5;
[self.animator addBehavior:pushBehavior];

UISnapBehavior

UISnapBehavior 可以使物体从当前位置快速移动到新位置,并产生弹性效果。我们可以指定吸引的目标点、阻尼等属性。

1
2
3
UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:view1 snapToPoint:CGPointMake(150, 150)];
[snap setDamping:0.5];
[self.animator addBehavior:snap];

组合动画

下面是一个将上面多个效果组合运用的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) UIImageView *imgView1;
@property (nonatomic, strong) UIImageView *imgView2;
@property (nonatomic, strong) UIDynamicAnimator *animator;
@property (nonatomic, strong) UIGravityBehavior *gravityBehavior1;
@property (nonatomic, strong) UIGravityBehavior *gravityBehavior2;

@property (nonatomic, strong) UICollisionBehavior *collosionBehavior;
@property (nonatomic, strong) UIAttachmentBehavior *attachmentBehavior;
@property (nonatomic, strong) UIPushBehavior *pushBehavior;
@property (nonatomic, strong) UIDynamicItemBehavior *itemBehavior;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    // 创建一个UIDynamicAnimator对象
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    
    // 创建2个球体
    self.imgView1 = [[UIImageView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
    self.imgView1.backgroundColor = [UIColor redColor];
    self.imgView1.layer.cornerRadius = 50;
    [self.view addSubview:self.imgView1];
    
    self.imgView2 = [[UIImageView alloc] initWithFrame:CGRectMake(200, 400, 100, 100)];
    self.imgView2.backgroundColor = [UIColor redColor];
    self.imgView2.layer.cornerRadius = 50;
    [self.view addSubview:self.imgView2];
    
    // 创建重力行为
    self.gravityBehavior1 = [[UIGravityBehavior alloc] initWithItems:@[self.imgView1,self.imgView2]];
    [self.animator addBehavior:self.gravityBehavior1];
    
    // 创建碰撞行为
    self.collosionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.imgView1,self.imgView2]];
    self.collosionBehavior.translatesReferenceBoundsIntoBoundary = YES;
    [self.animator addBehavior:self.collosionBehavior];
    
    // 创建附着行为
    CGPoint anchorPoint = self.view.center;
    self.attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.imgView1 attachedToAnchor:anchorPoint];
    [self.animator addBehavior:self.attachmentBehavior];
    
    // 创建推力行为
    self.pushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.imgView1,self.imgView2] mode:UIPushBehaviorModeInstantaneous];
    [self.pushBehavior setMagnitude:10.0];
    [self.pushBehavior setAngle:M_PI_2];
    [self.animator addBehavior:self.pushBehavior];
    
    // 创建物体行为
    self.itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.imgView1,self.imgView2]];
    [self.itemBehavior setElasticity:0.5];
    [self.itemBehavior setDensity:2.0];
    [self.itemBehavior setResistance:0.5];
    [self.itemBehavior setAngularResistance:0.5];
    [self.itemBehavior setAllowsRotation:YES];
    [self.animator addBehavior:self.itemBehavior];
    
    
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self.view addGestureRecognizer:pan];
}
- (void)pan:(UIPanGestureRecognizer *)pan
{
    CGPoint point = [pan locationInView:self.view];
    CGPoint origin = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
    CGFloat distance = sqrtf(powf(point.x - origin.x, 2)) + powf(point.y - origin.y, 2);
    CGFloat angle = atan2(point.y - origin.y, point.x - origin.x);
    distance = MIN(distance, 100);
    
    [self.pushBehavior setMagnitude:distance / 100.0];
    [self.pushBehavior setAngle:angle];
    
    //指定元素的作用力点偏移量
    UIOffset offset = UIOffsetMake(1, 5);
    [self.pushBehavior setTargetOffsetFromCenter:offset forItem:self.imgView1];

    [self.pushBehavior setActive:YES];
}
@end
本文由作者按照 CC BY 4.0 进行授权

iOS开发:核心动画Core Animation

iOS开发:触摸事件和手势识别