首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS 惯性滑动效果

iOS 惯性滑动效果

作者头像
清墨
发布2018-05-07 15:52:37
3.2K0
发布2018-05-07 15:52:37
举报

最近公司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这种效果可选。

-(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的动画了。

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

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

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

@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;

- (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帧),为了使我们动画效果高效流畅,我们使用这个。

-(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调用的方法里:

-(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,在作用对象运动过程中

CGPoint point = CGPointMake(velocity.x/30.0/currentCount, velocity.y/30.0/currentCount);

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

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.07.27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档