仿高德路线规划滑动效果

因为项目有个界面要模仿高德地图路径规划滑动效果,因此写了demo,并简单说下分析过程。

高德地图效果演示:

仿高德路线规划滑动.gif

demo效果演示:

高德地图规划滑动.gif

Demo地址:https://github.com/fangjinfeng/MySampleCode/tree/master/FJFBlogProjectDemo

一. 分析

  • 首先,我们可以看出这个滚动的视图应该是UIScrollView或者UIScrollView的子类(比如:UITableView);

  • 其次,从高德地图里的视图一开始的滑动,可以看出这个滑动是平稳的滑动,没有加速和减速,因此这里不可能是UIScrollView的滚动效果,因为UIScrollView的滚动效果是由一个加减速的过程,因此一开始滑动,应该是通过滑动手势UIPanGestureRecognizer,来移动UIScrollViewy值来移动

  • 接着滑动到指定位置之后,UIScrollViewy值固定不动,然后UIScrollView的内容进行滚动。这里就涉及到滑动手势UIPanGestureRecognizer的滑动,还有UIScrollView内部的滚动的处理。高德地图的演示效果里面,一开始滑动视图向上移动,移动到指定的点之后,立马就变成视图的滚动,这里可以分析,UIScrollView既支持手势的滑动又支持视图的滚动,只是通过条件来判断限制两者的执行逻辑。

  • 同时我们可以看到,如果一开始向上拉动视图力度大一点,视图会直接滚动到指定位置,如果力度小,就恢复到原来位置,因此这里需要依据手势滑动的加速度来进行判断处理。

  • 而当你滑动到中间位置的时候,也需要依据最后滑动的位置来判断应该动画滚动到上方还是下方。

  • 最后滑动的时候上方的视图和滑动视图本身有背景颜色的渐变效果,这里需要依据滑动距离来判断。

二.代码分析:

  • 首先由于滚动视图(demo里面是UITableView)需要支持手势滑动和内部滚动,因此需要写一个类FJBaseTableView继承自UITableView,然后在FJBaseTableView的实现里面重写如下方法:
// 当有 多个手势 都可以 响应
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {

    return YES;
}

来支持响应多个手势。

  • 然后给滚动视图tableView添加滑动手势,当tableView从底部滑动到顶部指定位置时,应该限制tableView内部的视图滚动。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (self.tableView.frame.origin.y > _scrollViewStartPositionY) {
        [scrollView setContentOffset:CGPointMake(0, 0)];
    }
}

这里的_scrollViewStartPositionY是顶部指定位置。

  • 接着看下手势滑动的处理逻辑:
#pragma mark - 手势处理
- (void)handlePanGesture:(UIPanGestureRecognizer *)sender {
    
    if (sender.state == UIGestureRecognizerStateBegan) {
        
        _beganPoint = [sender locationInView:sender.view.superview];
        _curPoint = sender.view.center;
        _topTipContainerViewCurrentY = _topContainerView.frame.origin.y;
        _previousOffsetY = self.tableView.contentOffset.y;
        
    } else if(sender.state == UIGestureRecognizerStateChanged) {
        
        CGPoint point = [sender locationInView:sender.view.superview];
        
        CGFloat offsetY = _previousOffsetY - self.tableView.contentOffset.y;
        NSInteger y_offset = point.y - _beganPoint.y - offsetY;
        
        if (sender.view.frame.origin.y >= _scrollViewStartPositionY || (self.tableView.contentOffset.y == 0 && self.tableView.contentSize.height > self.tableView.frame.size.height)) {
            sender.view.center = CGPointMake(_curPoint.x, _curPoint.y + y_offset);
            [self updateViewControlsWithSlideOffset:y_offset];
        }
        
        if (sender.view.frame.origin.y > _scrollViewLimitMaxY) {
            sender.view.y = _scrollViewLimitMaxY;
            [self updateViewControlsWithSlideUp:NO];
        }
        else if(sender.view.frame.origin.y < _scrollViewStartPositionY) {
            
            sender.view.y = _scrollViewStartPositionY;
             [self updateViewControlsWithSlideUp:YES];
        }
    } else if(sender.state == UIGestureRecognizerStateEnded) {
        
        if (sender.view.frame.origin.y <= _scrollViewStartPositionY || sender.view.frame.origin.y > _scrollViewLimitMaxY) {
            if (sender.view.frame.origin.y <= _scrollViewStartPositionY) {
                [self updateViewControlsWithSlideUp:YES];
            }
            if (sender.view.frame.origin.y > _scrollViewLimitMaxY) {
                [self updateViewControlsWithSlideUp:NO];
            }
            return;
        }
        // 滑动速度处理
        CGPoint velocity = [sender velocityInView:self.view];
        CGFloat speed = 350;
        if (velocity.y < - speed) {
            // 快速向上
            [self tableViewMoveToTop];
            return;
        } else if (velocity.y > speed) {
            // 快速向下
            [self tableViewMoveToBottom];
            return;
        }
        
        // 滑动临界值
        CGFloat criticalValue = _scrollViewLimitMaxY/2.0;
        if (sender.view.frame.origin.y <= criticalValue) {
            [self tableViewMoveToTop];
        } else {
            [self tableViewMoveToBottom];
        }
    }
}

这里几个点需要注意:

  1. _beganPoint、_curPoint两个参数是用来计算手势滑动距离然后调整scrollView滑动距离。而_previousOffsetY是用来记录滑动之前tableView的内部视图的偏移距离,因为当tableView滑动到顶部指定位置后,tableView开始滚动,这时候tableView向下滑动是先移动了tableView内部的滚动距离,然后才是滑动距离,因此需要将这部分值先记录,然后去除掉,才是tableView向下真正需要滑动的距离。
 CGFloat offsetY = _previousOffsetY - self.tableView.contentOffset.y;
 NSInteger y_offset = point.y - _beganPoint.y - offsetY;

2.滑动过程中,顶部视图的移动和渐变处理,这里先依据滑动的距离算出tableView滑动距离tableView最大滑动距离比值,然后再算出顶部视图需要移动的距离和背景的透明度

- (void)updateViewControlsWhenSliding {
    if (self.tableView.frame.origin.y > _scrollViewStartPositionY && self.tableView.frame.origin.y < _scrollViewLimitMaxY) {
        
        CGFloat offsetLimitDistance = _scrollViewLimitMaxY - _scrollViewStartPositionY;
        CGFloat offsetDistance = self.tableView.frame.origin.y - _scrollViewStartPositionY;
        if (offsetDistance > 0 && offsetDistance < offsetLimitDistance) {
            CGFloat topViewHeight = [FJFTopContainerView viewHeight];
            CGFloat topViewHeightOffset =  offsetDistance * (topViewHeight / offsetLimitDistance);
            CGFloat viewAlpha = offsetDistance / offsetLimitDistance;
            _topContainerView.y = topViewHeightOffset - topViewHeight;
            _topContainerView.alpha = viewAlpha;       
         }
    }
}

3.滑动速度处理,依据velocityInView函数获取速度值,然后依据当前速度值大小正负设定的速度值比较来判断是否需要向上或向下移动

 // 滑动速度处理
CGPoint velocity = [sender velocityInView:self.view];
CGFloat speed = 350;
if (velocity.y < - speed) {
     // 快速向上
      [self tableViewMoveToTop];
      return;
} else if (velocity.y > speed) {
    // 快速向下
    [self tableViewMoveToBottom];
    return;
 }

4.滑动临界值处理,判断最后滑动位置与底部指定位置一半,两个值的大小来判断滑动的方向。

 // 滑动临界值
CGFloat criticalValue = _scrollViewLimitMaxY/2.0;
if (sender.view.frame.origin.y <= criticalValue) {
    [self tableViewMoveToTop];
} else {
    [self tableViewMoveToBottom];
 }

三.总结

这里最主要就是介绍了分析的思路,来找出可靠的实现方法,具体逻辑,详见demo

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,945评论 4 60
  • 废话不多说,直接上干货 ---------------------------------------------...
    小小赵纸农阅读 3,315评论 0 15
  • 中秋佳节与宏良兄同游姑苏平江路。 评弹绣女弄琵琶,摆橹船娘唱盛华。 两岸霓虹明似昼,游人接踵不思家。 下平:六麻 ...
    诗呆阅读 1,514评论 20 51
  • 时间滴答的飞快的转动 离坟墓越来更近 离自己却越来越远 越来越讨厌速成和功利性的成功学 那是一碗经过精心包装的有毒...
    小李非刀阅读 312评论 0 0
  • 首先,介绍下大背景~ 我是2018届考上体体育管理专硕的学生,本科呢是双非二本并且至少前5年学院没有考上过上体的师...
    关尔曰阅读 4,583评论 0 6