iOS 惯性滑动效果

最近公司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上放了。要是有帮到大家是我的荣幸,另外夏天热,可以帮我买块西瓜去去暑 %>_<%。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351