UIDynamic物理引擎

CleBennetts 7年前
   <h2>概述</h2>    <p>最近群里有人私信我关于iOS物理引擎的知识,虽然UIDynamic在iOS7就引入了,但项目中还真没用到过,就简单研究了下。由于本demo很简单,就没有上传GitHub,想要源码的可以进文章底部的技术群获取。</p>    <h2>一、基本知识</h2>    <p>UIDynamic可以为继承UIView的控件添加物理行为。可以看下这些API</p>    <ul>     <li> <p>Dynamic Animator 动画者,为动力学元素提供物理学相关的能力及动画,同时为这些元素提供相关的上下文,是动力学元素与底层iOS物理引擎之间的中介,将Behavior对象添加到Animator即可实现动力仿真</p> </li>     <li> <p>Dynamic Animator Item:动力学元素,是任何遵守了UIDynamic协议的对象,从iOS7开始,UIView和UICollectionViewLayoutAttributes默认实现协议,如果自定义对象实现了该协议,即可通过Dynamic Animator实现物理仿真。</p> </li>     <li> <p>UIDynamicBehavior:仿真行为,是动力学行为的父类,基本的动力学行为类包括:</p> </li>    </ul>    <ul>     <li> <p>UIGravityBehavior 重力行为</p> </li>     <li> <p>UICollisionBehavior 碰撞行为</p> </li>     <li> <p>UIAttachmentBehavior 吸附行为</p> </li>     <li> <p>UISnapBehavior 迅猛移动弹跳摆动行为</p> </li>     <li> <p>UIPushBehavior 推动行为</p> </li>    </ul>    <p>具体实现步骤:</p>    <ol>     <li> <p>创建一个仿真者[UIDynamicAnimator] 用来仿真所有的物理行为</p> </li>     <li> <p>创建具体的物理仿真行为[如重力UIGravityBehavior]</p> </li>     <li> <p>将物理仿真行为添加给仿真者实现仿真效果。</p> </li>    </ol>    <h3>二、单行为效果</h3>    <p>我这里简单写几个行为事例,其他创建方法基本和这一样,可以自己尝试:</p>    <h3>1、重力效果:</h3>    <pre>  // 创建一个仿真者[UIDynamicAnimator] 用来仿真物理行为    UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];      // 创建重力的物理仿真行为,并设置具体的items(需要仿真的view)    UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[view]];      // 将重力仿真行为添加给仿真者实现仿真效果,开始仿真    [animator addBehavior:gravity];</pre>    <p>具体效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2a7bd252d44d5975b97a888a9d839f44.gif"></p>    <h3>2、碰撞效果:</h3>    <p>是不是很简单,咱们再来一个碰撞效果:</p>    <pre>  // 碰撞检测  UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[view]];  // 设置不要出边界,碰到边界会被反弹  collision.translatesReferenceBoundsIntoBoundary = YES;  // 开始仿真  [animator addBehavior:collision];</pre>    <p>具体效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/058abbd00157547ad92029ef0b8dd52e.gif"></p>    <p>碰撞效果</p>    <h3>3、摆动效果:</h3>    <pre>  // 创建震动行为,snapPoint是它的作用点      self.snap = [[UISnapBehavior alloc] initWithItem:view snapToPoint:view.center];    // 开始仿真  [animator addBehavior:collision];</pre>    <p>具体效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/78068ba3e71ea7ba17d6f9b143c2fe8a.gif"></p>    <p>摆动效果</p>    <p>单效果创建都差不多,这里只写几个简单的,其他的可以看我参考的这篇文章 <a href="/misc/goto?guid=4959751844295138872" rel="nofollow,noindex">http://www.jianshu.com/p/e096d2dda478</a></p>    <h2>三、组合行为效果</h2>    <p>把单效果稍微组合一下:</p>    <h3>1、重力加碰撞:</h3>    <pre>  // 创建一个仿真者[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>    <p>具体效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e387c8904824bd040e860e23060f83f5.gif"></p>    <p>重力加碰撞</p>    <h3>2、重力加弹跳:</h3>    <pre>      // 动态媒介      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>    <p>具体效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1800780e109efa193bcce55c9adcfbb4.gif"></p>    <p>重力加弹跳,酷炫吧?</p>    <h2>四、大厂用到的实际效果</h2>    <p>一些大厂在利用这些效果,比如苹果的iMessage消息滚动视觉差效果、百度外卖重力感应(这个用到了重力感应)、摩拜单车贴纸效果,接下来咱们就逐个实现一下这些效果:</p>    <h3>1、防iMessage滚动效果:</h3>    <p>这里参考了著名开发者王维 <a href="/misc/goto?guid=4959751844388806620" rel="nofollow,noindex">@onevcat</a> 重的一篇文章</p>    <pre>  // 自定义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;  }</pre>    <p>具体效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/753c846d0c917af02f43af0926535c09.gif"></p>    <p>防iMessage滚动效果</p>    <h3>2、防摩拜单车贴纸效果:</h3>    <pre>  // 这里需要创建一个监听运动的管理者用来监听重力,然后实时改变重力方向          _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);      }];</pre>    <p>具体效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7c91f3a2c322d525b8f12d7938ba2330.gif"></p>    <p>防摩拜单车贴纸效果</p>    <h3>3、防百度外卖首页重力感应:</h3>    <pre>           // 这里需要创建一个监听运动的管理者用来监听重力方向,然后实时改变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];          }];      }];</pre>    <p>具体效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a1b1f54e48ef404e5fb19739a5371df6.gif"></p>    <p>防百度外卖首页重力感应</p>    <h3>总结</h3>    <ul>     <li> <p>本篇文章参考了</p> </li>    </ul>    <ul>     <li> <p><a href="/misc/goto?guid=4959751844295138872" rel="nofollow,noindex">iOS中UIDynamic物理仿真详解</a></p> </li>     <li> <p><a href="/misc/goto?guid=4959751844476429205" rel="nofollow,noindex">王巍的技术博客</a></p> </li>    </ul>    <ul>     <li> <p>最后几个效果图有点失真,具体效果可以找我要demo看</p> </li>    </ul>    <p>来自:http://www.jianshu.com/p/bed8f94c204d</p>    <p> </p>