UIKit dynamics and Motion Effects(二)


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

大背景:需要添加多个 child view controller到另外一个view controller,用手势控制该显示哪个child view controller
CGRect frameForView = CGRectOffset(self.view.bounds,0.0f,self.view.bounds.size.height - offset); 
//  CGRectOffset保持source frame的size 不变,给origin point一个offset,然后返回该source frame

//create the child view controller user current storyboard来实例化child view controller
UIStroyboard *myStroyboard =[UIStroyboard stroyboardWithName:@"Main" bundle:nil];  //
SandwichViewController *viewController = [myStroyboard instantiateViewControllerWithIdentifier:@"SandwichVC"];

//provide data and set frame
UIView *view = viewController.view;
view.frame = frameForView;
viewController.sandWich = sandwich;//sandwich是data,方法传进来的参数

//add as a child
[self addChilediewController:viewController];
[self.view addSubView:viewController.view];
viewController didMoveToParentViewController:self]; //该方法与addChildViewController:方法结合用


//add a gesture recognizer to SandwichViewController
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePan:)];
[view controller.view addGestureRecognizer:pan];

当手势发生时,会调用handlePan方法:
-(void)handlePan:(UIPanGestureRecognizer*)gesture{
    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;
    }
}
该方法在手势发生时调用,先从self.view上拿到触碰的point,获取触碰的view
CGPoint touchPoint = [gesture locationInView:self.view];
    UIView *draggedView = gesture.view;

然后在手势开始的时候,先要获取手势所拖曳的view在self.view的开始点,然后设置touchPoint为前一次的touchPoint
在手势使用中:先计算出手势所控制的view的center的offset,然后重新设置view的center,完了以后设置touchPoint为previousTouchPoint
在手势停止后:先把gesture的速度传给手势所控制的view,view一概速度如果到达snap的范围,则停止,否则则在重力的作用下返回原位置。然后通知animator用当前view的state来update item state

传递gesture的速度给gesture.view

-(void)addVelocityToView:(UIView*)view fromgesture:(UIPanGestureRecognizer*)gesture{
    CGPoint vel = [gesture velocityInView:self.view];
    vel.x =0;
    UIDynamicItemBehavior *behavior = [self itemBehaviorForView:view];
    [behavior addLinearVelocity:vel forItem:view];
}
先获取手势的速度,不想让X方向有速度,所以设为0,然后获取该view的item behavior,用方法addLinearVelocity:forItem给view添加速度。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值