UIDynamic物理引擎

概述


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

詳細(xì)


一、基本知識(shí)

UIDynamic可以為繼承UIView的控件添加物理行為。可以看下這些API
  • Dynamic Animator 動(dòng)畫者,為動(dòng)力學(xué)元素提供物理學(xué)相關(guān)的能力及動(dòng)畫,同時(shí)為這些元素提供相關(guān)的上下文,是動(dòng)力學(xué)元素與底層iOS物理引擎之間的中介,將Behavior對(duì)象添加到Animator即可實(shí)現(xiàn)動(dòng)力仿真

  • Dynamic Animator Item:動(dòng)力學(xué)元素,是任何遵守了UIDynamic協(xié)議的對(duì)象,從iOS7開始,UIView和UICollectionViewLayoutAttributes默認(rèn)實(shí)現(xiàn)協(xié)議,如果自定義對(duì)象實(shí)現(xiàn)了該協(xié)議,即可通過Dynamic Animator實(shí)現(xiàn)物理仿真。

  • UIDynamicBehavior:仿真行為,是動(dòng)力學(xué)行為的父類,基本的動(dòng)力學(xué)行為類包括:

    • UIGravityBehavior 重力行為
    • UICollisionBehavior 碰撞行為
    • UIAttachmentBehavior 吸附行為
    • UISnapBehavior 迅猛移動(dòng)彈跳擺動(dòng)行為
    • UIPushBehavior 推動(dòng)行為
具體實(shí)現(xiàn)步驟:

1、創(chuàng)建一個(gè)仿真者[UIDynamicAnimator] 用來仿真所有的物理行為
2、創(chuàng)建具體的物理仿真行為[如重力UIGravityBehavior]
3、將物理仿真行為添加給仿真者實(shí)現(xiàn)仿真效果。

二、單行為效果

我這里簡(jiǎn)單寫幾個(gè)行為事例,其他創(chuàng)建方法基本和這一樣,可以自己嘗試:

1、重力效果:

// 創(chuàng)建一個(gè)仿真者[UIDynamicAnimator] 用來仿真物理行為
  UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

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

  // 將重力仿真行為添加給仿真者實(shí)現(xiàn)仿真效果,開始仿真
  [animator addBehavior:gravity];

具體效果:

Untitled.gif

2、碰撞效果:

是不是很簡(jiǎn)單,咱們?cè)賮硪粋€(gè)碰撞效果:

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

具體效果:

碰撞效果

3、擺動(dòng)效果:

// 創(chuàng)建震動(dòng)行為,snapPoint是它的作用點(diǎn)
    self.snap = [[UISnapBehavior alloc] initWithItem:view snapToPoint:view.center];

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

具體效果:

擺動(dòng)效果

單效果創(chuàng)建都差不多,這里只寫幾個(gè)簡(jiǎn)單的,其他的可以看我參考的這篇文章 http://www.lxweimin.com/p/e096d2dda478

三、組合行為效果

把單效果稍微組合一下:

1、重力加碰撞:

// 創(chuàng)建一個(gè)仿真者[UIDynamicAnimator] 用來仿真物理行為
  UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

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

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

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

具體效果:

重力加碰撞

2、重力加彈跳:


    // 動(dòng)態(tài)媒介
    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];
    
    // 動(dòng)力學(xué)屬性
    UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[square]];
    itemBehavior.elasticity = 1;
    [animator addBehavior:itemBehavior];

具體效果:

重力加彈跳,酷炫吧?

四、大廠用到的實(shí)際效果

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

1、防iMessage滾動(dòng)效果:

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


// 自定義UICollectionViewFlowLayout
@interface WZBCollectionViewLayout : UICollectionViewFlowLayout

// 重寫prepareLayout方法
- (void)prepareLayout
{
    [super prepareLayout];
    
    if (!_animator) {
        // 創(chuàng)建一個(gè)仿真者[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) {
            // 創(chuàng)建一個(gè)吸附行為
            UIAttachmentBehavior *spring = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:item.center];
            spring.length = 0;
            spring.damping = .8;
            spring.frequency = .5;
            [_animator addBehavior:spring];
        }
    }
}
    
// 重寫這個(gè)方法刷新布局
- (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滾動(dòng)效果

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

// 這里需要?jiǎng)?chuàng)建一個(gè)監(jiān)聽運(yùn)動(dòng)的管理者用來監(jiān)聽重力,然后實(shí)時(shí)改變重力方向
        _motionManager = [[CMMotionManager alloc] init];
        
        // 設(shè)備狀態(tài)更新幀率
        _motionManager.deviceMotionUpdateInterval = 0.01;
    // 創(chuàng)建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;
        // 添加動(dòng)力學(xué)屬性
        [self.dynamicItem addItem:imageView];
    }
    
    // 開始監(jiān)聽
    [self.motionManager startDeviceMotionUpdatesToQueue:NSOperationQueue.mainQueue withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
        // 設(shè)置重力方向
        self.gravity.gravityDirection = CGVectorMake(motion.gravity.x * 3, -motion.gravity.y * 3);
    }];

具體效果:

防摩拜單車貼紙效果

3、防百度外賣首頁(yè)重力感應(yīng):

         // 這里需要?jiǎng)?chuàng)建一個(gè)監(jiān)聽運(yùn)動(dòng)的管理者用來監(jiān)聽重力方向,然后實(shí)時(shí)改變UI
        _motionManager = [[CMMotionManager alloc] init];
        // 加速計(jì)更新頻率,我這里設(shè)置每隔0.06s更新一次,也就是說,每隔0.06s會(huì)調(diào)用一次下邊這個(gè)監(jiān)聽的block
    self.motionManager.accelerometerUpdateInterval = 0.06;
       // 開始監(jiān)聽
    [self.motionManager startAccelerometerUpdatesToQueue:NSOperationQueue.mainQueue withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {
        // 獲取加速計(jì)在x方向上的加速度
        CGFloat x = accelerometerData.acceleration.x;
        
        // collectionView的偏移量
        CGFloat offSetX = self.collectionView.contentOffset.x;
        CGFloat offSetY = self.collectionView.contentOffset.y;
        
        // 動(dòng)態(tài)修改偏移量
        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;
        }
        
        // 動(dòng)畫修改collectionView的偏移量
        [UIView animateWithDuration:0.06 animations:^{
            [self.collectionView setContentOffset:CGPointMake(offSetX, offSetY) animated:NO];
        }];
    }];

具體效果:

防百度外賣首頁(yè)重力感應(yīng)

總結(jié)


  • 本篇文章參考了
  • 最后幾個(gè)效果圖有點(diǎn)失真,具體效果可以找我要demo看
  • 最近急著招人,平時(shí)能擠出的時(shí)間也不多,所以這篇文章寫的比較粗糙,有任何疑問都可以私信我
  • 喜歡就點(diǎn)個(gè)贊吧

我的更多文章:老司機(jī)帶你飛

請(qǐng)不要吝惜,隨手點(diǎn)個(gè)喜歡或者關(guān)注一下吧!您的支持是我最大的動(dòng)力??!
您可以關(guān)注我以便及時(shí)查看我的最新文章,如果您對(duì)本篇文章有任何疑問,請(qǐng)隨時(shí)私信我。

碼字實(shí)在不易,可以請(qǐng)我喝杯咖啡嘛~ 期待的搓搓小手??


微信
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,654評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容

  • 概念介紹 UIDynamic從ios7才開始有的,其他2D仿真引擎:BOX2D:C語(yǔ)言框架,免費(fèi)Chipmunk:...
    我是滕先生閱讀 2,263評(píng)論 5 23
  • 本文中所有代碼演示均有GitHub源碼,點(diǎn)擊下載 UIDynamic簡(jiǎn)介 簡(jiǎn)介:UIKit動(dòng)力學(xué)最大的特點(diǎn)是將現(xiàn)實(shí)...
    si1ence閱讀 10,255評(píng)論 8 79
  • 最近產(chǎn)品提了個(gè)需求(電商的APP-兩鮮),需要在APP背景加上幾個(gè)水果圖案在那里無規(guī)則緩慢游蕩。。。模仿 天天果園...
    Yan青天閱讀 2,540評(píng)論 5 18
  • ————————————【 iOS—UIDynamic 】————————————— 一、簡(jiǎn)單介紹 1.什么是UI...
    北辰青閱讀 1,672評(píng)論 0 1
  • 古典老師在《你的身份,被什么決定》中,說到現(xiàn)在新職業(yè)價(jià)值體系是由圈子+能力+特色組成的,未來職業(yè)中只有這三種人:媒...
    smallfen閱讀 484評(píng)論 0 0