Motion Effects
用Motion Effects可以做出像ios 7 home screen上那样的并行效果,motion effects通常被用在背景上.当device 稍微倾斜时,可以看到背景与view都会随着device的倾斜而产生变化
用到的比较重要的方法:
1 CGRect CGRectInset(CGRect rect,CGFloat dx,CGFloat dy); 的使用
运行下面的代码:
UIImageView *backgroundImage = [[UIImageViewalloc]initWithImage:[UIImageimageNamed:@"Background-LowerLayer.png"]];
backgroundImage.frame = CGRectInset(self.view.frame,25,25); //p1
// backgroundImage.frame = CGRectInset(self.view.frame,50,50);//p2
// backgroundImage2.frame = CGRectInset(backgroundImage,-25,-25);//p3
[self.viewaddSubview:backgroundImage];
p1 p3 p2
CGRectInset方法能够用已经存在的frame经过适当的放大和缩小来初始化另外的view的frame,在初始化中,两个view的center不变,参数dx,dy正值相比原来的view.frame缩小,负值比原来的view要大.
具体的实现步骤:
设置两张背景view,处于lower-layer的背景view要比screen大,处于mid-layer的view与screen的大小一样,处于顶层的view与lower-layer需要添加motion effects,代码如下:
//set backgroundImage Lower-layer image
UIImageView *backgroundImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"background-LowerLayer.png"]];
backgroundImageView.frame = CGRectInset(self.view.frame,-50,-50);//用self.view.frame来初始化backgroundView.frame
[self.addSubView:backgroundImageView] // 添加background view 到 view hierarchy
[self addMotionEffectToView:backgroundView magnitude:50.0f];// 用自定义的方法给backgrounImage view 添加motion effect
//set background image mid-layer image
UIImageView *midLayerImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"background-midLayer.png"];
[self.view addSubView:midLayerImageView];
// add foreground image
UIImageView *header = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"Sarnie.png"];
header.center = CGPointMake(220,180);
[self.view addSubView:header];
[self addMotionEffectToView:header magnitude:-20.0f]; //给header添加motion effect
下面是方法addMotionEffectToView:magnitude
-(void)addMotionEffectToView:(UIView*)view magnitude:(CGFloat)magnitude{
//init UIInterpolationMotionEffect 实例 keyPath是想要更改的view的property
UIInterpolatingMotionEffect *xMotion =[[UIInterpolatingMotionEffect alloc]initWithkeyPath:@"center.x"
type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
xMotion.minimumRelativeValue = @(-magnitude);
xMotion,maxMumRelativeValue = @(magnitude);
UIInterpolatingMotionEffect *yMotion =[[UIInterpolatingMotionEffect alloc]initWithkeyPath:@"center.x"
type:UIInterpolatingMotionEffectTypeTiltAlongVerticlalAxis];
xMotion.minimumRelativeValue = @(-magnitude);
xMotion,maxMumRelativeValue = @(magnitude);
UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc]init];
group.motionEffects = @[xMotion,yMotion];
[view addMotionEffect:group]; //添加Motion effect to view
//UImotionEffectGroup是一个Motion Efffect的collection,里面放置同时应用到一个view的motion effects
}
到此为止当倾斜device的时候,header view与backgroundview就会随着倾斜的方向移动,产生动态的效果
在实际应用中如果view倾斜超出了screen的边界,可以设置view.clipsToBounds = YES;让subview的边界服从receiver的边界,在这里view发送给self.view,故view的边界应该服从screen的边界,超出的部分会被剪掉
UIKit dynamics
UIKit dynamics可以给指定的view 添加dynamic behavior,比如gravity等,使人感到更加的真实
// 设置dynamic animator
self.animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view]; //设置animator的坐标系统与self.view一样
//init and add gravity behaviors
self.gravity = [UIGravityBehavior alloc]init];
[self.animtor addBehavior:self.gravity];
[self.gravity addItem:view];//给一个view 添加gravity behavior
self.gravity.magnitude = 4.0f; // 1.0 代表1000 points/s ^2; 值越大,下降的越快
//init and add collision behavior
UICollisionBehavior *collision = [[UICollisionBehavior alloc]initWithItems:@[view]];
[self.animator addBehavior:collision];
//给collision添加一个边界
[collision addBoundaryWithIdentifier:@1 fromPoint:boundaryStart toPoint:boundaryEnd];
这里添加了一条直线作为边界,当view到该边界时会发生碰撞的behavior. collision的identifier应该是唯一的,以便后边识别collision behavior
如果服从了collision delegate并设置collision.collisionDelegate = self;
发生collision时会调用方法:
UIInterpolatingMotionEffect *xMotion =[[UIInterpolatingMotionEffect alloc]initWithkeyPath:@"center.x"
type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
xMotion.minimumRelativeValue = @(-magnitude);
xMotion,maxMumRelativeValue = @(magnitude);
可以在该方法里对collision发生时要做的事情进行设置
下面是实例中的设置:
-(void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p{
if([@2 isEqual:identifier]){
UIView *view = (UIView *)item;
[self tryDockView:view];
view.center= self.view.center;
}
if([@1 isEqual:identifier]){
UIView *view = (UIView *)item;
UIDynamicItemBehavior *behavior=[self itemBehaviorForView:view];
CGPoint vel = [behavior linearVelocityForItem:view];
NSInteger index = [self.views indexOfObject:view];
NSInteger inde=0;
for(NSInteger ind = index + 1; ind <[self.views count];ind ++){
vel.x = 0;
if(ind==4){
inde = 2;
}else if(ind == 5){
inde = 1;
}else {
inde = ind;
}
UIDynamicItemBehavior *OtherBehavior =[self itemBehaviorForView:[self.views objectAtIndex:ind]];
vel.y = vel.y/inde;
[OtherBehavior addLinearVelocity:vel forItem:[self.views objectAtIndex:ind]];
}
}
}
这段代码根据collision identifier的不同,来设置发生不同的collision behavior时做的事情
collision identifier 1发生时,把view的速度的部分传给别的view,
collision identifier 2发生时,用snap behavior把view 抓住,不让他在gravity的作用下向下运动
可以通过设置dynamic item behavior的一些property来使collision看起来更加的真实
//init dynamic item behavior
UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc]initWithItems:@[view];
[self.animator addBehavior:itemBehavior];
itemBehavior.resistance =2.0f; //这个值设定item下降是的阻力的大小,值越大,阻力越大,速度也就越慢
itemBehavior.elasticity = 0.7; //这个值设定item发生碰撞后能量和速度的损耗程度,值最大为1,表示没有损耗,最小为0,表示发生碰撞后速度为0;
//设置snap behavior
snap behavior在满足某个条件下可以让view 停留在某一个position上,
-(void)tryDockView:(UIView*)view {
BOOL viewHasReachedDockLocation = view.frame.origin.y < 50.0;
if(viewHasReachedDockLocation){
if(!self.viewDocked){
view.center = self.view.center;
self.snap = [[UISnapBehavior alloc]initWithItem:view snapToPoint:self.view.center];
self.snap.damping = 0.8;
[self.animator addBehavior:self.snap];
view.center= self.view.center;
[self setAlphaWhenViewDocked:view alpha:0.0];
self.viewDocked = YES;
}
}else {
if(self.viewDocked){
[self.animator removeBehavior:self.snap];
[self setAlphaWhenViewDocked:view alpha:1.0];
self.viewDocked = NO;
}
}
}
这段代码初始化了一个snap behavior,在距screen 50 points的地方抓住view,创建了一个snap behavior,让view 停留在self.view.center.
self.snap.damp = 0.8设定sanp时的view的阻尼系数,值越小,抓住该view时振荡幅度越大,值越大,越稳定
当抓取一个view时,用[self setAlphaWhenViewDocked:view alpha:0.0];来设置其他的view的alpha为0,隐去其他view
当view脱离设定的条件时,需要移除snap behavior,然后设定其他的view可见。
额外的知识:
添加gesture controll
CGPoint touchPoint = [gesture locationInView:self.view];
UIView *draggedView = gesture.view;
if(gesture.state == UIGestureRecognizerStateBegan){
CGPoint dragStartLocation = [gesture locationInView:draggedView];
if(dragStartLocation.y <400.0f){
self.draggingView = YES;
self.previousTouchPoint = touchPoint;
}
}else if(gesture.state == UIGestureRecognizerStateChanged && self.draggingView){
//handle gragging
CGFloat yoffset = self.previousTouchPoint.y - touchPoint.y;
gesture.view.center =CGPointMake(draggedView.center.x, draggedView.center.y-yoffset);
if(self.previousTouchPoint.x !=touchPoint.x){
touchPoint.x = self.previousTouchPoint.x;
}
self.previousTouchPoint = touchPoint;
}else if(gesture.state == UIGestureRecognizerStateEnded &&self.draggingView){
//getsture ended
[self tryDockView:draggedView];
[self addVelocityToView:draggedView fromgesture:gesture];
[self.animator updateItemUsingCurrentState:draggedView];
self.draggingView =NO;
}
}
UIView *draggedView = gesture.view;
传递gesture的速度给gesture.view
CGPoint vel = [gesture velocityInView:self.view];
vel.x =0;
UIDynamicItemBehavior *behavior = [self itemBehaviorForView:view];
[behavior addLinearVelocity:vel forItem:view];
}