iOS开发——UI篇OC篇&UIDynamic详解

iOS开发拓展篇—UIDynamic(简单介绍)

一、简单介绍

1.什么是UIDynamic

UIDynamic是从iOS 7开始引入的一种新技术,隶属于UIKit框架

可以认为是一种物理引擎,能模拟和仿真现实生活中的物理现象

如:重力、弹性碰撞等现象

 

2.物理引擎的价值

广泛用于游戏开发,经典成功案例是“愤怒的小鸟”

让开发人员可以在远离物理学公式的情况下,实现炫酷的物理仿真效果

提高了游戏开发效率,产生更多优秀好玩的物理仿真游戏

 

3.知名的2D物理引擎

Box2d

Chipmunk

 

二、使用步骤

要想使用UIDynamic来实现物理仿真效果,大致的步骤如下

(1)创建一个物理仿真器(顺便设置仿真范围)

(2)创建相应的物理仿真行为(顺便添加物理仿真元素)

(3)将物理仿真行为添加到物理仿真器中  开始仿真

 

三、相关说明

1.三个概念

(1)谁要进行物理仿真?

  物理仿真元素(Dynamic Item)

 

(2)执行怎样的物理仿真效果?怎样的动画效果?

  物理仿真行为(Dynamic Behavior) 

 

(3)让物理仿真元素执行具体的物理仿真行为

  物理仿真器(Dynamic Animator)

 

2.物理仿真元素

注意:

不是任何对象都能做物理仿真元素

不是任何对象都能进行物理仿真

 

物理仿真元素要素:

任何遵守了UIDynamicItem协议的对象

UIView默认已经遵守了UIDynamicItem协议,因此任何UI控件都能做物理仿真

UICollectionViewLayoutAttributes类默认也遵守UIDynamicItem协议

 

3.物理仿真行为

(1)UIDynamic提供了以下几种物理仿真行为

UIGravityBehavior:重力行为

UICollisionBehavior:碰撞行为

UISnapBehavior:捕捉行为

UIPushBehavior:推动行为

UIAttachmentBehavior:附着行为

UIDynamicItemBehavior:动力元素行为

 

(2)物理仿真行为须知

上述所有物理仿真行为都继承自UIDynamicBehavior

所有的UIDynamicBehavior都可以独立进行

组合使用多种行为时,可以实现一些比较复杂的效果

 

 

4.物理仿真器

(1)物理仿真器须知

它可以让物理仿真元素执行物理仿真行为

它是UIDynamicAnimator类型的对象

 

(2)UIDynamicAnimator的初始化

- (instancetype)initWithReferenceView:(UIView *)view;

view参数:是一个参照视图,表示物理仿真的范围

 

5.物理仿真器的说明

(1)UIDynamicAnimator的常见方法

  - (void)addBehavior:(UIDynamicBehavior *)behavior;    //添加1个物理仿真行为

  - (void)removeBehavior:(UIDynamicBehavior *)behavior;  //移除1个物理仿真行为

  - (void)removeAllBehaviors;    //移除之前添加过的所有物理仿真行为

 

(2)UIDynamicAnimator的常见属性

  @property (nonatomic, readonly) UIView* referenceView;  //参照视图 

  @property (nonatomic, readonly, copy) NSArray* behaviors;//添加到物理仿真器中的所有物理仿真行为

  @property (nonatomic, readonly, getter = isRunning) BOOL running;//是否正在进行物理仿真

  @property (nonatomic, assign) id <UIDynamicAnimatorDelegate> delegate;//代理对象(能监听物理仿真器的仿真过程,比如开始和结束)

 

由于博客迁移至www.coderyi.com,文章请看http://www.coderyi.com/archives/426

UIkit动力学是UIkit框架中模拟真实世界的一些特性。

UIDynamicAnimator

主要有UIDynamicAnimator类,通过这个类中的不同行为来实现一些动态特性。

它一般有两种初始化方法,先讲常见的第一种

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

 动态特性的实现主要依靠它所添加的行为,通过以下方法进行添加和移除,

?
  1 [animator addBehavior:attachmentBehavior]; 2 [animator removeAllBehaviors]; 

 

 

接下来介绍五个不同的行为,UIAttachmentBehavior(吸附),UICollisionBehavior(碰撞),UIGravityBehavior(重力),UIPushBehavior(推动),UISnapBehavior(捕捉)。另外还有一个辅助的行为UIDynamicItemBehavior用来在item层级设定一些参数,比如item的摩擦,阻力,角阻力,弹性密度和可允许的旋转等等。

UIAttachmentBehavior(吸附)

先讲吸附行为,

它的初始化方法

?
  1 attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:iv 2 offsetFromCenter:centerOffset 3 attachedToAnchor:location]; 

item是实现UIDynamicItem协议的id类型,这里设置吸附一个UIImageView的实例iv。offset可以设置吸附的偏移,anchor是设置锚点。

 UIAttachmentBehavior有几个属性,例如dampingfrequencydamping是阻尼数值,frequency震动频率

直接上代码,实现一个pan手势,让一个image跟着手势跑

?
 1 -(void)gesture:(UIPanGestureRecognizer *)gesture{
 2     CGPoint location = [gesture locationInView:self.view];
 3     CGPoint boxLocation = [gesture locationInView:iv];
 4      
 5     switch (gesture.state) {
 6         case UIGestureRecognizerStateBegan:{
 7             NSLog(@"you touch started position %@",NSStringFromCGPoint(location));
 8             NSLog(@"location in image started is %@",NSStringFromCGPoint(boxLocation));
 9              
10             [animator removeAllBehaviors];
11              
12             // Create an attachment binding the anchor point (the finger's current location)
13             // to a certain position on the view (the offset)
14              
15             UIOffset centerOffset = UIOffsetMake(boxLocation.x - CGRectGetMidX(iv.bounds),
16                                                  boxLocation.y - CGRectGetMidY(iv.bounds));
17             attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:iv
18                                                                 offsetFromCenter:centerOffset
19                                                                 attachedToAnchor:location];
20          
21             attachmentBehavior.damping=0.5;
22             attachmentBehavior.frequency=0.8;
23              
24              
25             // Tell the animator to use this attachment behavior
26             [animator addBehavior:attachmentBehavior];
27             break;
28         }
29         case UIGestureRecognizerStateEnded: {
30                [animator removeBehavior:attachmentBehavior];
31             
32             break;
33         }
34         default:
35               [attachmentBehavior setAnchorPoint:[gesture locationInView:self.view]];
36             break;
37     }
38 }

 

 

 

UIPushBehavior(推动)

 UIPushBehavior 可以为一个UIView施加一个力的作用,这个力可以是持续的,也可以只是一个冲量。我们可以指定力的大小,方向和作用点等等信息。 

?
  1 pushBehavior = [[UIPushBehavior alloc] 2 initWithItems:@[iv] 3 mode:UIPushBehaviorModeInstantaneous]; 

UIPushBehavior pushDirectionmagnitude等属性,

?
 1 //1
 2             CGPoint velocity = [gesture velocityInView:self.view];
 3             CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
 4              
 5             if (magnitude > ThrowingThreshold) {
 6                 //2
 7                 pushBehavior = [[UIPushBehavior alloc]
 8                                                 initWithItems:@[iv]
 9                                                 mode:UIPushBehaviorModeInstantaneous];
10                 pushBehavior.pushDirection = CGVectorMake((velocity.x / 10) , (velocity.y / 10));
11                 pushBehavior.magnitude = magnitude / ThrowingvelocityPadding;
12                  
13                
14                 [animator addBehavior:pushBehavior];
15                  
16                 //3
17 //                UIDynamicItemBehavior 其实是一个辅助的行为,用来在item层级设定一些参数,比如item的摩擦,阻力,角阻力,弹性密度和可允许的旋转等等
18                 NSInteger angle = arc4random_uniform(20) - 10;
19                  
20                 itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[iv]];
21                 itemBehavior.friction = 0.2;
22                 itemBehavior.allowsRotation = YES;
23                 [itemBehavior addAngularVelocity:angle forItem:iv];
24                 [animator addBehavior:itemBehavior];
25                  
26                 //4
27                 [self performSelector:@selector(resetDemo) withObject:nil afterDelay:0.4];
28             }

 

 

UIGravityBehavior(重力)

 直接上代码,实现随机掉落一张图片的代码

   

?
 1  // Set up
 2     self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
 3      
 4     self.gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:nil];
 5      
 6    
 7      
 8      
 9     [self.animator addBehavior:self.gravityBeahvior];
10   
11   
12 - (void)tapped:(UITapGestureRecognizer *)gesture {
13      
14     NSUInteger num = arc4random() % 40 + 1;
15     NSString *filename = [NSString stringWithFormat:@"m%lu", (unsigned long)num];
16     UIImage *image = [UIImage imageNamed:filename];
17     UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
18     [self.view addSubview:imageView];
19      
20     CGPoint tappedPos = [gesture locationInView:gesture.view];
21     imageView.center = tappedPos;
22      
23     [self.gravityBeahvior addItem:imageView];
24   
25 }

 

 

UICollisionBehavior(碰撞)

继续上面的代码,当图片快掉落出边界的时候有 碰撞效果,这个就是UICollisionBehavior实现的。

  

?
 1 // Set up
 2     self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
 3      
 4     self.gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:nil];
 5      
 6     self.collisionBehavior = [[UICollisionBehavior alloc] initWithItems:nil];
 7     self.collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
 8      
 9     self.itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:nil];
10     self.itemBehavior.elasticity = 0.6;
11     self.itemBehavior.friction = 0.5;
12     self.itemBehavior.resistance = 0.5;
13      
14      
15     [self.animator addBehavior:self.gravityBeahvior];
16     [self.animator addBehavior:self.collisionBehavior];
17     [self.animator addBehavior:self.itemBehavior];
18   
19   
20   
21 - (void)tapped:(UITapGestureRecognizer *)gesture {
22      
23     NSUInteger num = arc4random() % 40 + 1;
24     NSString *filename = [NSString stringWithFormat:@"m%lu", (unsigned long)num];
25     UIImage *image = [UIImage imageNamed:filename];
26     UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
27     [self.view addSubview:imageView];
28      
29     CGPoint tappedPos = [gesture locationInView:gesture.view];
30     imageView.center = tappedPos;
31      
32     [self.gravityBeahvior addItem:imageView];
33     [self.collisionBehavior addItem:imageView];
34     [self.itemBehavior addItem:imageView];
35 }

 

另外,UICollisionBehavior有它的代理,其中列举两个方法,它们表示行为开始和结束的时候的代理。

?
1 - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier atPoint:(CGPoint)p;
2 - (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier;

 

 

UISnapBehavior(捕捉)

UISnapBehavior UIView通过动画吸附到某个点上。

?
 1 - (void) handleTap:(UITapGestureRecognizer *)paramTap{
 2     CGPoint tapPoint = [paramTap locationInView:self.view];
 3      
 4     if (self.snapBehavior != nil){
 5         [self.animator removeBehavior:self.snapBehavior];
 6     }
 7     self.snapBehavior = [[UISnapBehavior alloc] initWithItem:self.squareView snapToPoint:tapPoint];
 8     self.snapBehavior.damping = 0.5f;  //剧列程度
 9     [self.animator addBehavior:self.snapBehavior];
10 }

 

 

UICollectionView与UIDynamicAnimator

文章开头说到UIDynamicAnimator有两种初始化方法,这里介绍它与UICollectionView的完美结合,让UICollectionView产生各种动态特性的行为。

你是否记得iOS系统中信息应用中的附有弹性的消息列表,他就是加入了UIAttachmentBehavior吸附行为,这里通过一个UICollectionView实现类似效果。

主要是复写UICollectionViewFlowLayout,在layout中为每一个布局属性元素加上吸附行为就可以了。

关于复写layout,可以参考onevcat的博客

http://www.onevcat.com/2012/08/advanced-collection-view/

下面就直接上代码了

首先遍历每个 collection view layout attribute 来创建和添加新的 dynamic animator

?
 1 -(void)prepareLayout {
 2     [super prepareLayout];
 3      
 4     if (!_animator) {
 5         _animator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self];
 6         CGSize contentSize = [self collectionViewContentSize];
 7         NSArray *items = [super layoutAttributesForElementsInRect:CGRectMake(0, 0, contentSize.width, contentSize.height)];
 8          
 9         for (UICollectionViewLayoutAttributes *item in items) {
10             UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:item.center];
11              
12             attachment.length = 0;
13             attachment.damping = self.damping;
14             attachment.frequency = self.frequency;
15              
16             [_animator addBehavior:attachment];
17         }
18     }
19 }

 

 

 

接下来我们现在需要实现 layoutAttributesForElementsInRect: layoutAttributesForItemAtIndexPath: 这两个方法,UIKit 会调用它们来询问 collection view 每一个 item 的布局信息。我们写的代码会把这些查询交给专门做这些事的 dynamic animator

?
1 -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
2     return [_animator itemsInRect:rect];
3 }
4   
5 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
6     return [_animator layoutAttributesForCellAtIndexPath:indexPath];
7 }

 

 

 

然后是响应滚动事件的方法

这个方法会在 collection view bound 发生改变的时候被调用,根据最新的 content offset 调整我们的 dynamic animator 中的 behaviors 的参数。在重新调整这些 behavior item 之后,我们在这个方法中返回 NO;因为 dynamic animator 会关心 layout 的无效问题,所以在这种情况下,它不需要去主动使其无效

?
 1 -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
 2     UIScrollView *scrollView = self.collectionView;
 3     CGFloat scrollDelta = newBounds.origin.y - scrollView.bounds.origin.y;
 4     NSLog(@" %f   %f",newBounds.origin.y,scrollView.bounds.origin.y);
 5     CGPoint touchLocation = [scrollView.panGestureRecognizer locationInView:scrollView];
 6      
 7     for (UIAttachmentBehavior *behavior in _animator.behaviors) {
 8          
 9         CGPoint anchorPoint = behavior.anchorPoint;
10         CGFloat distanceFromTouch = fabsf(touchLocation.y - anchorPoint.y);
11         CGFloat scrollResistance = distanceFromTouch / self.resistanceFactor;
12          
13         UICollectionViewLayoutAttributes *item = [behavior.items firstObject];
14         CGPoint center = item.center;
15         center.y += (scrollDelta > 0) ? MIN(scrollDelta, scrollDelta * scrollResistance)
16         : MAX(scrollDelta, scrollDelta * scrollResistance);
17         item.center = center;
18          
19         [_animator updateItemUsingCurrentState:item];
20     }
21     return NO;
22 }

 

 

 

 让我们仔细查看这个代码的细节。首先我们得到了这个 scroll view(就是我们的 collection view ),然后计算它的 content offset  y 的变化(在这个例子中,我们的 collection view 是垂直滑动的)。一旦我们得到这个增量,我们需要得到用户接触的位置。这是非常重要的,因为我们希望离接触位置比较近的那些物体能移动地更迅速些,而离接触位置比较远的那些物体则应该滞后些。

对于 dynamic animator 中的每个 behavior,我们将接触点到该 behavior 物体的  y 的距离除以 500。分母越小,这个 collection view 的的交互就越有弹簧的感觉。

 

你可能感兴趣的:(dynamic)