iOS 惯性滑动效果

2018-05-07 15:52:37 浏览数 (1)

最近公司SDK新搞了个功能,手势滑动地图后,要具备惯性滑动效果的功能。安卓是先做出来了,然后给我看,由于我早体验过某鸟地图,某鸟地图也有这种效果,加上安卓做得确实不错,还在忙着研究OpenGL的我也只能先放下手中活,看着新功能默默构思了。

先把结果放出来:

寅时室内地图.gif

讲一下写这篇文章的原因:安卓是由于有系统的api,在滑动手势结束后调用系统自有api,传入手势结束时的速度(x方向和y方向)就能由系统自己做完往后的操作。而iOS并没有,但我还是自以为这个功能很好做...然而构思之后发现还得找百度啊,但百度给我的结果却没有一个能满足我。所以,在我做出这个效果之后,我得将它分享出来,给有需要的人提供思路,也希望能相互讨论,接受到更好的办法做出更好的效果。(这就跟UIScrollView的滑动效果类似,但是网上是没有代码资料的)

为了公司利益考虑,文章代码我专门写了demo来演示。

进入正题: 1.明确我们的目的:手势滑动后拥有惯性滑动效果 2.思考具体实现:手滑得越快,作用对象的惯性越大,运动时间越长,手滑得慢,作用对象的运动速度就越小,运动时间也越短 3.出现的一些小问题:解决它

OK,想到第2点就已经可以成为嘴强王者了,接下来就看操作是不是青铜了:

demo效果如下:

自写demo.gif

请大家不要看gif图好像有点卡,实际是一点都不卡的,很流畅很自然!

动.gif

demo中使用了两种方法让其做惯性滑动。

一、第一种是在手势结束后通过UIView的动画来改蓝色图片的center,因为系统UIView的动画有快进慢出UIViewAnimationOptionCurveEaseOut这种效果可选。

代码语言:javascript复制
-(void)paned:(UIPanGestureRecognizer *)pan
{
        CGPoint locationPoint = [pan locationInView:self.view];  //手指点
    
        CGPoint transPoint = [pan translationInView:self.view];  //移动点
    
        //    if (CGRectContainsPoint(blueImgView.frame, locationPoint)) {//若要只作用于蓝地图,以下代码移到此处
        
        //}
        
        blueImgView.center = CGPointMake(blueImgView.center.x transPoint.x, blueImgView.center.y transPoint.y);
        [pan setTranslation:CGPointZero inView:self.view];
        
        if (pan.state == UIGestureRecognizerStateEnded) {
            
            CGPoint velocity = [pan velocityInView:self.view];   //手指离开时x和y方向速度,单位是points/second
            CGFloat magnitude = sqrtf((velocity.x * velocity.x)   (velocity.y * velocity.y)); //真实速度
            CGFloat slideMult = magnitude / 200;  //自己试出来的比例,改动此处可修改灵敏度
            
            float slideFactor = 0.1 * slideMult;
            CGPoint finalPoint = CGPointMake(pan.view.center.x   (velocity.x * slideFactor),
                                             pan.view.center.y   (velocity.y * slideFactor));
            finalPoint.x = MIN(MAX(finalPoint.x, 0), self.view.bounds.size.width);
            finalPoint.y = MIN(MAX(finalPoint.y, 0), self.view.bounds.size.height);
            
            [UIView animateWithDuration:slideFactor delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ //slideFactor秒内做完改变center的动画,动画效果快进慢出(先快后慢)
                blueImgView.center = finalPoint;
            } completion:nil];
            
        }
 
}

重点是看UIGestureRecognizerStateEnded里的处理

CGPoint velocity = [pan velocityInView:self.view];这个方法可以获取手势离开时在x,y方向的速度,单位是点每秒(逻辑尺寸点)。

接着就是根据x、y的速度求出总速度,大家可以输出下velocity,看看它的数据,找到它的规律(我就是这样多次看,看出来的)。根据我们手滑动的快慢,velocity值也会跟着变化,总速度magnitude也会跟着变化,当然是手滑越快magnitude越大,越慢magnitude越小,那么,时间就用magnitude来确定吧,然后就试出来了除以200。另外我们根据velocity知道它在x,y方向上的速度,确定了运动时间,当然也能知道这段时间内它移动的距离:即 距离 = 速度 * 时间。 (毕竟读过小学)

然后就是做UIView的动画了。

代码语言:javascript复制
[UIView animateWithDuration:slideFactor delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ //slideFactor秒内做完改变center的动画,动画效果快进慢出(先快后慢)
                blueImgView.center = finalPoint;
            } completion:nil];

第一种方法点评:个人觉得不太自然,可能系统UIViewAnimationOptionCurveEaseOut效果并不是很明显吧,当然也很有可能改改代码,调一调灵敏度,效果会好很多。 最重要的是:我们公司的产品用这种UIView的方式是实现不了的,使用的是矩阵transform,所以接下来就开始第二种方法:

二、两种方法的区别在于处理手势滑动事件,第二种方法我们先定义了几个变量对象:

代码语言:javascript复制
@interface OtherViewController ()
{

    UIImageView *blueImgView;
    
    CGAffineTransform viewTransform;   //基础self.view的transform
    CGAffineTransform currentTransform; //当前transform
    
    CADisplayLink *dis; //定时器
    int updateCount;    //需要刷新次数
    int currentCount;   //当前刷新次数
    CGPoint velocity;   //速度
}

然后在viewDidLoad里将 viewTransform = self.view.transform;

代码语言:javascript复制
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
    blueImgView = [[UIImageView alloc]init];
    blueImgView.frame = CGRectMake(50, 100, 100, 100);
    blueImgView.image = [UIImage imageNamed:@"地图1"];
    [self.view addSubview:blueImgView];
    
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(paned:)];
    [self.view addGestureRecognizer:pan];
    
    viewTransform = self.view.transform;
    
    UIButton *Btn = [UIButton buttonWithType:UIButtonTypeCustom];
    Btn.frame = CGRectMake(40, 40, 100, 40);
    Btn.backgroundColor = [UIColor grayColor];
    [Btn setTitle:@"上个界面" forState:UIControlStateNormal];
    [Btn addTarget:self action:@selector(Btn) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:Btn];
}

在手势滑动事件里我们使用到了CADisplayLink,CADisplayLink也是一种定时器,调用时间间隔跟屏幕刷新频率是一致的(1s60次,X出来了,好像是每秒120帧),为了使我们动画效果高效流畅,我们使用这个。

代码语言:javascript复制
-(void)paned:(UIPanGestureRecognizer *)pan
{
    if (dis) {
        [dis invalidate];
        dis = nil;
    }

    CGPoint locationPoint = [pan locationInView:self.view];  //手指点
    
    CGPoint transPoint = [pan translationInView:self.view];  //移动点
    
    //    if (CGRectContainsPoint(blueImgView.frame, locationPoint)) {//若要只作用于蓝地图,以下代码移到此处
    
    //}

    currentTransform = CGAffineTransformTranslate(viewTransform, transPoint.x, transPoint.y);
    blueImgView.transform = currentTransform;
    
    if (pan.state == UIGestureRecognizerStateEnded) {
        viewTransform = currentTransform;
        
        velocity = [pan velocityInView:self.view];
        CGFloat magnitude = sqrtf((velocity.x * velocity.x)   (velocity.y * velocity.y));
        CGFloat slideMult = magnitude / 200;
        float slideFactor = 0.1 * slideMult;
        
        updateCount = slideFactor * 120   1;
        currentCount = 0;
        dis = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateView)];
        [dis addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    }

}

代码中关于速度的处理跟第一种方式一样,但接下来的动作是确定动画调用次数updateCount,为什么updateCount = slideFactor * 120 1;也是试出来的,本来是*60,大家可以自行更改看看效果。

在CADisplayLink调用的方法里:

代码语言:javascript复制
-(void)updateView
{
    
    currentCount  ;
    if (currentCount>updateCount || currentCount>60) {
        
        //        dis.paused = YES;
        [dis removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [dis invalidate];
        dis = nil;
    }else{
        CGPoint point = CGPointMake(velocity.x/30.0/currentCount, velocity.y/30.0/currentCount);
        currentTransform = CGAffineTransformTranslate(viewTransform, point.x, point.y);
        blueImgView.transform = currentTransform;
        viewTransform = currentTransform;
    }
    
}

我们规定调用次数要不多于60次,即作用对象最多运动1s,在作用对象运动过程中

代码语言:javascript复制
CGPoint point = CGPointMake(velocity.x/30.0/currentCount, velocity.y/30.0/currentCount);

point就是来确定后续运动时x,y方向速度的,velocity是x,y方向的速度,除以30可以得到一个运动较适合的速度值,除以currentCount的原因是让作用对象做减速运动,currentCount在递增,除以currentCount的话,运动速度就是递减了。 (方法完,可自行修改这个速度来改变灵敏度)

总结:所有代码都在上面了,就不往github上放了。要是有帮到大家是我的荣幸,另外夏天热,可以帮我买块西瓜去去暑 %>_<%。

0 人点赞