UIDynamic物理引擎
概述
最近群里有人私信我关于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];
具体效果:
Untitled.gif2、碰撞效果:
是不是很简单,咱们再来一个碰撞效果:
// 碰撞检测
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看
- 最近急着招人,平时能挤出的时间也不多,所以这篇文章写的比较粗糙,有任何疑问都可以私信我
- 喜欢就点个赞吧
我的更多文章:老司机带你飞
请不要吝惜,随手点个喜欢或者关注一下吧!您的支持是我最大的动力😊!
您可以关注我以便及时查看我的最新文章,如果您对本篇文章有任何疑问,请随时私信我,您还可以加入我们的群,大家庭期待您的加入!