ios UIKit動力

UIkit動力學是UIkit框架中模擬真實世界的一些特性。

UIDynamicAnimator

主要有UIDynamicAnimator類,通過這個類中的不同行爲來實現一些動態特性。

它一般有兩種初始化方法,先講常見的第一種

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

 動態特性的實現主要依靠它所添加的行爲,通過以下方法進行添加和移除,

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



接下來介紹五個不同的行爲,UIAttachmentBehavior(吸附),UICollisionBehavior(碰撞),UIGravityBehavior(重力),UIPushBehavior(推動),UISnapBehavior(捕捉)。另外還有一個輔助的行爲UIDynamicItemBehavior用來在item層級設定一些參數,比如item的摩擦,阻力,角阻力,彈性密度和可允許的旋轉等等。

UIAttachmentBehavior(吸附)

先講吸附行爲,

它的初始化方法

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

item是實現UIDynamicItem協議的id類型,這裏設置吸附一個UIImageView的實例iv。offset可以設置吸附的偏移,anchor是設置錨點。

 UIAttachmentBehavior有幾個屬性,例如dampingfrequencydamping是阻尼數值,frequency震動頻率

直接上代碼,實現一個pan手勢,讓一個image跟着手勢跑

?
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
-(void)gesture:(UIPanGestureRecognizer *)gesture{
    CGPoint location = [gesture locationInView:self.view];
    CGPoint boxLocation = [gesture locationInView:iv];
     
    switch (gesture.state) {
        case UIGestureRecognizerStateBegan:{
            NSLog(@"you touch started position %@",NSStringFromCGPoint(location));
            NSLog(@"location in image started is %@",NSStringFromCGPoint(boxLocation));
             
            [animator removeAllBehaviors];
             
            // Create an attachment binding the anchor point (the finger's current location)
            // to a certain position on the view (the offset)
             
            UIOffset centerOffset = UIOffsetMake(boxLocation.x - CGRectGetMidX(iv.bounds),
                                                 boxLocation.y - CGRectGetMidY(iv.bounds));
            attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:iv
                                                                offsetFromCenter:centerOffset
                                                                attachedToAnchor:location];
         
            attachmentBehavior.damping=0.5;
            attachmentBehavior.frequency=0.8;
             
             
            // Tell the animator to use this attachment behavior
            [animator addBehavior:attachmentBehavior];
            break;
        }
        case UIGestureRecognizerStateEnded: {
               [animator removeBehavior:attachmentBehavior];
            
            break;
        }
        default:
              [attachmentBehavior setAnchorPoint:[gesture locationInView:self.view]];
            break;
    }
}


UIPushBehavior(推動)

 UIPushBehavior 可以爲一個UIView施加一個力的作用,這個力可以是持續的,也可以只是一個衝量。我們可以指定力的大小,方向和作用點等等信息。 

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

UIPushBehavior pushDirectionmagnitude等屬性,

?
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
//1
            CGPoint velocity = [gesture velocityInView:self.view];
            CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
             
            if (magnitude > ThrowingThreshold) {
                //2
                pushBehavior = [[UIPushBehavior alloc]
                                                initWithItems:@[iv]
                                                mode:UIPushBehaviorModeInstantaneous];
                pushBehavior.pushDirection = CGVectorMake((velocity.x / 10) , (velocity.y / 10));
                pushBehavior.magnitude = magnitude / ThrowingvelocityPadding;
                 
               
                [animator addBehavior:pushBehavior];
                 
                //3
//                UIDynamicItemBehavior 其實是一個輔助的行爲,用來在item層級設定一些參數,比如item的摩擦,阻力,角阻力,彈性密度和可允許的旋轉等等
                NSInteger angle = arc4random_uniform(20) - 10;
                 
                itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[iv]];
                itemBehavior.friction = 0.2;
                itemBehavior.allowsRotation = YES;
                [itemBehavior addAngularVelocity:angle forItem:iv];
                [animator addBehavior:itemBehavior];
                 
                //4
                [self performSelector:@selector(resetDemo) withObject:nil afterDelay:0.4];
            }

 

UIGravityBehavior(重力)

 直接上代碼,實現隨機掉落一張圖片的代碼

   

?
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
 // Set up
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
     
    self.gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:nil];
     
   
     
     
    [self.animator addBehavior:self.gravityBeahvior];
  
  
- (void)tapped:(UITapGestureRecognizer *)gesture {
     
    NSUInteger num = arc4random() % 40 + 1;
    NSString *filename = [NSString stringWithFormat:@"m%lu", (unsigned long)num];
    UIImage *image = [UIImage imageNamed:filename];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    [self.view addSubview:imageView];
     
    CGPoint tappedPos = [gesture locationInView:gesture.view];
    imageView.center = tappedPos;
     
    [self.gravityBeahvior addItem:imageView];
  
}


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
30
31
32
33
34
35
// Set up
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
     
    self.gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:nil];
     
    self.collisionBehavior = [[UICollisionBehavior alloc] initWithItems:nil];
    self.collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
     
    self.itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:nil];
    self.itemBehavior.elasticity = 0.6;
    self.itemBehavior.friction = 0.5;
    self.itemBehavior.resistance = 0.5;
     
     
    [self.animator addBehavior:self.gravityBeahvior];
    [self.animator addBehavior:self.collisionBehavior];
    [self.animator addBehavior:self.itemBehavior];
  
  
  
- (void)tapped:(UITapGestureRecognizer *)gesture {
     
    NSUInteger num = arc4random() % 40 + 1;
    NSString *filename = [NSString stringWithFormat:@"m%lu", (unsigned long)num];
    UIImage *image = [UIImage imageNamed:filename];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    [self.view addSubview:imageView];
     
    CGPoint tappedPos = [gesture locationInView:gesture.view];
    imageView.center = tappedPos;
     
    [self.gravityBeahvior addItem:imageView];
    [self.collisionBehavior addItem:imageView];
    [self.itemBehavior addItem:imageView];
}

另外,UICollisionBehavior有它的代理,其中列舉兩個方法,它們表示行爲開始和結束的時候的代理。

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


UISnapBehavior(捕捉)

UISnapBehavior UIView通過動畫吸附到某個點上。

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

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


 

接下來我們現在需要實現 layoutAttributesForElementsInRect:  layoutAttributesForItemAtIndexPath: 這兩個方法,UIKit 會調用它們來詢問 collection view 每一個 item 的佈局信息。我們寫的代碼會把這些查詢交給專門做這些事的dynamic animator

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


 

然後是響應滾動事件的方法

這個方法會在 collection view  bound 發生改變的時候被調用,根據最新的 content offset 調整我們的 dynamic animator 中的 behaviors 的參數。在重新調整這些 behavior  item 之後,我們在這個方法中返回 NO;因爲 dynamic animator 會關心 layout 的無效問題,所以在這種情況下,它不需要去主動使其無效

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


 讓我們仔細查看這個代碼的細節。首先我們得到了這個 scroll view(就是我們的 collection view ),然後計算它的 content offset  y 的變化(在這個例子中,我們的 collection view 是垂直滑動的)。一旦我們得到這個增量,我們需要得到用戶接觸的位置。這是非常重要的,因爲我們希望離接觸位置比較近的那些物體能移動地更迅速些,而離接觸位置比較遠的那些物體則應該滯後些。

對於 dynamic animator 中的每個 behavior,我們將接觸點到該 behavior 物體的  y 的距離除以 500。分母越小,這個 collection view 的的交互就越有彈簧的感覺。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章