UIDynamic物理引擎

CleBennetts 7年前發布 | 18K 次閱讀 UIDynamic iOS開發 移動開發

概述

最近群里有人私信我關于iOS物理引擎的知識,雖然UIDynamic在iOS7就引入了,但項目中還真沒用到過,就簡單研究了下。由于本demo很簡單,就沒有上傳GitHub,想要源碼的可以進文章底部的技術群獲取。

一、基本知識

UIDynamic可以為繼承UIView的控件添加物理行為。可以看下這些API

  • Dynamic Animator 動畫者,為動力學元素提供物理學相關的能力及動畫,同時為這些元素提供相關的上下文,是動力學元素與底層iOS物理引擎之間的中介,將Behavior對象添加到Animator即可實現動力仿真

  • Dynamic Animator Item:動力學元素,是任何遵守了UIDynamic協議的對象,從iOS7開始,UIView和UICollectionViewLayoutAttributes默認實現協議,如果自定義對象實現了該協議,即可通過Dynamic Animator實現物理仿真。

  • UIDynamicBehavior:仿真行為,是動力學行為的父類,基本的動力學行為類包括:

  • UIGravityBehavior 重力行為

  • UICollisionBehavior 碰撞行為

  • UIAttachmentBehavior 吸附行為

  • UISnapBehavior 迅猛移動彈跳擺動行為

  • UIPushBehavior 推動行為

具體實現步驟:

  1. 創建一個仿真者[UIDynamicAnimator] 用來仿真所有的物理行為

  2. 創建具體的物理仿真行為[如重力UIGravityBehavior]

  3. 將物理仿真行為添加給仿真者實現仿真效果。

二、單行為效果

我這里簡單寫幾個行為事例,其他創建方法基本和這一樣,可以自己嘗試:

1、重力效果:

// 創建一個仿真者[UIDynamicAnimator] 用來仿真物理行為
  UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

// 創建重力的物理仿真行為,并設置具體的items(需要仿真的view) UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[view]];

// 將重力仿真行為添加給仿真者實現仿真效果,開始仿真 [animator addBehavior:gravity];</pre>

具體效果:

2、碰撞效果:

是不是很簡單,咱們再來一個碰撞效果:

// 碰撞檢測
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[view]];
// 設置不要出邊界,碰到邊界會被反彈
collision.translatesReferenceBoundsIntoBoundary = YES;
// 開始仿真
[animator addBehavior:collision];

具體效果:

碰撞效果

3、擺動效果:

// 創建震動行為,snapPoint是它的作用點
    self.snap = [[UISnapBehavior alloc] initWithItem:view snapToPoint:view.center];

// 開始仿真 [animator addBehavior:collision];</pre>

具體效果:

擺動效果

單效果創建都差不多,這里只寫幾個簡單的,其他的可以看我參考的這篇文章 http://www.jianshu.com/p/e096d2dda478

三、組合行為效果

把單效果稍微組合一下:

1、重力加碰撞:

// 創建一個仿真者[UIDynamicAnimator] 用來仿真物理行為
  UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

// 創建重力的物理仿真行為,并設置具體的items(需要仿真的view) _gravity = [[UIGravityBehavior alloc] init]; _collision = [[UICollisionBehavior alloc] init]; _collision.translatesReferenceBoundsIntoBoundary = YES;

// 將重力仿真行為添加給仿真者實現仿真效果,開始仿真 [self.animator addBehavior:_gravity]; [self.animator addBehavior:_collision];

// 為view添加重力效果 [self.gravity addItem:view]; // 為view添加碰撞效果 [self.collision addItem:view];</pre>

具體效果:

重力加碰撞

2、重力加彈跳:

    // 動態媒介
    UIDynamicAnimator animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    [self.animators addObject:animator];
    // 重力
    UIGravityBehavior gravity = [[UIGravityBehavior alloc] initWithItems:@[square]];
    [animator addBehavior:gravity];

// 碰撞
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[square]];
collision.collisionDelegate = self;
[collision addBoundaryWithIdentifier:@"barrier" forPath:[UIBezierPath bezierPathWithRect:self.view.bounds]];
collision.translatesReferenceBoundsIntoBoundary = YES;
[animator addBehavior:collision];

// 動力學屬性
UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[square]];
itemBehavior.elasticity = 1;
[animator addBehavior:itemBehavior];</pre> 

具體效果:

重力加彈跳,酷炫吧?

四、大廠用到的實際效果

一些大廠在利用這些效果,比如蘋果的iMessage消息滾動視覺差效果、百度外賣重力感應(這個用到了重力感應)、摩拜單車貼紙效果,接下來咱們就逐個實現一下這些效果:

1、防iMessage滾動效果:

這里參考了著名開發者王維 @onevcat 重的一篇文章

// 自定義UICollectionViewFlowLayout
@interface WZBCollectionViewLayout : UICollectionViewFlowLayout

// 重寫prepareLayout方法
- (void)prepareLayout
{
    [super prepareLayout];

    if (!_animator) {
        // 創建一個仿真者[UIDynamicAnimator] 用來仿真物理行為
        _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 *spring = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:item.center];
            spring.length = 0;
            spring.damping = .8;
            spring.frequency = .5;
            [_animator addBehavior:spring];
        }
    }
}

// 重寫這個方法刷新布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    UIScrollView *scrollView = self.collectionView;
    CGFloat scrollDeltaY = newBounds.origin.y - scrollView.bounds.origin.y;
    CGFloat scrollDeltaX = newBounds.origin.x - scrollView.bounds.origin.x;
    CGPoint touchLocation = [scrollView.panGestureRecognizer locationInView:scrollView];
    for (UIAttachmentBehavior *spring in _animator.behaviors) {
        CGPoint anchorPoint = spring.anchorPoint;
        CGFloat distanceFromTouch = fabs(touchLocation.y - anchorPoint.y);
        CGFloat scrollResistance = distanceFromTouch / 2000;
        UICollectionViewLayoutAttributes *item = (id)[spring.items firstObject];
        CGPoint center = item.center;
        center.y += (scrollDeltaY > 0) ? MIN(scrollDeltaY, scrollDeltaY * scrollResistance)
        : MAX(scrollDeltaY, scrollDeltaY * scrollResistance);

        CGFloat distanceFromTouchX = fabs(touchLocation.x - anchorPoint.x);
        center.x += (scrollDeltaX > 0) ? MIN(scrollDeltaX, scrollDeltaX * distanceFromTouchX / 2000)
        : MAX(scrollDeltaX, scrollDeltaX * distanceFromTouchX / 2000);

        item.center = center;
        [_animator updateItemUsingCurrentState:item];
    }
    return NO;
}

具體效果:

防iMessage滾動效果

2、防摩拜單車貼紙效果:

// 這里需要創建一個監聽運動的管理者用來監聽重力,然后實時改變重力方向
        _motionManager = [[CMMotionManager alloc] init];

        // 設備狀態更新幀率
        _motionManager.deviceMotionUpdateInterval = 0.01;
    // 創建view
    for (NSInteger i = 0; i < 40; i++) {
        UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"MobikeTest"]];
        imageView.frame = CGRectMake(100, 0, 50, 50);
        imageView.layer.masksToBounds = YES;
        imageView.layer.cornerRadius = 25;
        [self.view addSubview:imageView];

        // 添加重力效果
        [self.gravity addItem:imageView];
        // 碰撞效果
        [self.collision addItem:imageView];
        self.dynamicItem.elasticity = .7;
        // 添加動力學屬性
        [self.dynamicItem addItem:imageView];
    }

    // 開始監聽
    [self.motionManager startDeviceMotionUpdatesToQueue:NSOperationQueue.mainQueue withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
        // 設置重力方向
        self.gravity.gravityDirection = CGVectorMake(motion.gravity.x * 3, -motion.gravity.y * 3);
    }];

具體效果:

防摩拜單車貼紙效果

3、防百度外賣首頁重力感應:

         // 這里需要創建一個監聽運動的管理者用來監聽重力方向,然后實時改變UI
        _motionManager = [[CMMotionManager alloc] init];
        // 加速計更新頻率,我這里設置每隔0.06s更新一次,也就是說,每隔0.06s會調用一次下邊這個監聽的block
    self.motionManager.accelerometerUpdateInterval = 0.06;
       // 開始監聽
    [self.motionManager startAccelerometerUpdatesToQueue:NSOperationQueue.mainQueue withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {
        // 獲取加速計在x方向上的加速度
        CGFloat x = accelerometerData.acceleration.x;

        // collectionView的偏移量
        CGFloat offSetX = self.collectionView.contentOffset.x;
        CGFloat offSetY = self.collectionView.contentOffset.y;

        // 動態修改偏移量
        offSetX -= 15 * x;
        CGFloat maxOffset = self.collectionView.contentSize.width + 15 - self.view.frame.size.width;

        // 判斷最大和最小的偏移量
        if (offSetX > maxOffset) {
            offSetX = maxOffset;
        } else if (offSetX < -15) {
            offSetX = -15;
        }

        // 動畫修改collectionView的偏移量
        [UIView animateWithDuration:0.06 animations:^{
            [self.collectionView setContentOffset:CGPointMake(offSetX, offSetY) animated:NO];
        }];
    }];

具體效果:

防百度外賣首頁重力感應

總結

  • 本篇文章參考了

  • 最后幾個效果圖有點失真,具體效果可以找我要demo看

來自:http://www.jianshu.com/p/bed8f94c204d

 

 本文由用戶 CleBennetts 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!