iOS-UIScrollview滑动时标题栏自动隐藏和显示效果

  本文主要介绍如何实现当UIScrollview滑动时,自动隐藏和显示标题栏的效果。其中布局采用Autolayout的方式,并且为了代码精简,使用了第三方库Masonry,其使用参考【iOS-Masonry学习笔记】。使用它的原因是可以很好的结合动画效果的实现!

布局

  首先我们来介绍一下整个布局。最外层的是一个UIScrollview(mainScrollview),其子视图包括一个模拟标题栏的UIView视图,以及一个UIScrollview视图(innerScrollview),并且其是按顺序上下排列的。其中的innerScrollview包含一个UIView容器子视图,并且该容器中包括若干个UIView子视图来模拟cell。其显示效果大致如下:


常量设置

//屏幕宽度
#define UIScreenWidth [[UIScreen mainScreen] bounds].size.width
//屏幕高度
#define UIScreenHeight [[UIScreen mainScreen] bounds].size.height

//标题栏高度
NSInteger const titleHeight = 100;
//每个cell高度
NSInteger const cellHeight = 80;
//触发标题栏隐藏和显示事件的scrollview在Y方向上的滑动位移阈值
NSInteger const triggerToHideY = 200;

视图初始化

  整个布局的视图属性并不多,主要是一下这些。其中cells数组用于存储添加到container中的子UIView视图,以便于此后的视图约束设置。还有一个属性isHide是用来表示标题的状态的,如果标题隐藏则为YES,反之为NO,默认值为NO;

@property (strong, nonatomic) UIScrollView *mainScrollview;
@property (strong, nonatomic) UIView *hideView;
@property (strong, nonatomic) UIScrollView *innerScrollview;
@property (strong, nonatomic) UIView *container;
@property (strong, nonatomic) NSMutableArray *cells;

@property (nonatomic) BOOL isHide;

接下来则是手动初始化各个视图对象,并设置它们的父子关系。

- (void)viewDidLoad {
    [super viewDidLoad];

    //设置默认值
    self.isHide = NO;
    //初始化cells数组
    self.cells = [NSMutableArray new];

    //初始化mainScrollview视图
    self.mainScrollview = [UIScrollView new];
    [self.mainScrollview setBackgroundColor:[UIColor whiteColor]];
    [self.view addSubview:self.mainScrollview];

    //初始化hideView视图
    self.hideView = [UIView new];
    [self.hideView setBackgroundColor:[UIColor colorWithRed:0.000 green:0.502 blue:1.000 alpha:1.000]];
    [self.mainScrollview addSubview:self.hideView];

    //初始化container
    self.container = [UIView new];
    [self.container setBackgroundColor:[UIColor whiteColor]];

    //初始化innerScrollView,默认开启弹簧效果
    self.innerScrollview = [UIScrollView new];
    self.innerScrollview.delegate = self;
    //self.innerScrollview.bounces = NO;
    [self.innerScrollview setBackgroundColor:[UIColor blackColor]];
    [self.innerScrollview addSubview:self.container];
    [self.mainScrollview addSubview:self.innerScrollview];

    //生成若干个子视图,并添加到container中
    for (int i = 0; i < 20; i++) {
        UIView *view = [[UIView alloc] init];
        [view setBackgroundColor:[UIColor colorWithRed:1-(i*10.0/255) green:1-(i*10.0/255) blue:1-(i*10.0/255) alpha:1.0f]];

        [self.container addSubview:view];
        [self.cells addObject:view];
    }
}

视图约束

  视图的约束主要是采用Autolayout的布局思路,并使用第三方框架Masonry。哈哈大家可以看到使用框架之后省了好多代码量哈哈哈。

-(void)updateViewConstraints
{
    //设置mainScrollview约束
    [self.mainScrollview mas_makeConstraints:^(MASConstraintMaker *make) {
           //其边距与sel.view相等,即全屏显示
        make.edges.equalTo(self.view);
    }];

    //设置hideView约束
    [self.hideView mas_makeConstraints:^(MASConstraintMaker *make) {
        //其上,左,右边距紧靠mainScrollview
        make.top.left.right.equalTo(self.mainScrollview);
        //X方向上居中
        make.centerX.equalTo(self.mainScrollview);
        //设置标题的高度
        make.height.equalTo(@(titleHeight));
    }];

    //设置innerScrollview约束
    [self.innerScrollview mas_makeConstraints:^(MASConstraintMaker *make) {
        //其top紧靠标题的bottom,即它位于标题下方
        make.top.equalTo(self.hideView.mas_bottom);
        //左,右,下紧靠mainScrollview
        make.left.and.right.equalTo(self.mainScrollview);
        make.centerY.equalTo(self.mainScrollview).with.centerOffset(CGPointMake(0, titleHeight));
    }];

    //设置container约束
    [self.container mas_makeConstraints:^(MASConstraintMaker *make) {
        //containt主要约束为和innerScrollview的大小一致
        make.edges.equalTo(self.innerScrollview);
        make.width.equalTo(self.innerScrollview);
    }];

    //设置每个cell的约束
    for (int i = 0; i < self.cells.count; i++) {
        //获取需要约束的视图
        UIView *subview = self.cells[i];
        [subview mas_makeConstraints:^(MASConstraintMaker *make) {
            make.right.left.centerX.equalTo(self.container);
            make.height.equalTo(@(cellHeight));
            //如果是第一个cell,则其top属性紧靠container容器
            //否则每个cell的top属性紧靠上一个cell的bottom属性
            if (i == 0) {
                make.top.equalTo(self.container);
            }
            else{
                UIView *topView = self.cells[i - 1];
                make.top.equalTo(topView.mas_bottom);
            }
        }];
    }

    //设置容器底部约束
    [self.container mas_makeConstraints:^(MASConstraintMaker *make) {
        //约束容器的bottom紧靠最后一个cell的bottom
        //完成这个约束InnerScrollview就可以自动计算contentSize
        //然后就可以滑动了!很神奇是不是!
        UIView *lastView = self.cells[self.cells.count - 1];
        make.bottom.equalTo(lastView.mas_bottom);
    }];

    //最后不要忘了调用超类的方法
    [super updateViewConstraints];
}

自动隐藏和显示

  接下来就是如何实现自动隐藏和显示了。其实这个也很简单,了解UIScrollview的就会知道其有一个协议为UIScrollViewDelegate,其中包括了一些当scrollview滑动时会回调的函数,滑动动画开始、结束时的回调,用户手指拖拽和结束拖拽等诸多事件的回调。在这里我们主要用到的回调方法为scrollViewDidScroll:,就是当scrollview出现滑动事件时就会回调的方法。
所以首先要实现该协议。

@interface ViewController () <UIScrollViewDelegate>

然后设置innerScrollview的delegate属性。

self.innerScrollview.delegate = self;

  最后则是实现scrollViewDidScroll:方法。在方法里,先判断scrollview滑动的距离是否达到了触发自动隐藏和显示的阈值,然后判断当前标题栏的状态再是否需要进行动画隐藏和显示。其中动画实现的原理很简单,当需要隐藏标题栏时,则将标题视图移出视图(可以考虑将其也隐藏),并且重新设置InnerScrollview的显示区域。(更多UIScrollView相关参考【iOS实战-自定义的横向滚动控件CustomScrollView】)

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (scrollView.contentOffset.y > triggerToHideY) {
        if (!self.isHide) {
            self.isHide = YES;
            [UIView animateWithDuration:0.3 animations:^{
                self.hideView.center = CGPointMake(self.hideView.center.x, -self.hideView.center.y);
                self.innerScrollview.frame = CGRectMake(0, 0, UIScreenWidth, UIScreenHeight);
            }];
        }
    }
    else if(scrollView.contentOffset.y < triggerToHideY){
        if (self.isHide) {
            self.isHide = NO;
            [UIView animateWithDuration:0.3 animations:^{
                self.hideView.center = CGPointMake(self.hideView.center.x, -(self.hideView.center.y));
                self.innerScrollview.frame = CGRectMake(0, titleHeight, UIScreenWidth, UIScreenHeight);
            }];
        }
    }
}

效果图

这就是大概的效果图,对于动画的一些设置可以调整一下(动画时间啊,动画时间函数什么的),可能会有更好的效果。


源代码

  工程的源代码已经上传到了Github上。由于本项目是使用了cocoapods进行第三方框架的引入,所以如果有问题的话可以考虑pod installpod update一下。如果还有别的问题可以联系我。

  项目为iOSDemon中的iOS_UI_study目录下的UIScrollviewAndHideView工程。
传送门-iOSDemon-iOS_UI_study-UIScrollviewAndHideView