版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.08.06 |
前言
我们的app很多都需要获取使用者的动作、方向以及其他和方位或者位置有关的参数,在ios中对应的框架就是
CoreMotion
,而在硬件对应的就是集成的加速计和陀螺仪。这几篇我们就从基础原理理论出发,讲一下相关的知识。关于这个框架的了解感兴趣的可以看这几篇。
1. CoreMotion框架(一)—— 基础理论
2. CoreMotion框架(二)—— 利用加速度计获取设备的方向
3. CoreMotion框架(三)—— 获取陀螺仪数据
功能要求
我们在摩拜的贴纸中,上面可以看见,很多贴纸小球可以随着手机姿势的变化,而滚动,下面我们就模仿这个效果,做个例子,效果图如下所示。
这里没给大家截gif
图,手机上截取不是很好截取,所以就只能给个静态图了,大家喜欢的可以自己打开app
看一下效果。
功能实现
1. 几个相关的类和框架
在给出代码之前,先给出几个相关的类和框架。
UIDynamicAnimator
UIGravityBehavior
UICollisionBehavior
UIDynamicItemBehavior
UIDynamicAnimator
作用:可以通过该类添加不同的行为,来实现一些动态效果。
下面看一下这个类的相关api。
#import <Foundation/Foundation.h>
#import <UIKit/UIView.h>
#import <UIKit/UICollectionViewLayout.h>
NS_ASSUME_NONNULL_BEGIN
@class UIDynamicBehavior;
@class UIDynamicAnimator;
@protocol UIDynamicAnimatorDelegate <NSObject>
@optional
- (void)dynamicAnimatorWillResume:(UIDynamicAnimator *)animator;
- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator;
@end
NS_CLASS_AVAILABLE_IOS(7_0) @interface UIDynamicAnimator: NSObject
// When you initialize a dynamic animator with this method, you should only associates views with your behaviors.
// the behaviors (and their dynamic items) that you add to the animator employ the reference view’s coordinate system.
- (instancetype)initWithReferenceView:(UIView *)view NS_DESIGNATED_INITIALIZER;
- (void)addBehavior:(UIDynamicBehavior *)behavior;
- (void)removeBehavior:(UIDynamicBehavior *)behavior;
- (void)removeAllBehaviors;
@property (nullable, nonatomic, readonly) UIView *referenceView;
@property (nonatomic, readonly, copy) NSArray<__kindof UIDynamicBehavior*> *behaviors;
// Returns the dynamic items associated with the animator’s behaviors that intersect a specified rectangle
- (NSArray<id<UIDynamicItem>> *)itemsInRect:(CGRect)rect;
// Update the item state in the animator if an external change was made to this item
- (void)updateItemUsingCurrentState:(id <UIDynamicItem>)item;
@property (nonatomic, readonly, getter = isRunning) BOOL running;
#if UIKIT_DEFINE_AS_PROPERTIES
@property (nonatomic, readonly) NSTimeInterval elapsedTime;
#else
- (NSTimeInterval)elapsedTime;
#endif
@property (nullable, nonatomic, weak) id <UIDynamicAnimatorDelegate> delegate;
@end
@interface UIDynamicAnimator (UICollectionViewAdditions)
// When you initialize a dynamic animator with this method, you should only associate collection view layout attributes with your behaviors.
// The animator will employ thecollection view layout’s content size coordinate system.
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout;
// The three convenience methods returning layout attributes (if associated to behaviors in the animator) if the animator was configured with collection view layout
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForCellAtIndexPath:(NSIndexPath *)indexPath;
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath;
@end
NS_ASSUME_NONNULL_END
UIGravityBehavior
作用:给物体添加一个类似于自由落体的运动。
#import <Foundation/Foundation.h>
#import <UIKit/UIView.h>
#import <UIKit/UIDynamicBehavior.h>
NS_ASSUME_NONNULL_BEGIN
NS_CLASS_AVAILABLE_IOS(7_0) @interface UIGravityBehavior : UIDynamicBehavior
- (instancetype)initWithItems:(NSArray<id <UIDynamicItem>> *)items NS_DESIGNATED_INITIALIZER;
- (void)addItem:(id <UIDynamicItem>)item;
- (void)removeItem:(id <UIDynamicItem>)item;
@property (nonatomic, readonly, copy) NSArray<id <UIDynamicItem>> *items;
// The default value for the gravity vector is (0.0, 1.0)
// The acceleration for a dynamic item subject to a (0.0, 1.0) gravity vector is downwards at 1000 points per second².
@property (readwrite, nonatomic) CGVector gravityDirection;
@property (readwrite, nonatomic) CGFloat angle;
@property (readwrite, nonatomic) CGFloat magnitude;
- (void)setAngle:(CGFloat)angle magnitude:(CGFloat)magnitude;
@end
NS_ASSUME_NONNULL_END
UICollisionBehavior
作用:碰撞检测,和上面的重力配合查看效果。
NS_CLASS_AVAILABLE_IOS(7_0) @interface UICollisionBehavior : UIDynamicBehavior
- (instancetype)initWithItems:(NSArray<id <UIDynamicItem>> *)items NS_DESIGNATED_INITIALIZER;
- (void)addItem:(id <UIDynamicItem>)item;
- (void)removeItem:(id <UIDynamicItem>)item;
@property (nonatomic, readonly, copy) NSArray<id <UIDynamicItem>> *items;
@property (nonatomic, readwrite) UICollisionBehaviorMode collisionMode;
@property (nonatomic, readwrite) BOOL translatesReferenceBoundsIntoBoundary;
- (void)setTranslatesReferenceBoundsIntoBoundaryWithInsets:(UIEdgeInsets)insets;
- (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier forPath:(UIBezierPath *)bezierPath;
- (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier fromPoint:(CGPoint)p1 toPoint:(CGPoint)p2;
- (nullable UIBezierPath *)boundaryWithIdentifier:(id <NSCopying>)identifier;
- (void)removeBoundaryWithIdentifier:(id <NSCopying>)identifier;
@property (nullable, nonatomic, readonly, copy) NSArray<id <NSCopying>> *boundaryIdentifiers;
- (void)removeAllBoundaries;
@property (nullable, nonatomic, weak, readwrite) id <UICollisionBehaviorDelegate> collisionDelegate;
@end
NS_ASSUME_NONNULL_END
UIDynamicItemBehavior
作用:给动画效果增加各种物理特性。具体可以增加下面的物理特性。
-
allowsRotation
旋转特性。 -
resistance
组抗力,抗阻力0~CGFLOAT_MAX
,阻碍原有所加注的行为(如本来是重力自由落体行为,则阻碍其下落,阻碍程度根据其值来决定)。
-friction
: 磨擦力0.0 ~ 1.0
在碰撞行为里,碰撞对象的边缘产生。 -
elasticity
:弹跳性0.0 ~ 1.0
。 -
density
:密度0 ~ 1
。
#import <Foundation/Foundation.h>
#import <UIKit/UIDynamicBehavior.h>
NS_ASSUME_NONNULL_BEGIN
NS_CLASS_AVAILABLE_IOS(7_0) @interface UIDynamicItemBehavior : UIDynamicBehavior
- (instancetype)initWithItems:(NSArray<id <UIDynamicItem>> *)items NS_DESIGNATED_INITIALIZER;
- (void)addItem:(id <UIDynamicItem>)item;
- (void)removeItem:(id <UIDynamicItem>)item;
@property (nonatomic, readonly, copy) NSArray<id <UIDynamicItem>> *items;
@property (readwrite, nonatomic) CGFloat elasticity; // Usually between 0 (inelastic) and 1 (collide elastically)
@property (readwrite, nonatomic) CGFloat friction; // 0 being no friction between objects slide along each other
@property (readwrite, nonatomic) CGFloat density; // 1 by default
@property (readwrite, nonatomic) CGFloat resistance; // 0: no velocity damping
@property (readwrite, nonatomic) CGFloat angularResistance; // 0: no angular velocity damping
/*!
Specifies the charge associated with the item behavior. Charge determines the degree to which a dynamic item is affected by
electric and magnetic fields. Note that this is a unitless quantity, it is up to the developer to
set charge and field strength appropriately. Defaults to 0.0
*/
@property (readwrite, nonatomic) CGFloat charge NS_AVAILABLE_IOS(9_0);
/*!
If an item is anchored, it can participate in collisions, but will not exhibit
any dynamic response. i.e. The item will behave more like a collision boundary.
The default is NO
*/
@property (nonatomic, getter = isAnchored) BOOL anchored NS_AVAILABLE_IOS(9_0);
@property (readwrite, nonatomic) BOOL allowsRotation; // force an item to never rotate
// The linear velocity, expressed in points per second, that you want to add to the specified dynamic item
// If called before being associated to an animator, the behavior will accumulate values until being associated to an animator
- (void)addLinearVelocity:(CGPoint)velocity forItem:(id <UIDynamicItem>)item;
- (CGPoint)linearVelocityForItem:(id <UIDynamicItem>)item;
// The angular velocity, expressed in radians per second, that you want to add to the specified dynamic item
// If called before being associated to an animator, the behavior will accumulate values until being associated to an animator
- (void)addAngularVelocity:(CGFloat)velocity forItem:(id <UIDynamicItem>)item;
- (CGFloat)angularVelocityForItem:(id <UIDynamicItem>)item;
@end
NS_ASSUME_NONNULL_END
2. 代码实现
下面我们就直接看代码。
1. JJMoBikeBallVC.m
#import "JJMoBikeBallVC.h"
#import "Masonry.h"
#import <CoreMotion/CoreMotion.h>
#define kJJMoBikeBallVCBallWidth 40.0
@interface JJMoBikeBallVC () <UICollisionBehaviorDelegate>
@property (nonatomic, strong) UIButton *startButton;
@property (nonatomic, strong) NSMutableArray <UIImageView *> *ballImageArrM;
@property (nonatomic, strong) UIGravityBehavior *gravityBehavior;
@property (nonatomic, strong) UICollisionBehavior *collision;
@property (nonatomic, strong) UIDynamicAnimator *animator;
@property (nonatomic, strong) UIDynamicItemBehavior *dynamicItemBehavior;
@property (nonatomic, strong) CMMotionManager *motionManager;
@end
@implementation JJMoBikeBallVC
#pragma mark - Override Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupUI];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.navigationController.navigationBarHidden = YES;
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
self.navigationController.navigationBarHidden = NO;
}
- (void)dealloc
{
[self.motionManager stopDeviceMotionUpdates];
self.motionManager = nil;
}
#pragma mark - Object Private Function
- (void)setupUI
{
self.view.backgroundColor = [UIColor whiteColor];
UIButton *startButton = [UIButton buttonWithType:UIButtonTypeCustom];
startButton.backgroundColor = [UIColor lightTextColor];
[startButton setTitle:@"开始" forState:UIControlStateNormal];
startButton.titleLabel.font = [UIFont boldSystemFontOfSize:25.0];
[startButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[startButton addTarget:self action:@selector(startButtonDidClick:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:startButton];
self.startButton = startButton;
[self.startButton sizeToFit];
[self.startButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.top.equalTo(self.view).offset(50.0);
}];
}
- (void)loadBallData
{
NSInteger numberOfBalls = 30;
self.ballImageArrM = [NSMutableArray arrayWithCapacity:30];
for (NSInteger i = 0; i < numberOfBalls; i++) {
UIImageView *imageView = [[UIImageView alloc] init];
CGFloat redValue = arc4random_uniform(255);
CGFloat greenValue = arc4random_uniform(255);
CGFloat blueValue = arc4random_uniform(255);
imageView.backgroundColor = [UIColor colorWithRed:redValue/255.0 green:greenValue/255.0 blue:blueValue/255.0 alpha:1.0];
CGFloat width = kJJMoBikeBallVCBallWidth;
imageView.layer.cornerRadius = kJJMoBikeBallVCBallWidth * 0.5;
imageView.layer.masksToBounds = YES;
imageView.frame = CGRectMake(arc4random()%((int)(self.view.bounds.size.width - width)), 0, width, width);
[self.view addSubview:imageView];
[self.ballImageArrM addObject:imageView];
//设置这些球的碰撞重力等其他特性
[self setupBallProperty];
}
}
- (void)setupBallProperty
{
UIDynamicAnimator *animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
self.animator = animator;
//添加重力的动态特性,使其可执行
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:self.ballImageArrM];
[self.animator addBehavior:gravityBehavior];
self.gravityBehavior = gravityBehavior;
//添加碰撞的动态特性,使其可执行
UICollisionBehavior *collision = [[UICollisionBehavior alloc]initWithItems:self.ballImageArrM];
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];
self.collision = collision;
//弹性
UIDynamicItemBehavior *dynamicItemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:self.ballImageArrM];
dynamicItemBehavior.allowsRotation = YES;//允许旋转
dynamicItemBehavior.elasticity = 0.6;//弹性
[self.animator addBehavior:dynamicItemBehavior];
//开始配置陀螺仪配置
[self setupGyroPush];
}
- (void)setupGyroPush
{
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.deviceMotionUpdateInterval = 1.0/15;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
__weak __typeof(self) weakSelf = self;
[self.motionManager startDeviceMotionUpdatesToQueue:queue withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
NSString *yaw = [NSString stringWithFormat:@"%f",motion.attitude.yaw];
NSString *pitch = [NSString stringWithFormat:@"%f",motion.attitude.pitch];
NSString *roll = [NSString stringWithFormat:@"%f",motion.attitude.roll];
double rotation = atan2(motion.attitude.pitch, motion.attitude.roll);
strongSelf.gravityBehavior.angle = rotation;
}];
}
#pragma mark - Action && Notification
- (void)startButtonDidClick:(UIButton *)button
{
button.enabled = NO;
//加载球的数据
[self loadBallData];
}
#pragma mark - <UICollisionBehaviorDelegate>
- (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(nullable id <NSCopying>)identifier atPoint:(CGPoint)p
{
}
- (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(nullable id <NSCopying>)identifier
{
}
@end
功能效果
下面我们就看一下功能效果。
可以很好的实现了效果。
后记
未完,待续~~~