概述
最近群里有人私信我关于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 推动行为
具体实现步骤:
创建一个仿真者[UIDynamicAnimator] 用来仿真所有的物理行为
创建具体的物理仿真行为[如重力UIGravityBehavior]
将物理仿真行为添加给仿真者实现仿真效果。
二、单行为效果
我这里简单写几个行为事例,其他创建方法基本和这一样,可以自己尝试:
1、重力效果:
// 创建一个仿真者[UIDynamicAnimator] 用来仿真物理行为 UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; // 创建重力的物理仿真行为,并设置具体的items(需要仿真的view) UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[view]]; // 将重力仿真行为添加给仿真者实现仿真效果,开始仿真 [animator addBehavior:gravity];
具体效果:
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];
具体效果:
摆动效果
单效果创建都差不多,这里只写几个简单的,其他的可以看我参考的这篇文章 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];
具体效果:
重力加碰撞
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];
具体效果:
重力加弹跳,酷炫吧?
四、大厂用到的实际效果
一些大厂在利用这些效果,比如苹果的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
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。