美文网首页代码片段iOS相关技术iOS开发攻城狮的集散地
实现微博个人页面的滑块浮动切换页面效果(OC)

实现微博个人页面的滑块浮动切换页面效果(OC)

作者: ZeroJ | 来源:发表于2016-07-06 23:57 被阅读2531次

    前言

    这篇文章真的是不想写的, 因为之前分享过一篇用swift实现的相同的效果, 无奈很多的朋友总是问有没有oc版的, 好吧, 也可以理解在swift不太普及的情况下出现这样的事情, 看来最近真的是闲的慌, 每天在交流群里跟大家吹牛不说, 还把这个用oc写了一遍, 不过实现的过程和原来的swift有一点不一样, 因为对应的oc版的ZJScrollPageView更新了, 但是swift的并没有更新.demo地址

    最终效果

    示例效果.gif

    一` 实现的思路
    , 大多和swift中一样只是增加了一个scrollView

    构思: 利用监控和设置UIScrollView的偏移量来实现

    层级结构

    .

    1. 层级结构, 第一层为控制器的view, 第二层为containerView, 第三层为contentView, 第四层为 headView(为了实现header也能滑动, 添加了一个scrollView在他的下面)和segmentView

    2. 第三层的contentView,设置contentView的大小和控制器的view的大小一样, 来显示子控制器的view的内容, 需要设置他的子控制器view的tableView(collectionVie)的初始偏移量为segmentView和headView的高度之和,同时将segmentView和headView添加到view上面, 以实现tableView在滚动的时候segmentView和headView可以同步滚动

    3. subview的添加顺序, 先添加contentView, 然后再添加segmentView和headView(因为contentView和view大小一样, 若是在后面添加就将segmentView和headView遮住了)

    4. 同步滚动原理: 监控子控制器的scrollView的滚动偏移量(contentOffset.y), 根据偏移量的改变量来同步的调整segmentView和headView的frame, 这里的监控之前我是使用Closure来实现,但是后面需要更新tableView的偏移量有需要一个Closure, 所以就改为了delegate来实现.

    5. 使segmentView浮动: 通过监控子控制器的scrollView的滚动偏移量(contentOffset.y), 根据偏移量的改变量来同步的调整segmentView和headView的frame, 当segmentView同步"滚动"到顶部的时候, 通过判断scrollView的滚动偏移量的范围来固定segmentView和headView的frame, 即达到浮动效果

    6. 在segmentView和headView的frame随着scrollView滚动到下方原始位置的时候, 通过判断scrollView的滚动偏移量的范围来固定segmentView和headView的frame

    二. 实现部分 .

    1. 这些懒加载中设置了具体的ZJScrollPageView的属性和初始化的frame


      Snip20160706_7.png

    2, #pragma ZJScrollPageViewDelegate 代理方法

    - (NSInteger)numberOfChildViewControllers {
        return self.titles.count;
    }
    
    - (UIViewController<ZJScrollPageViewChildVcDelegate> *)childViewController:(UIViewController<ZJScrollPageViewChildVcDelegate> *)reuseViewController forIndex:(NSInteger)index {
        UIViewController<ZJScrollPageViewChildVcDelegate> *childVc = reuseViewController;
        
        if (!childVc) {
            childVc = [[ZJPageViewController alloc] init];
            
        }
        
        
        if (index%2==0) {
            childVc.view.backgroundColor = [UIColor blueColor];
        } else {
            childVc.view.backgroundColor = [UIColor redColor];
            
        }
        // 设置代理, 用于处理子控制器的滚动
        _currentChildVc = (ZJPageViewController *)childVc;
        _currentChildVc.delegate = self;
        return childVc;
    }
    

    3, 相应子控制器的scrollView的滚动, 在这里面调整segmentView等的位置,
    这里面会处理三种情况,

    • 第一种是向上滚到滑块的位置在navigationBar的时候, 需要使得继续向上滚的时候滑块停在这里
    • 第二种是向下滚动直到headView完全显示出来的时候, 需要处理
    • 第三种就是上面两种之间的时候, 需要让segmentView等同步滚动
    • 下面的代码就是分别处理这三种情况
    - (void)scrollViewIsScrolling:(UIScrollView *)scrollView {
        _childOffsetY = scrollView.contentOffset.y;
        self.currentOffsetY = _childOffsetY + defaultOffSetY;
        
    //    NSLog(@"%f", _currentOffsetY);
    
        if (self.currentOffsetY <= 0 ) {
    
    // 让headView停在navigationBar下面
            self.segmentView.zj_y = -_childOffsetY - segmentViewHeight;
            self.scrollView.zj_y = self.segmentView.zj_y - headViewHeight;
            
        }
        else if (self.currentOffsetY>=headViewHeight) {
            // 使滑块停在navigationBar下面
            self.scrollView.zj_y = naviBarHeight - headViewHeight;
            self.segmentView.zj_y = naviBarHeight;
    
        }
        
        else {
            // 这里是让滑块和headView随着上下滚动
            self.segmentView.zj_y = -_childOffsetY - segmentViewHeight;
            self.scrollView.zj_y = self.segmentView.zj_y - headViewHeight;
            // "递归"
            if (self.scrollView.contentOffset.y == self.currentOffsetY) {
                return;
            }
            [self.scrollView setContentOffset:CGPointMake(0, self.currentOffsetY)];
    
        }
    
    }
    

    4, 处理headView的滚动

    
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        
        self.currentOffsetY = scrollView.contentOffset.y;
    // 这样的方式达到, 底部的scrollView滚动,但是headView在屏幕上的位置不变, 
        self.headView.zj_y = self.currentOffsetY;
    
        if (self.currentOffsetY < 0) {
            self.containerView.zj_y = -self.currentOffsetY;
            return;
        } else {
            self.containerView.zj_y = 0;
    
        }
    
        if (_currentChildVc.tableView.contentOffset.y == self.currentOffsetY - defaultOffSetY) {
            return;
        }
      // 同步滚动子控制器的scrollView
        [_currentChildVc.tableView setContentOffset:CGPointMake(0, self.currentOffsetY - defaultOffSetY)];
    }
    

    5, 当切换页面的时候, 需要根据当前页面的scrollView的偏移量以及当前的segmentView等的位置, 来整体的调整, 同时在悬停的时候保留每个页面的滚动偏移量, 这里处理两种情况

    • 新页面出来的时候滑块并没有到上面悬停的位置, 这个时候, 将新页面的scrollView偏移量置为初始的
    • 新页面出来的时候滑块在上面悬停的位置, 这个时候, 使用新页面的scrollView偏移量, 并且调整各个控件的位置
    
    - (void)setupScrollViewOffSetYWhenViewWillAppear:(UIScrollView *)scrollView {
    
        dispatch_block_t setHeight = ^ {
            _childOffsetY = scrollView.contentOffset.y;
            [scrollView setContentSize:CGSizeMake(0, MAX(scrollView.bounds.size.height - naviBarHeight - segmentViewHeight, scrollView.contentSize.height))];
        };
        
        if (_childOffsetY < -(naviBarHeight + segmentViewHeight)) {
            [scrollView setContentOffset:CGPointMake(0, _childOffsetY)];
            setHeight();
            return;
        } else {
            if (scrollView.contentOffset.y < -(naviBarHeight + segmentViewHeight)) {
                [scrollView setContentOffset:CGPointMake(0, -(naviBarHeight + segmentViewHeight))];
    
                // 使滑块停在navigationBar下面
                self.scrollView.zj_y = naviBarHeight - headViewHeight;
                self.segmentView.zj_y = naviBarHeight;
                setHeight();
                return;
            }
            
            setHeight();
            return;
            
        }
        
    }
    

    6, 子控制器中, 需要使用代理, 通知, kvo, block等方式让父控制器知道当前的scrollView(tableView, collectionView均可)的偏移量即可, 这里我使用了代理来实现

    #pragma ZJScrollPageViewChildVcDelegate
    // 每次页面出现的时候会调用, 这个时候传递当前的偏移量
    - (void)setUpWhenViewWillAppearForTitle:(NSString *)title forIndex:(NSInteger)index firstTimeAppear: (BOOL)isFirstTime {
        [self.delegate setupScrollViewOffSetYWhenViewWillAppear:self.tableView];
        
        if(isFirstTime) {
            // 加载数据
        } else {
            //刷新...
        }
    }
    #pragma UIScrollViewDelegate 传递滚动的偏移量
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        [self.delegate scrollViewIsScrolling:scrollView];
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
        self.tableView.delegate = self;
        self.tableView.dataSource = self;
        [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:cellId];
    // 这里设置初始的内容便宜 200+64+44 这个数字是父控制器中的常量, 这里偷个懒
        [self.tableView setContentInset:UIEdgeInsetsMake(200+64+44, 0, 0, 0)];
        [self.view addSubview:self.tableView];
    }
    

    然后呢就简单的实现了这种效果, 其中可能会有问题, 欢迎交流, demo地址

    相关文章

      网友评论

      • S型身材的猪:其实对于这种界面,一直是个难点,我至今还没有看到一个人写出锅我个人满意的答案,像微博还是算比较简单的,因为它至少不需要左右切换后还得纪录原来的位置,只要左右切换回来后,直接滑到第一行就行了,像饿了吗,美团也有类似的效果,但是这2款的实现原理和微博有不较大不同,比如美团滑动内层scrollView时就是以2倍左右的速度滑动没,还有爱奇艺里面也有,爱奇艺是我认为最难的,它做到了位置纪录,整体刷新等,为了这种界面,我花了5天的时间去思考和编写,最终写了3个demo,代码有点乱https://github.com/SPStore/HVScrollView
        PGOne爱吃饺子:就冲着你说这些话,我感觉你就是个技术大牛,关注你一下,研究一下这个demo
      • icoder:如果是网络图片就不好弄
      • 夜凉若水:很不错,就是貌似左右滑动的时候,上下也会稍微的滑动一点,这个要是解决了就太好了,等更新
        李小南:请问这个问题解决了吗
      • JinkeyAI:还是需要swift的
      • code简:感谢分享~~
        code简:@ZeroJ 这个怎么给主页加刷新方法 ? 右滑返回有点问题 和他跳转的界面中 id target = self.navigationController.interactivePopGestureRecognizer.delegate;
        冲突了
        ZeroJ:@code简 多谢你的支持
      • 清眸如画:这个用tableview 的 headerview 和sectionview是不是更简便些
        ZeroJ:@约定一生 你去试试就知道了:smile:

      本文标题:实现微博个人页面的滑块浮动切换页面效果(OC)

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