模拟京东商城实现导航条隐藏功能

作者: 小蠢驴打代码 | 来源:发表于2017-11-08 17:24 被阅读323次

    样式需求展示-京东导航条

    :.gif
    需求说明:

    1.导航条隐藏功能
    2.界面向上滚动的时候,导航条隐藏
    3.界面向下滚动的时候,导航条显示


    层次结构分析:

    核心思路:导航条必须隐藏,显示的顶部的类似于导航条的控件,是我们自定义的UIView,才能实现效果!

    层级结构分析:


    1.png

    思路①:使用图中 - 原谅色的View - 导航条View - 替代navigationBar

    ==>问题出现 - 这种整个导航条View隐藏的时候,顶部时间View也隐藏了!不符合要求

    2.png

    思路②.顶部分成三个模块部分相互独立:

    • 顶部时间工具条自己一个View
    • 导航条自己一个View
    • 按钮VIew自己一个独立的View
    • 内容tableView自己独立一个View就不用说了

    隐藏导航条 && 界面移动的原理解释

    ①.界面上移的时候 - 导航View隐藏:

    3.png
    • 原理色的导航条View隐藏
    • 按钮View上移
    • tableView上移
    • tabView高度 ++ (加上导航条View的高度)

    ①.界面上移的时候 - 隐藏的导航View显示:


    4.png
    • 原理色的导航条View显示
    • 按钮View下移
    • tableView下移
    • tabView高度 -- (减去刚刚++的导航条View高度)

    问题难点:如果知道 下方的tableView滚动方法(怎么知道是向上滚还是向下滚动)

    思路1:tableVIew本质是scrollview,判断scrollview的滚动方向,通过contentOffset

    思路2:

    使用KVO,监听tableVIew的滚动,监听两个值 - NSKeyValueObservingOptionOld && NSKeyValueObservingOptionNew,通过新旧值的 .y值,判断滚动方向。

    这里使用的就是思路2的方法:

    a.隐藏系统默认的导航条View,然后自定义和导航条一模一样的UIView上去
    [self.navigationController setNavigationBarHidden:YES];

    b.tableView添加KVO监听滑动方向
    [_tableView addObserver:self forKeyPath:NSStringFromSelector(@selector(contentOffset)) options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

    c.通过观察者监听值的变化

    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        CGFloat oldOffsetY = [change[NSKeyValueChangeOldKey] CGPointValue].y;
        CGFloat newOffsetY = [change[NSKeyValueChangeNewKey] CGPointValue].y;
        CGFloat deltaY = newOffsetY - oldOffsetY;
    
        if(deltaY >= 0) { 
           //向上滚动}
        else{
          //向下滚动
    }
    

    c.在向上滚动的时候 - 设置导航条隐藏 + View上移

        if(deltaY >= 0) {  //向上滚动
    
            [UIView animateWithDuration:0.25 animations:^{
                
                //隐藏导航条
                _navigationView.hidden = YES;
                
                //按钮View上移导航条View的高度 - Y值改变
                CGRect tempShowViewFrame =  _showView.frame;
                tempShowViewFrame.origin.y -= navigationBarH;
                _showView.frame = tempShowViewFrame;
                
                 //tableView上移导航条View的高度 - Y值改变 && 高度 增加导航条的view的高度
                CGRect tempTableViewFrame = _tableView.frame;
                tempTableViewFrame.origin.y -= navigationBarH;
                tempTableViewFrame.size.height += navigationBarH;
                _tableView.frame = tempTableViewFrame;
                
            }];
            
        }
    

    d.在界面向下滚动的时候 - 设置导航条View显示 + View下移

        else {
            //向下滚动 - show
            [UIView animateWithDuration:0.25 animations:^{
                
                _navigationView.hidden = NO;
                
                CGRect tempShowViewFrame =  _showView.frame;
                tempShowViewFrame.origin.y += navigationBarH;
                _showView.frame = tempShowViewFrame;
                
                CGRect tempTableViewFrame = _tableView.frame;
                tempTableViewFrame.origin.y += navigationBarH;
                tempTableViewFrame.size.height -= navigationBarH;
                _tableView.frame = tempTableViewFrame;
                
            }];
        }
    

    核心代码如上,其实本质就是通过KVO观察tableView的滚动方向,然后设置对应的View显示 && 位置变化

    Demo展示:


    singleVC.gif

    进阶篇:跨控制器改变View的显示


    demo中的View都在同一个界面,可以直接在observeValueForKeyPath方法中,直接通过 UIView的成员变量改View的状态,但是如果跨控制器呢?

    complexVC.gif

    如图:此界面的顶部三个按钮,分别对应响应的三个控制器[‘全部’,‘测试1’,‘测试2’],控制器结构分析:


    5.png
    • 导航View && 按钮View && 按钮在外层的控制器上
    • 每个按钮对应各自的单独一个控制器,显示内容
    • 按钮对应的内部VC的view 添加到外层的VC的View上,才能得到显示
    • 最终显示的tableView,其实是按钮VC里面的- [btn->内部VC的view addsubView:tableVIew]
    • 所以简单的说,就是tableVIew和其他的View不在同一个控制器里

    思路:跨控制器传值

    (这里打算使用 - 代理模式)


    ==>思路:

    1. 因为是通过tableView滚动方向,判断View的显示隐藏 && 位置,所以判断在tableView对应的控制器上;
    2. 外部控制器根据tableView控制器的滚动方向而做出相应的变化,所以外部控制器要成为代理对象,协议声明写在tableView控制器上
    3. 外部控制要显示还是隐藏,tableView控制器要告诉他,所以代理方法要传值
    //tableView控制器的.h文件
    @protocol HZOrderNavigationViewDelegate <NSObject>
    
    - (void)changeNavigationViewShow:(BOOL)hidden;
    @end
    
    


    tableView控制器,还是一样通过KVO监听tableView的contentOffSet,然后在observeValueForKeyPath 方法中,由于无法直接控制外部VC的界面属性,所以通过代理传值,告诉外部控制器,界面要发生的变化

    //tableView控制器的.m文件 
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        
        CGFloat oldOffsetY          = [change[NSKeyValueChangeOldKey] CGPointValue].y;
        CGFloat newOffsetY          = [change[NSKeyValueChangeNewKey] CGPointValue].y;
        CGFloat deltaY              = newOffsetY - oldOffsetY;
        if(deltaY >= 0) {  //向上滚动
            //变成执行代理方法,通知外部VC的界面发生改变
            if ([_delegate respondsToSelector:@selector(changeNavigationViewShow:)]) {
                
                [_delegate changeNavigationViewShow:YES];
            }
    }
    
    //外部控制器接收到tableView控制器传来的值之后,做出的界面改变
    -(void)changeNavigationViewShow:(BOOL)hidden{
        
        if (hidden) {
            //导航条隐藏
            [UIView animateWithDuration:0.25 animations:^{
                
                _navigationView.hidden = YES;
                //按钮View位置改变
                CGRect tempShowViewFrame =  _topBtnsView.frame;
                tempShowViewFrame.origin.y -= navigationBarH;
                _topBtnsView.frame = tempShowViewFrame;
                
                //修改scrollview - 按钮VC对应的view添加到这个scrollview上的
                CGRect tempScrollViewFrame = _contentScrollView.frame;
                tempScrollViewFrame.origin.y -= navigationBarH;
                tempScrollViewFrame.size.height += navigationBarH;
                _contentScrollView.frame = tempScrollViewFrame;
            }];
    
        }
    

    原理其实和同一个控制器里面改变UIView的属性一样,只是这里跨了控制器,无法拿到属性,所以是通过代理传值告诉外部的那个控制器做出相应的改变而已,本质核心不变。

    几个小细节:

    • 细节1:
    7.png 8.png

    所以界面变化的步骤应该如下:

    1. 导航View隐藏
    2. 按钮View上移
    3. 外层scrollview上移,高度 ++
    4. tableView要和scrollview一样高度++,但是Y不用移动!!

    • 细节2:判断界面的显示or隐藏,如果导航条View已经隐藏了,再怎么上拉,也不能再调用使界面再次隐藏的办法,同时不能再让下方两个View的Y值 --;
    bug1.gif

    如图所示,要添加判断,如果导航条已经隐藏了,按钮View 和 tableView就不要再一直往上跑了,最多就上移一个View的位置就够了,所以要添加判断;

    如果是在同一个控制器中,可以添加 - _navigationView.hidden 属性判断

       if(deltaY >= 0) {  //向上滚动
           NSLog(@"向上滚动 - hidden");
           if (_navigationView.hidden == YES) {
               return;
           }
    

    但是这里是跨控制器的,_navigationView属性是在外部控制器上,而不是在tableView的控制器上,所以拿不到!

    • 根据y值判断?


      6.png
    9.png

    问题出现:tableView是添加到外部控制的内容ScrollView上的,Y值永远是0!所以不能用y值判断!

    10.png

    解决办法:Y值虽然是0无法进行判断,但是可以通过tableView的高度进行判断!

      if(deltaY >= 0) {  //向上滚动
            
            if (_tableView.frame.size.height == ScreenH - (topTimeToolH + CarInsOrderTopViewH )) {
               //说明tableView上方没有导航条View  - 导航条已经隐藏了,此时上滚就不用再改变位置了
                return;
            }
    

    同理:向下滚动的时候也要添加判断

        else{
            if (_tableView.frame.size.height == ScreenH - (topTimeToolH + CarInsOrderTopViewH + navigationBarH))   return;
            }
    
    
    • 细节3:判断的值最好不要用0,不然稍微一碰tableView,界面就发生变化
        if(deltaY >= 50) {  //向上滚动
    }
        else if (deltaY <= -50){
    }
    

    进阶 - 下拉刷新导致的Bug

    下拉刷新Bug.gif

    bug说明:如图,只要一使用下拉刷新,就自动调用 - 导航条View隐藏 并且 外部控制器上移效果

    下拉刷新的时候,本质上也是拖动tableView,一样会进tableView的监听方法!


    项目需求:下拉刷新的时候,不要和gif显示的一样,导致导航条隐藏并使得界面上移!

    解决方案 :


    11.png

    如图,我们发现,下拉的时候,跑出来的 mj_headerView - 高度54,就等于,直接让tableView的contentOffset.y = 54了!

    解决如下:所以要进代理方法,判断y的位移量,一定要大于54!大于54才让进入代理方法,例如取个80,否则每次下拉刷新都会进入代理方法改变界面

    
        if(deltaY >= 80) {  //向上滚动
            
            if (_tableView.frame.size.height == ScreenH - (topTimeToolH + CarInsOrderTopViewH )) {
                return;
            }
    

    最终效果演示:


    解决下拉刷新Bug.gif

    demo地址~

    相关文章

      网友评论

        本文标题:模拟京东商城实现导航条隐藏功能

        本文链接:https://www.haomeiwen.com/subject/lnaemxtx.html