美文网首页iOS开发技术iOS Developer
分页控制器半自动滑动(仿36氪)

分页控制器半自动滑动(仿36氪)

作者: Mister志伟 | 来源:发表于2017-03-14 10:51 被阅读156次

    在很多APP里都能看到屏幕上方有一条用于显示分页控制器的View,一般分页控制器栏的总体宽度都会超过屏幕宽度,为了让用户减少手动滑动分页控制器栏的操作,所以分页控制器的半自动滑动就必不可少了。(代码采用双语方式)

    36氪客户端.png 腾讯新闻客户端.png

    该功能点主要有五个:
    1.选中标题和上一个选中标题互换字体颜色。
    2.底部线条永远跟随选中标题,并且页面滑动时线条要向对应的分栏标题方向滑动。
    3.点击标题按钮时,判断点击标题的临近标题按钮是否在屏幕内,不在则滑动显示临近标题。
    4.滑动页面切换标题栏时,判断滑到的标题栏的临近标题按钮是否在屏幕内,不在则滑动显示临近标题。
    5.不同长度的标题如何动态显示按钮和控制底部线条滑动。
    实际效果如下:

    segmentDemoShow.gif

    功能点一,很容易用一个按钮属性记录上一个选中按钮就可以搞定了,然后每次点击后就重新设置上次选中按钮,设置后再将当前选中按钮赋值给选中按钮属性记录,周而复始即可。

    功能点二,因为页面取屏幕宽度,底部线条要移动的范围是下一个按钮的宽度,所以需要换算移动比例,此处命名scrollGap为页面滑动距离,scrollGap<0,页面向左滑动。页面滑动时监听方法

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView;
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) 
    

    通过计算获取到页面滑动距离,设置底部线条的位置。

    if (scrollGap < 0) { // 页面向左滑动 底部线向右滑动
            CGFloat scale = -scrollGap*2/Screen_Width;
            CGFloat gap = ([self.buttonWidths[index] floatValue]+[self.buttonWidths[index+1] floatValue]-2*self.bottomLineWidth)/2 + self.bottomLineWidth;
            
            if (scale <= 1) {
                bottomLine.frame = CGRectMake(bottomLine.frame.origin.x, bottomLine.frame.origin.y, self.bottomLineWidth+scale*gap, 2);
            }
            else {
                bottomLine.frame = CGRectMake(self.bottomLastX+(scale-1)*gap, bottomLine.frame.origin.y, self.bottomLineWidth+gap-(scale-1)*gap, 2);
            }
            
        }
        else { // 页面向右滑动 底部线向左滑动
            CGFloat scale = scrollGap*2/Screen_Width;
            CGFloat gap = ([self.buttonWidths[index] floatValue]+[self.buttonWidths[index-1] floatValue]-2*self.bottomLineWidth)/2 + self.bottomLineWidth;
            if (scale <= 1) {
                bottomLine.frame = CGRectMake(self.bottomLastX-scale*gap, bottomLine.frame.origin.y, self.bottomLineWidth+scale*gap, 2);
                
            }
            else {
                bottomLine.frame = CGRectMake(self.bottomLastX-gap, bottomLine.frame.origin.y, self.bottomLineWidth+gap-(scale-1)*gap, 2);
            }
        }
    
    if scrollGap < 0 { // 页面向左滑动 底部线向右滑动
                let scale : CGFloat = -scrollGap*2.0/Screen_Width
                let gap : CGFloat = ((buttonWidths[index] as! CGFloat)+(buttonWidths[index+1] as! CGFloat)-2*self.bottomLineWidth)/2 + bottomLineWidth;
                
                if scale <= 1 {
                    bottomLine.frame = CGRect.init(x: bottomLine.frame.origin.x, y:  bottomLine.frame.origin.y, width: bottomLineWidth+scale*gap, height: 2.0)
                }
                else {
                    bottomLine.frame = CGRect.init(x: bottomLastX+(scale-1)*gap, y: bottomLine.frame.origin.y, width: bottomLineWidth+gap-(scale-1)*gap, height: 2.0)
                }
                
            }
            else { // 页面向右滑动 底部线向左滑动
                let scale : CGFloat = scrollGap*2.0/Screen_Width;
                let gap : CGFloat = ((buttonWidths[index] as! CGFloat)+(buttonWidths[index-1] as! CGFloat)-2*bottomLineWidth)/2 + bottomLineWidth;
                if (scale <= 1) {
                    bottomLine.frame = CGRect.init(x: bottomLastX-scale*gap, y: bottomLine.frame.origin.y, width: bottomLineWidth+scale*gap, height: 2.0)
                }
                else {
                    bottomLine.frame = CGRect.init(x: bottomLastX-gap, y: bottomLine.frame.origin.y, width: bottomLineWidth+gap-(scale-1)*gap, height: 2.0)
                }
            }
    

    功能点三,判断相邻的标题按钮是否在屏幕内,这本身需要两个判断,一个是判断当前标题按钮距离屏幕左侧近还是右侧近,如果是距离左侧近,那只需要判断当前按钮左侧的标题按钮是否在,反之判断右侧的。
    我们通过当前点击按钮的center.x和其所在scrollView容器的contentOffset.x比较判断它的位置。当距离左侧近时,又有两种情况,当前按钮完全在屏幕内,当前按钮未完全在屏幕内。但是我们不用去判断而是统一为如果当前按钮不是最左侧按钮,那么它的做边距距离屏幕左边距是否有一个标题按钮的距离,如果有那么它临近的按钮自然就在屏幕上,否则就不完全在屏幕上。
    接下来判断临近的按钮是否在屏幕内。

    分页控制器示意图.png

    如图,

    CGFloat leftGap = CGRectGetMinX(button.frame) - contentOffsetX;
    
    let leftGap : CGFloat = button.frame.minX - contentOffsetX
    

    leftGap是点击按钮左边距离当前scrollView在屏幕中最左边的距离,有正负值,那么要判断点击按钮临近左侧按钮是否在屏幕内,首先判断leftGap < self.buttonWidth的条件,为true那么临近的按钮未完全显示,否则完全显示不用做滑动处理。我们做的滑动处理都是在条件为true的基础上。右侧同理。

    CGFloat contentOffsetX = backScrollView.contentOffset.x;
        CGFloat gap = button.center.x - contentOffsetX;
        
        if (gap < Screen_Width/2) { // 按钮距离屏幕左边近
            // 按钮左边距距离屏幕左边距的距离
            CGFloat leftGap = CGRectGetMinX(button.frame) - contentOffsetX;
           
            if (index == 0) { // 按钮为最左侧按钮 只把自己显示全即可
                [self reSetTitleScrollViewOffsetWithX:contentOffsetX+leftGap];
                
            } else {
                CGFloat leftButtonWidth = [self.buttonWidths[index-1] floatValue];
                if ( leftGap < leftButtonWidth){ // 按钮距离左边距不足临近按钮距离 此时需要移动
                        NSLog(@"向右滑动显示左侧按钮");
                [self reSetTitleScrollViewOffsetWithX:contentOffsetX+leftGap-leftButtonWidth];
                }
            }
            
        } else if (gap > Screen_Width/2){
        
            // 按钮右边距距离屏幕右边距的距离
            CGFloat rightGap = contentOffsetX+Screen_Width-CGRectGetMaxX(button.frame);
            
                if (index == self.titles.count-1) { // 当前是最右侧按钮
                    [self reSetTitleScrollViewOffsetWithX:contentOffsetX-rightGap];
                    
                } else {
                    
                    CGFloat rightButtonWidth = [self.buttonWidths[index+1] floatValue];
                    if (rightGap < rightButtonWidth) { // 按钮距离右边距不足一个按钮距离 此时需要移动
                     [self reSetTitleScrollViewOffsetWithX:contentOffsetX+rightButtonWidth-rightGap];
                }
                
            }
        }
    
    - (void)reSetTitleScrollViewOffsetWithX:(CGFloat)pointX{
        [UIView animateWithDuration:0.3 animations:^{
            backScrollView.contentOffset = CGPointMake(pointX, 0);
        }];
    }
    
    let contentOffsetX : CGFloat = backScrollView.contentOffset.x
            let gap : CGFloat = button.center.x - contentOffsetX
            
            if gap < Screen_Width/2 { // 按钮距离屏幕左边近
                // 按钮左边距距离屏幕左边距的距离
                let leftGap : CGFloat = button.frame.minX - contentOffsetX
                
                if index == 0 { // 按钮为最左侧按钮 只把自己显示全即可
                    self.reSetTitleScrollViewOffsetWithX(pointX: contentOffsetX+leftGap)
                }
                else {
                    let leftButtonWidth : CGFloat = buttonWidths[index-1] as! CGFloat
                    if leftGap < leftButtonWidth{ // 按钮距离左边距不足临近按钮距离 此时需要移动
                        print("向右滑动显示左侧按钮")
                        self.reSetTitleScrollViewOffsetWithX(pointX: contentOffsetX+leftGap-leftButtonWidth)
                    }
                }
            }
            else if gap > Screen_Width/2 {
                // 按钮右边距距离屏幕右边距的距离
                let rightGap : CGFloat = contentOffsetX+Screen_Width-button.frame.maxX
                if index == titles.count-1 { // 当前是最右侧按钮
                    self.reSetTitleScrollViewOffsetWithX(pointX: contentOffsetX-rightGap)
                }
                else {
                    let rightButtonWidth : CGFloat = buttonWidths[index+1] as! CGFloat
                    if rightGap < rightButtonWidth { // 按钮距离右边距不足一个按钮距离 此时需要移动
                        self.reSetTitleScrollViewOffsetWithX(pointX: contentOffsetX+rightButtonWidth-rightGap)
                    }
                }
            }
    func reSetTitleScrollViewOffsetWithX(pointX : CGFloat) {
            UIView.animate(withDuration: 0.3) { 
                backScrollView.contentOffset = CGPoint.init(x: pointX, y: 0)
            }
        }
    

    因为这种分栏标题一屏显示的标题一般都大于3个,所以如果按钮居中,那么两侧的按钮一定都在屏幕内,所以不用做处理。

    功能点四,通过页面滑动完成的监听方法,

    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) 
    

    获取当前页面滑动到的位置index,有了index就可以做类似功能三的判断了。

    详细可下载demo查看:
    OC版demo下载地址
    Swift版demo下载地址 GitHub给个Star噢!
    喜欢就点个赞呗!
    欢迎大家提出更好的改进意见和建议,一起进步!

    MARK

    OC版做了优化,可配置isBackGroud熟悉开关选择不同显示样式,显示如下:


    segmentDemo.gif

    相关文章

      网友评论

        本文标题:分页控制器半自动滑动(仿36氪)

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